springboot中實現多數據源

springboot中實現多數據源

一、什麼場景須要多數據源

  • 業務讀寫分離
  • 業務分庫
  • 業務功能模塊拆分多庫

二、常見的多數據源的方案

  • 按照數據源分別把mapper和entity放到不一樣的package下,而後用兩個數據源分別註冊、掃描對應的package,獨立的sessionfactoty
  • 基於aop動態的切換的數據源

三、本文重點介紹的是基於aop的方案

3.一、原理介紹

  • 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.二、代碼示例

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);
  }
}

四、後續

更多精彩,敬請關注, 程序員導航網 https://chenzhuofan.topmysql

相關文章
相關標籤/搜索