0%

springboot中mybatis多数据源动态切换实现

在开发中,动态数据源配置还是用的比较多的,比如在多数据源使用方面,又或者是在多个DB之间切换方面。这里给出一个动态数据源的配置方案,两个DB均以mysql为例。

多数据源配置引入

mybatis和mysql在springboot中的引入这里就不在说了,不了解的可以参见springboot中mysql与mybatis的引入

数据源配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
datasource:
master:
type: com.alibaba.druid.pool.DruidDataSource
jdbc-url: jdbc:mysql://127.0.0.1:3306/sbac_master?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
log:
type: com.alibaba.druid.pool.DruidDataSource
jdbc-url: jdbc:mysql://127.0.0.1:3306/sbac_log?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis的配置引入如下:

1
2
3
mybatis:
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:com/lazycece/sbac/mysql/data/dao/*/mapper/*.xml

这里已然使用的是springboot的自动配置功能配置mybatis信息,只是手动指定了数据源的。如下所示,指定了master和log两个数据源,设置master为默认数据源:

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
@Configuration
public class MultiDataSource {

public static final String MASTER_DATA_SOURCE = "masterDataSource";
public static final String LOG_DATA_SOURCE = "logDataSource";

@Bean(name = MultiDataSource.MASTER_DATA_SOURCE)
@ConfigurationProperties(prefix = "datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = MultiDataSource.LOG_DATA_SOURCE)
@ConfigurationProperties(prefix = "datasource.log")
public DataSource logDataSource() {
return DataSourceBuilder.create().build();
}

@Primary
@Bean(name = "dynamicDataSource")
public DynamicDataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
Map<Object, Object> dataSourceMap = new HashMap<>(4);
dataSourceMap.put(MASTER_DATA_SOURCE, masterDataSource());
dataSourceMap.put(LOG_DATA_SOURCE, logDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
}

动态数据源路由实现

引入了配置信息之后,便是该说如何实现多数据源切换了。我们是通过实现AbstractRoutingDataSource类的determineCurrentLookupKey方法来实现数据源的动态路由,设置ThreadLocal线程保护变量存储数据源key,确保线程间不受影响。

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
package com.lazycece.sbac.mysql.multi.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* @author lazycece
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);

private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<>();

static void changeDataSource(String dataSourceKey) {
DATA_SOURCE_KEY.set(dataSourceKey);
}

static void clearDataSource() {
DATA_SOURCE_KEY.remove();
}

@Override
protected Object determineCurrentLookupKey() {
String key = DATA_SOURCE_KEY.get();
LOGGER.info("current data-source is {}", key);
return key;
}
}

随后,便是用AOP的方式来实现数据源的动态切换,注解和切面定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DataSource {

String value();
}

@Component
@Aspect
public class DataSourceConfig {

@Before("@annotation(dataSource)")
public void beforeSwitchDataSource(DataSource dataSource) {
DynamicDataSource.changeDataSource(dataSource.value());
}

@After("@annotation(DataSource)")
public void afterSwitchDataSource() {
DynamicDataSource.clearDataSource();
}
}

动态数据源切换使用

动态数据源切换只需要在业务中使用@DataSource注解来标明需要使用的数据源即可,如下所示(这里只贴出关键代码):

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
32
@Service
public class DynamicDataSourceServiceImpl implements DynamicDataSourceService {

@Resource
private UserDao userDao;
@Resource
private SystemLogDao systemLogDao;

@Override
@DataSource(value = MultiDataSource.MASTER_DATA_SOURCE)
public void addUserInfo(User user) {
userDao.insert(user);
}

@Override
@DataSource(value = MultiDataSource.MASTER_DATA_SOURCE)
public User getUserInfo(String username) {
return userDao.findByUsername(username);
}

@Override
@DataSource(value = MultiDataSource.LOG_DATA_SOURCE)
public void addSystemLog(SystemLog systemLog) {
systemLogDao.insert(systemLog);
}

@Override
@DataSource(value = MultiDataSource.LOG_DATA_SOURCE)
public List<SystemLog> getSystemLogInfo(Date beginTime, Date endTime) {
return systemLogDao.findByCreateTime(beginTime, endTime);
}
}

案例源码

案例源码地址: https://github.com/lazycece/springboot-actual-combat/tree/master/springboot-ac-mysql/springboot-ac-mysql-multi