天天看點

SpringBoot多資料源druid

SpringBoot多資料源druid

    • 依賴引入
    • 資料源和druid的配置
    • druid的servlet和filter設定代碼
    • 多語言切換注解
    • 枚舉資料源
    • AOP 代碼
    • 建立dataSource的Bean對象
    • 自定義DynamicDataSource
    • 設定資料源注解的有效範圍
    • 循環依賴問題
    • 效果
    • 源碼
      • AbstractRoutingDataSource類

依賴引入

主要包含JDBC、mysql、druid(需要logj4d的依賴)、aop(多資料源選擇時需要切面)

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
           

資料源和druid的配置

多資料源我配了一個local、remote。

druid的基礎配置可以通過配置檔案也可以代碼配置,建議配置檔案。但是關于servlet和filter可以考慮通過的代碼設定。

####################################druid連接配接池配置-開始##########################################
#連接配接池類型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters=stat
### 配置初始化大小、最小、最大
#資料庫連接配接池初始化大小
spring.datasource.initialSize=100
#最小空閑數
spring.datasource.minIdle=500
#最大活躍數
spring.datasource.maxActive=1000
#配置擷取連接配接等待逾時的時間
spring.datasource.maxWait=60000
#配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接配接,機關是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
#配置一個連接配接在池中最小生存的時間,機關是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
#測試連接配接
spring.datasource.validationQuery=select 'x'
#申請連接配接的時候檢測,建議配置為true,不影響性能,并且保證安全性
spring.datasource.testWhileIdle=true
#擷取連接配接時執行檢測,建議關閉,影響性能
spring.datasource.testOnBorrow=false
#歸還連接配接時執行檢測,建議關閉,影響性能
spring.datasource.testOnReturn=false
#是否開啟PSCache,PSCache對支援遊标的資料庫性能提升巨大,oracle建議開啟,mysql下建議關閉
spring.datasource.poolPreparedStatements=false
#開啟poolPreparedStatements後生效
spring.datasource.maxOpenPreparedStatements=20
#配置擴充插件,常用的插件有=>stat:監控統計  log4j2:日志  wall:防禦sql注入
pring.datasource.filters.commons-log.connection-logger-name=stat,wall,log4j2
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000
# Druid WebStatFilter配置,url統計
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions='*.gif,*.png,*.jpg,*.html,*.js,*.css,*.ico,/druid/*'
# Druid StatViewServlet配置,監控界面配置
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.login-username=root
spring.datasource.druid.stat-view-servlet.login-password=123456
########################druid連接配接池配置-結束##########################################

#資料庫配置
spring.datasource.local.username=root
spring.datasource.local.password=123456
spring.datasource.local.url=jdbc:mysql://127.0.0.1/spring_boot_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.local.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.remote.username=root
spring.datasource.remote.password=123456
spring.datasource.remote.url=jdbc:mysql://127.0.0.1/spring_boot_test_remote?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.remote.driver-class-name=com.mysql.jdbc.Driver

           

druid的servlet和filter設定代碼

如果完全按照上面的配置,這步需要忽略。

這個代碼就是手動設定servlet和filter,登入資訊和過濾不需要統計的請求。

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean druidServletRegistrationBean(){
        ServletRegistrationBean<Servlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");
        Map<String,String> initParams = new HashMap<>();
        initParams.put("loginUsername","admin");
        initParams.put("loginPassword","123456");
        //背景允許誰可以通路
        //initParams.put("allow", "localhost"):表示隻有本機可以通路
        //initParams.put("allow", ""):為空或者為null時,表示允許所有通路
        initParams.put("allow","");
        //deny:Druid 背景拒絕誰通路
        //initParams.put("msb", "192.168.1.20");表示禁止此ip通路

        servletRegistrationBean.setInitParameters(initParams);
        return servletRegistrationBean;
    }

    //配置 Druid 監控 之  web 監控的 filter
    //WebStatFilter:用于配置Web和Druid資料源之間的管理關聯監控統計
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        //exclusions:設定哪些請求進行過濾排除掉,進而不進行統計
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);

        //"/*" 表示過濾所有請求
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}
           

