avatar

目录
mybatis 使用及动态数据源切换

mybatis 使用

继承spring的SqlSessionDaoSupport,注入 SqlSessionTemplate, 在xml中定义bean
Code
1
2
3
4
5
6
7
8
9
10
public class BaseDao extends SqlSessionDaoSupport {

@Resource(name = "sqlSession-touch")
public SqlSessionTemplate sqlSessionTouch;

@PostConstruct
public void initSqlSessionTemplate() {
super.setSqlSessionTemplate(sqlSessionTouch);
}
}
然后继承定义的baseDao,直接使用就可以
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Repository
public class SubscribeDao extends BaseDao{

public void insertSubscribeRecord(Map args)throws DuplicateKeyException {
sqlSessionTouch.insert("subscribe",args);
}

public boolean hasSubscribed(Map args){
return sqlSessionTouch.selectList("record",args).size() > 0 ? true : false;
}

public List selectListByVid(Map paraMap){
return (List)sqlSessionTouch.selectList("selectListByVid",paraMap);
}
}
我们看一下 定义的bean
Code
1
2
3
4




注入 sqlSessionFactory,在sqlSessionFactory 中 加入dataSource 和 mybatis的配置文件

Code
1
2
3
4
	



这块可以优化下,重写一个快速失败的SqlSessionFactory工厂,只需要重写方法 用System.exit 退出就好

Code
1
2
3
4
5
6
7
8
9
@Override
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
try {
return super.buildSqlSessionFactory();
} catch (Exception ex) {
System.exit(-1); // 强退吧!
}
return null;
}
我们来看一下 dataSource的 配置
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
   













我们再看一下mybatis的配置文件
Code
1
配置别名和 mapper等

这就是一个基本的spring 集成 mybatis的流程

多数据源 的动态切换

原理:

AbstractRoutingDataSource类的determineTargetDataSource方法,重新获取数据源

AbstractRoutingDataSource类 说明
变量:

targetDataSources: 备选的数据源了,用一个map存储

defaultTargetDataSource 就是我们在配置的时候一般都会制定一个默认的数据源就是它了,

执行顺序

(1)程序运行的时候在加载配置文件的时候,首先会执行setTargetDataSources方法,这个方法会加载配置文件中配置的数据源,存储在上面说的targetDataSource中,

(2)设置setDefaultTergetDataSource,这个就是上面说的默认的数据源,

img

(3)执行这个方法,在其中会将所有的数据源用来初始化resolvedDataSources,而当实际上和数据库产生交互的时候那么会调用到下面的方法:

img

img

思路

(1)mybatis 每次获取数据库连接 , 都会走 determineCurrentLookupKey方法,重写这个方法,让程序在每次和数据库 做交互的时候 使用自己指定的source, 使用spring aop 通过注解 进行动态切换数据源
(2)使用一个类存放 lookupKey 值,使用threadLocal 避免线程不安全,每个线程一个key值,即每个线程可以使用一个数据源

1.定义注解

Code
1
2
3
4
5
6
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Datasource {
DappDbType value() default DbType.TTS_MASTER;//默认主库
}

2.DbType 是枚举类,

Code
1
2
TTS_MASTER("touch_tts_master"),
TTS_SLAVE("touch_tts_salve");

3.重写 AbstractRoutingDataSource的determineCurrentLookupKey方法

返回的 数据 我新建一个 Holder 类进行处理

说明:

1.通过Datasource注解 + RoutingDataSourceAdvice切点 来实现切换数据源

2.通过硬编码DataSourceContextHolder.setDataSourceType()来切换数据源也是可以的,这种方式用于应对需要对一个Service方法需要调用两种以上的数据源 *

3.如果一个Service方法里面只操作一次数据库,则Datasource注解可以加在Service上,也可以加在Dao上; * 如果操作不同的数据源,则加在Service上或者使用硬编码DataSourceContextHolder.setDataSourceType()

4.如果考虑事务,则不能加在Dao上;如果只是主从数据源都要操作,则建议注解为主库,然后添加事务注解;如果不是主从数据源的关系,分为不同的数据库,则需要采用分布式事务。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TouchTTSDynamicDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(TouchTTSDynamicDataSource.class);

@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}

@Override
public Connection getConnection() throws SQLException {
Connection connection = super.getConnection();
if (DataSourceContextHolder.getDataSourceType() != null) {
logger.debug(xxxxxxxxxxxxxxxxx");
}
else{
logger.debug("xxxxxxxxxxxxxxx");
}
return connection;
}
}

4.定义 存放dataSource Type的 holder 类

(1)使用ThreadLocal 用来确保线程安全,每个线程 有一个私有变量,就不会出现线程不安全的问题, 每一个线程操作数据库的时候 都明确了 我操作的是哪个库
(2)使用aop 让每次请求来的时候自动设置 contenHolder属性值,这样在获取datasource连接的时候就会从这个类中取 设置后的值
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DataSourceContextHolder {

private static final ThreadLocal contextHolder = new ThreadLocal();
// 设置数据源类型
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}

// 获取数据源类型
public static String getDataSourceType() {

}
// 清除数据源类型
public static void clearDataSourceType() {
contextHolder.remove();
}

}

(2) 此处可以加开关 用来进行切换 的开关控制

Code
1
2
3
4
5
6
7
8
9
10
public static String getDataSourceType() {
//加入主从切换开关,如果线上出现问题,切换为主库
if(分布式config.getBoolean("touch.ttsdb.switch", false)){
return contextHolder.get();
//开关打开,则根据TouchTTSDatasource注解来选择数据源,未配置注解,则选择从库(默认)
}
else{
return DappDbType.TTS_MASTER.getName();//开关关闭,写死为主库
}
}

5.定义注解处理器

和@DataSource 注解配合使用

(1)如果一个Service上的方法,需要考虑事务,则需要考虑上述配置是否会影响事务的正确性,需要考虑添加AOP 切点的顺序问题
(2)从注解中拿到 数据库的type 然后设置给holder类的属性中,这样来连接数据库的时候 就会自动取 holder类中的数据库type 从而进行操作
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class RoutingDataSourceAdvice {

//对*Service类或者*Dao里标注了@RoutingDataSource注解的方法 进行AOP
@Pointcut( "execution(@xxxxxxxxxe * com.xxxx.xxxx..*.*(..)) ")
private void routingDappDataSource(){}

@Around("routingDappDataSource()")
public Object routing(ProceedingJoinPoint joinPoint) throws Exception {
Class clazz = joinPoint.getTarget().getClass();
String className = clazz.getName();
Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
String methodName = method.getName();
Object[] arguments = joinPoint.getArgs();

String key = null;
DappDatasource routingDataSource = method.getAnnotation(DappDatasource.class);
key = routingDataSource.value().getName();

Object result = null;
DataSourceContextHolder.setDataSourceType(key);

try {
result = joinPoint.proceed(arguments);
} catch (Throwable e) {
xxxxxxxx
} finally {
DataSourceContextHolder.clearDataSourceType();
}
return result;
}
}

6.使用

可以在方法上加

Code
1
2
@Datasource(DbType.TTS_MASTER)
public int insertFavorite(CookieUser user,String supplierId) {

Mybatis 使用接口注解方式改造:

文章作者: 美式不加糖
文章链接: http://yoursite.com/2020/02/04/mybatis%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%8A%A8%E6%80%81%E6%95%B0%E6%8D%AE%E6%BA%90%E5%88%87%E6%8D%A2/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 湖畔小屋
打赏
  • 微信
    微信
  • 支付寶
    支付寶