在开发中,动态数据源配置还是用的比较多的,比如在多数据源使用方面,又或者是在多个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;
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