Dynamic DataSource

一、AOP 实现

AOP 实现多数据源,可读写分离

1 配置文件

dynamic-db.master.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.master.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.master.username = xxx
dynamic-db.master.password = xxx

dynamic-db.slave.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.slave.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.slave.username = xxx
dynamic-db.slave.password = xxx

2 ContextHolder

管理 DataSource

public class DynamicDataSourceContextHolder {
/**
* 存储当前 DataSource
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

/**
* 所有 DataSource 的 key
*/
public static List<Object> dataSourceKeys = new ArrayList<>();

/**
* 切换 DataSource
*/
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}

/**
* 获取当前 DataSource,默认为 master
*/
public static String getDataSourceKey() {
String key = CONTEXT_HOLDER.get();
return key == null ? "master" : key;
}

/**
* 清空当前 DataSource
*/
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}

/**
* 判断是否当前 DataSource
*/
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
}

3 注册动态配置

继承 AbstractRoutingDataSource

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
//将当前DataSource加入应用上下文
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}

4 加载配置

@Slf4j
@Configuration
public class DataSourceConfigurer {
@Bean("master")
@Primary
@ConfigurationProperties(prefix = "dynamic-db.master")
public DataSource master() {
return DataSourceBuilder.create().build();
}
@Bean("slave")
@ConfigurationProperties(prefix = "dynamic-db.slave")
public DataSource slave() {
return DataSourceBuilder.create().build();
}

/**
* 配置动态 DataSource
*/
@Bean("dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();

//所有DataSource
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master", master());
dataSourceMap.put("slave", slave());

//设置默认DataSource
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
//设置所有DataSource
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

//将所有DataSource的key放入,以供判断
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
return dynamicRoutingDataSource;
}

/**
* 将数据源添加到 SqlSession 工厂;获取 mybatis 配置
*/
@Bean
@ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
return sqlSessionFactoryBean;
}

/**
* 将数据源添加到事物管理器
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}

5 注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}

6 Aspect

要用 @Order(0) 注解让这个切面的优先级最高,以免被其他切面(如事务管理器)影响

@Aspect
@Component
@Order(0)
public class DynamicDataSourceAspect {
//切换
@Before("@annotation(targetDataSource))")
public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) {
if (DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value())){
DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value());
}
}
//还原
@After("@annotation(targetDataSource))")
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}

7 实例

读写分离

@Repository
public interface InterMsgMapper {
@InsertProvider(type = InterMsgProvider.class, method = "insert")
int insert(InterMsg interMsg);

@TargetDataSource("slave")
@SelectProvider(type = InterMsgProvider.class, method = "findMessages")
List<InterMsg> findMessages(Long userId, Integer limit, Long lastId, Long currentTime);
}

二、普通配置

1 配置文件

dynamic-db.master.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.master.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.master.username = xxx
dynamic-db.master.password = xxx

dynamic-db.slave.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.slave.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.slave.username = xxx
dynamic-db.slave.password = xxx

2 DataSourceConfig

@Configuration
public class DataSourceConfig {
//开启 数据库下划线转驼峰
@Bean
@Scope("prototype") //默认单例会使多数据源失效
@ConfigurationProperties("mybatis.configuration")
public org.apache.ibatis.session.Configuration globalConfiguration(){
return new org.apache.ibatis.session.Configuration();
}

@Bean("masterDataSource")
@Primary
@ConfigurationProperties(prefix = "dynamic-db.master")
public DataSource master() {
return DataSourceBuilder.create().build();
}
@Bean("slaveDataSource")
@ConfigurationProperties(prefix = "dynamic-db.slave")
public DataSource slave() {
return DataSourceBuilder.create().build();
}

//多数据源事务管理
@Bean(name="tranMagMaster")
public PlatformTransactionManager bfTransactionManager(@Qualifier("masterDataSource")DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name="tranMagSlave")
public PlatformTransactionManager bfscrmTransactionManager(@Qualifier("slaveDataSource")DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

3 JdbcTemplatesConfig

@Configuration
public class JdbcTemplatesConfig extends DataSourceConfig {

//支持JdbcTemplate实现多数据源
@Bean(name="masterJdbcTemplate")
public JdbcTemplate masterJdbcTemplate(@Qualifier("masterDataSource") DataSource dataSource){
return new JdbcTemplate(dataSource);
}

@Bean(name="slaveJdbcTemplate")
public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource") DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}

3 MybatisConfigMaster

@Configuration
@MapperScan(basePackages = "com.xxx.mapper.master",
sqlSessionTemplateRef = "masterSqlSessionTemplate"
)
public class MybatisConfigMaster extends DataSourceConfig {
@Bean
SqlSessionFactory masterSqlSessionFactory(){

SqlSessionFactory sqlSessionFactory = null;
try {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setConfiguration(globalConfiguration());
sqlSessionFactoryBean.setDataSource(masterDataSource());
sqlSessionFactory = sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sqlSessionFactory;
}

@Bean
SqlSessionTemplate masterSqlSessionTemplate(){
return new SqlSessionTemplate(masterSqlSessionFactory());
}
}

4 MybatisConfigSlave

@Configuration
@MapperScan(basePackages = "com.xxx.mapper.slave",
sqlSessionTemplateRef = "slaveSqlSessionTemplate"
)
public class MybatisConfigSlave extends DataSourceConfig {
@Bean
SqlSessionFactory slaveSqlSessionFactory(){

SqlSessionFactory sqlSessionFactory = null;
try {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setConfiguration(globalConfiguration());
sqlSessionFactoryBean.setDataSource(slaveDataSource());
sqlSessionFactory = sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sqlSessionFactory;
}

@Bean
SqlSessionTemplate slaveSqlSessionTemplate(){
return new SqlSessionTemplate(slaveSqlSessionFactory());
}
}

5 使用

在 @MapperScan 配置的目录下写 mapper 就行了

Author: iMine
Link: https://imine141.github.io/2020/07/19/Spring/Data/Dynamic%20DataSource/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.