多語言切換注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
     * 切換資料源名稱
     */
    DataSourceType value() default DataSourceType.REMOTE;
}
           

枚舉資料源

有多資料就設定多少

public enum DataSourceType {
    REMOTE,
    LOCAL
}
           

AOP 代碼

切面被注解的需要指定資料源的方法,這裡的設定的資料源是單個線程有效。但是也會出現互相調用這個時候就是最後一個調用為線程資料源。

@Aspect
@Order(1)
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.spring_boot.jdbc.mult.DataSource)")
    public void dsPointCut() {

    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        } finally {
            // 銷毀資料源 在執行方法之後
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}
           

建立dataSource的Bean對象

加載兩個(可以有多個)資料源建立bean,并且設定一個預設資料源,将所有的資料源,放在Map中,執行個體化自定義DynamicDataSource(類名字自定義無影響);

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.remote")
    public DataSource remoteDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.local")
    //@ConditionalOnProperty(prefix = "spring.datasource.local", name = "enabled", havingValue = "true")
    public DataSource localDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
        targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
        return new DynamicDataSource(remoteDataSource, targetDataSources);
    }
}
           

自定義DynamicDataSource

這個類就是我們自定義的dataSource對象的類,主要繼承AbstractRoutingDataSource抽象類,定義一個構造方法取設定預設資料源、設定所有資料源、檢查資料源,重寫determineCurrentLookupKey()每次擷取連接配接對象是取的資料源的key。

public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        // afterPropertiesSet()方法調用時用來将targetDataSources的屬性寫入resolvedDataSources中的
        super.afterPropertiesSet();
    }

    /**
     * 根據Key擷取資料源的資訊
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
           

設定資料源注解的有效範圍

每次觸發資料源切換,單個線程的資料源都保持統一。沒有考慮一個線程中多次調用資料源切換,這個會導緻一個業務請求在多個資料源中進行,如果是讀寫分離設計,會有一定的資料延遲影響。需要開發手動控制整體業務資料源的一緻性。

public class DynamicDataSourceContextHolder {

    /**
     * 使用ThreadLocal維護變量,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,
     *  是以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 設定資料源變量
     * @param dataSourceType
     */
    public static void setDataSourceType(String dataSourceType){
        System.out.printf("切換到{%s}資料源", dataSourceType);
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /**
     * 擷取資料源變量
     * @return
     */
    public static String getDataSourceType(){
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空資料源變量
     */
    public static void clearDataSourceType(){
        CONTEXT_HOLDER.remove();
    }
}
           

控制層代碼

@RestController
public class TestController {

  	@Autowired
  	JdbcTemplate jdbcTemplate;
    
	@GetMapping("/local")
    @DataSource(value = DataSourceType.LOCAL)
    public List<Map<String, Object>> local(){
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from mmp");
        return maps;
    }
    @GetMapping("/remote")
    @DataSource(value = DataSourceType.REMOTE)
    public List<Map<String, Object>> remote(){
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from mmp");
        return maps;
    }
 }
           

循環依賴問題

如果出現下面的問題就再的啟動類上面的加入

SpringBoot多資料源druid
SpringBoot多資料源druid

效果

SpringBoot多資料源druid
SpringBoot多資料源druid
SpringBoot多資料源druid
SpringBoot多資料源druid

druid位址和使用者名和密碼

SpringBoot多資料源druid

源碼

AbstractRoutingDataSource類

我們在可以看到系統在擷取的datasource對象是擷取通過determineCurrentLookupKey()擷取多個datasource存放的Map,這樣我們如果我們需要多資料源動态切換隻需要重寫determineCurrentLookupKey()并且動态擷取的key,就可以實作動态對象擷取。

SpringBoot多資料源druid

如果的多資料druid跑起來有問題可以試一下SpringBoot項目使用druid

SpringBoot學習之路

代碼位址

https://gitee.com/zhang798/spring-demo.git

分支 springBoot_druid_doubleDataSource

git clone https://gitee.com/zhang798/spring-demo.git -b springBoot_druid_doubleDataSource
           

繼續閱讀