原创

springboot中实现多数据源

springboot中实现多数据源

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://chenzhuofan.top

正文到此结束