1. 程式人生 > >SpringBoot配置多資料來源(druid)

SpringBoot配置多資料來源(druid)

分析

spring本身是支援多資料來源動態切換的,AbstractRoutingDataSource這個抽象類就是spring提供的一個數據源路由的一個入口,該抽象類暴露了一個determineCurrentLookupKey()的方法,該方法返回值是Object,該返回值作為key去取Map中的DataSource。

  • AbstractRoutingDataSource
    • getConnection()
      • determineTargetDataSource() 從Map中通過key獲取DataSource
        • determineCurrentLookupKey() 獲取key

1.建立一個執行緒執行緒安全的Holder來切換Key

public class DynamicDataSourceHolder {

    public static ThreadLocal<DataSourceKey> keyThreadLocal = new ThreadLocal<>();

    public static void clear(){
        keyThreadLocal.remove();
    }

    public static void set(DataSourceKey key){
        keyThreadLocal.set
(key); } public static DataSourceKey get(){ DataSourceKey key = keyThreadLocal.get(); return null==key?DataSourceKey.DB:key; } }

2.繼承AbstractRoutingDataSource設定key

public class DynamicRoutingDataSource extends AbstractRoutingDataSource{
    @Nullable
    @Override
    protected
Object determineCurrentLookupKey() { return DynamicDataSourceHolder.get(); } }

3.編寫Config來初始化DataSource

@Configuration
public class DynamicDatasourceConfig {

    @Autowired
    ApplicationContext applicationContext;

    @Bean("druid_db")//必須加上該註解,否則 @ConfigurationProperties無效
    @ConfigurationProperties(prefix = "dynamic-datasource.druid-datasources.db")
    public DataSource db(StandardEnvironment env){
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        return common(env,druidDataSource);
    }
    @Bean("druid_db1")
    @ConfigurationProperties(prefix = "dynamic-datasource.druid-datasources.db1")
    public DataSource db1(StandardEnvironment env){
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        return common(env,druidDataSource);
    }
    @Bean("druid_db2")
    @ConfigurationProperties(prefix = "dynamic-datasource.druid-datasources.db2")
    public DataSource db2(StandardEnvironment env){
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        return common(env,druidDataSource);
    }

    @Bean("dataSource")
    public DataSource dynamicDataSource(StandardEnvironment env) {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        Map<Object,Object> map = new HashMap<>();
        map.put(DataSourceKey.DB,applicationContext.getBean("druid_db"));
        map.put(DataSourceKey.DB1,applicationContext.getBean("druid_db1"));
        map.put(DataSourceKey.DB2,applicationContext.getBean("druid_db2"));
        dynamicRoutingDataSource.setDefaultTargetDataSource(applicationContext.getBean("druid_db"));
        dynamicRoutingDataSource.setTargetDataSources(map);
        return dynamicRoutingDataSource;
    }

    public DataSource common(StandardEnvironment env, DruidDataSource druidDataSource){
        Properties properties = new Properties();
        PropertySource<?> appProperties =  env.getPropertySources().get("applicationConfig: [classpath:/application.yml]");
        Map<String,Object> source = (Map<String, Object>) appProperties.getSource();
        properties.putAll(source);
        druidDataSource.configFromPropety(properties);
        return druidDataSource;
    }

}

4.通過aop動態去切換key

該註解確定的該方法使用哪一個資料來源

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    DataSourceKey value() default DataSourceKey.DB;
}

資料來源的key

public enum  DataSourceKey {
    DB,DB1,DB2,DB3,DB4;
}

一個是攔截特定的方法和攔截有TargetDataSource註解的方法去動態切換

@Aspect
@Component
@Order(-100)//提高優先順序
public class DynamicDataSourceAop {

    @Pointcut(value = "execution(public * com.yzz.boot..*.mapper ..*.*(..))")
    public void defaultDataSource(){}

    @Before(value = "defaultDataSource()")
    public void setDefaultDataSource(){

    }
    //沒有註解,就選擇預設的資料來源
    @Before(value = "@annotation(dataSource)&&defaultDataSource()")
    public void setDynamicDataSource(TargetDataSource dataSource){
        if (null == dataSource){
            System.err.println("設定預設資料來源"+DataSourceKey.DB);
            DynamicDataSourceHolder.set(DataSourceKey.DB);
        }else {
            System.err.println("切換資料來源"+dataSource.value());
            DynamicDataSourceHolder.set(dataSource.value());
        }
    }
//清除該執行緒當前的資料
    @After(value = "defaultDataSource()&&@annotation(com.yzz.boot.dyConfig.ann.TargetDataSource)")
    public void clean(){
        System.err.println("清除當前執行緒的資料來源");
        DynamicDataSourceHolder.clear();
    }
}

5.Mapper通過方法上通過註解去動態選擇資料來源

@Mapper
public interface TestMapper {
    @TargetDataSource(value = DataSourceKey.DB1)
    @Select("select * from t_es_test limit 5")
    List<HashMap> getAll();

    @TargetDataSource(value = DataSourceKey.DB2)
    @Select("select * from t_es_test limit 5")
    List<HashMap> getAll1();
}

6.程式入口去除預設的連線池的配置類

//去除預設的連線池
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
//掃描響應的包
@MapperScan("com.yzz.boot.*.mapper")
public class BootApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class);
    }
}

7.配置檔案

spring:
  profiles:
    active: system

dynamic-datasource:
  druid:
    filters: stat
    maxActive: 20
    initialSize: 1
    maxWait: 30000
    minIdle: 10
    maxIdle: 15
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    maxOpenPreparedStatements: 20
    removeAbandoned: true
    removeAbandonedTimeout: 1800
    logAbandoned: true
  druid-datasources:
    db:
      url: jdbc:mysql://192.168.1.12:3306/yzz
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
    db1:
      url: jdbc:mysql://192.168.1.12:3306/yzz
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
    db2:
      url: jdbc:mysql://192.168.1.12:3306/yzz
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver


jdbc-conn:
      url: jdbc:mysql://192.168.1.12:3306/yzz
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver

總結

通過註解來動態原則key,spring的DataSource路由選擇器通過key從之前設定進去的DataSource Map中獲取響應的DataSource,從而達到了動態切換的目的。通過ThreadLocal來儲存key,保證了數=資料在多執行緒環境下的正確性。