1, 什么场景需要多数据源
业务读写分离
业务分库
业务功能模块拆分多库
2, 常见的多数据源的方案
按照数据源分别把 mapper 和 entity 放到不同的 package 下, 然后用两个数据源分别注册, 扫描对应的 package, 独立的 sessionfactoty
基于 aop 动态的切换的数据源
3, 本文重点介绍的是基于 aop 的方案
3.1, 原理介绍
DatabaseType 列出所有的数据源的 key---key
DatabaseContextHolder 是一个线程安全的 DatabaseType 容器, 并提供了向其中设置和获取 DatabaseType 的方法
DynamicDataSource 继承 AbstractRoutingDataSource 并重写其中的方法 determineCurrentLookupKey(), 在该方法中使用 DatabaseContextHolder 获取当前线程的 DatabaseType
MyBatisConfig 中生成 2 个数据源 DataSource 的 bean---value
MyBatisConfig 中将 1) 和 4) 组成的 key-value 对写入到 DynamicDataSource 动态数据源的 targetDataSources 属性 (当然, 同时也会设置 2 个数据源其中的一个为 DynamicDataSource 的 defaultTargetDataSource 属性中)
将 DynamicDataSource 作为 primary 数据源注入到 SqlSessionFactory 的 dataSource 属性中去, 并且该 dataSource 作为 transactionManager 的入参来构造 DataSourceTransactionManager
使用的时候, 在 dao 层或 service 层先使用 DatabaseContextHolder 设置将要使用的数据源 key, 然后再调用 mapper 层进行相应的操作, 建议放在 dao 层去做 (当然也可以使用 spring aop + 自定注解去做)
注意: 在 mapper 层进行操作的时候, 会先调用 determineCurrentLookupKey() 方法获取一个数据源 (获取数据源: 先根据设置去 targetDataSources 中去找, 若没有, 则选择 defaultTargetDataSource), 之后在进行数据库操作.
3.2, 代码示例
a, 配置文件
- spring.aop.proxy-target-class = true
- spring.aop.auto = true
- spring.datasource.druid.db1.url =
- spring.datasource.druid.db1.username =
- spring.datasource.druid.db1.password =
- spring.datasource.druid.db1.driver-class-name = com.MySQL.jdbc.Driver
- spring.datasource.druid.db1.initialSize = 5
- spring.datasource.druid.db1.minIdle = 5
- spring.datasource.druid.db1.maxActive = 20
- spring.datasource.druid.db2.url =
- spring.datasource.druid.db2.username =
- spring.datasource.druid.db2.password =
- spring.datasource.druid.db2.driver-class-name = com.MySQL.jdbc.Driver
- spring.datasource.druid.db2.initialSize = 5
- spring.datasource.druid.db2.minIdle = 5
- spring.datasource.druid.db2.maxActive = 20
- spring.datasource.druid.db3.url =
- spring.datasource.druid.db3.username =
- spring.datasource.druid.db3.password =
- spring.datasource.druid.db3.driver-class-name = com.MySQL.jdbc.Driver
- spring.datasource.druid.db3.initialSize = 5
- spring.datasource.druid.db3.minIdle = 5
- spring.datasource.druid.db3.maxActive = 20
b, 生成 Datasource
- @Bean(name = "db1")
- @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
- public DataSource db1() {
- return DruidDataSourceBuilder.create().build();
- }
- @Bean(name = "db2")
- @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
- public DataSource db2() {
- return DruidDataSourceBuilder.create().build();
- }
- @Bean(name = "db3")
- @ConfigurationProperties(prefix = "spring.datasource.druid.db3")
- public DataSource db3() {
- return DruidDataSourceBuilder.create().build();
- }
c, 定义数据源的 key
- @Getter
- @AllArgsConstructor
- public enum DBTypeEnum {
- db1("db1"),
- db2("db2"),
- db3("db3");
- private String value;
- }
d, 构造数据源和 sessionFactory
- /**
- * 动态数据源配置
- *
- * @return
- */
- @Bean
- @Primary
- public DataSource multipleDataSource(
- @Qualifier("db1") DataSource db1,
- @Qualifier("db2") DataSource db2,
- @Qualifier("db3") DataSource db3) {
- DynamicDataSource dynamicDataSource = new DynamicDataSource();
- Map<Object, Object> targetDataSources = new HashMap<>();
- targetDataSources.put(DBTypeEnum.db1.getValue(), db1);
- targetDataSources.put(DBTypeEnum.db2.getValue(), db2);
- targetDataSources.put(DBTypeEnum.db3.getValue(), db3);
- dynamicDataSource.setTargetDataSources(targetDataSources);
- dynamicDataSource.setDefaultTargetDataSource(db2);
- return dynamicDataSource;
- }
- @Bean("sqlSessionFactory")
- public SqlSessionFactory sqlSessionFactory() throws Exception {
- MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
- sqlSessionFactory.setDataSource(multipleDataSource(db1(), db2(), db3()));
- MybatisConfiguration configuration = new MybatisConfiguration();
- configuration.setJdbcTypeForNull(JdbcType.NULL);
- configuration.setMapUnderscoreToCamelCase(true);
- configuration.setCacheEnabled(false);
- sqlSessionFactory.setConfiguration(configuration);
- // PerformanceInterceptor(),OptimisticLockerInterceptor()
- // 添加分页功能
- sqlSessionFactory.setPlugins(new Interceptor[] {paginationInterceptor()});
- sqlSessionFactory.setGlobalConfig(globalConfiguration());
- return sqlSessionFactory.getObject();
- }
e, 重写 datasource 切换策略
- public class DynamicDataSource extends AbstractRoutingDataSource {
- @Override
- protected Object determineCurrentLookupKey() {
- return DbContextHolder.getDbType();
- }
- }
f, 保存数据源切换的上下文信息
- public class DbContextHolder {
- private static final ThreadLocal contextHolder = new ThreadLocal<>();
- /**
- * 设置数据源
- *
- * @param dbTypeEnum
- */
- public static void setDbType(DBTypeEnum dbTypeEnum) {
- contextHolder.set(dbTypeEnum.getValue());
- }
- /**
- * 取得当前数据源
- *
- * @return
- */
- public static String getDbType() {
- return (String) contextHolder.get();
- }
- /** 清除上下文数据 */
- public static void clearDbType() {
- contextHolder.remove();
- }
- }
g,aop 实现动态的数据源切换
- @Component
- @Order(value = -100)
- @Slf4j
- @Aspect
- public class DataSourceSwitchAspect {
- @Pointcut("execution(* top.zhuofan.datafly.mapper.db1..*.*(..))")
- private void db1Aspect() {}
- @Pointcut("execution(* top.zhuofan.datafly.mapper.db2..*.*(..))")
- private void db2Aspect() {}
- @Pointcut("execution(* top.zhuofan.datafly.mapper.db3..*.*(..))")
- private void db3Aspect() {}
- @Before("db1Aspect()")
- public void db1() {
- log.debug("切换到 db1 数据源...");
- DbContextHolder.setDbType(DBTypeEnum.db1);
- }
- @Before("db2Aspect()")
- public void db2() {
- log.debug("切换到 db2 数据源...");
- DbContextHolder.setDbType(DBTypeEnum.db2);
- }
- @Before("db3Aspect()")
- public void db3() {
- log.debug("切换到 db3 数据源...");
- DbContextHolder.setDbType(DBTypeEnum.db3);
- }
- }
4, 后续
来源: https://www.cnblogs.com/chen-xing/p/10776280.html