SpringBoot與動態多數據源切換


本文簡單的介紹一下基於SpringBoot框架動態多數據源切換的實現,采用主從配置的方式,配置master、slave兩個數據庫。

一、配置主從數據庫

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主庫數據源
            master:
                url: jdbc:mysql://localhost:3306/practice?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 123456
            # 從庫數據源
            slave:
                # 從數據源開關/默認關閉
                enabled: true
                url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 123456

二、創建數據源枚舉類

public enum DataSourceType {
    /**
     * 主庫
     */
    MASTER,

    /**
     * 從庫
     */
    SLAVE
}

三、數據源切換處理

  創建一個數據源切換處理類,有對數據源變量的獲取、設置和清空的方法。其中的ThreadLocal用於保存某個線程共享變量。詳細的ThreadLocal的相關了解,可以查看地址:https://www.cnblogs.com/slivelove/p/10950527.html

public class DynamicDataSourceContextHolder {
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

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

    /**
     * 設置數據源變量
     * @param dataSourceType
     */
    public static void setDataSourceType(String dataSourceType){
        log.info("切換到{}數據源", dataSourceType);
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /**
     * 獲取數據源變量
     * @return
     */
    public static String getDataSourceType(){
        return CONTEXT_HOLDER.get();
    }

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

四、繼承AbstractRoutingDataSource

  動態切換數據源主要依靠AbstractRoutingDataSource。創建一個AbstractRoutingDataSource的子類,重寫determineCurrentLookupKey方法,用於決定使用哪一個數據源。這里主要用到AbstractRoutingDataSource的兩個屬性defaultTargetDataSource和targetDataSources。defaultTargetDataSource默認目標數據源,targetDataSources(map類型)存放用來切換的數據源。

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

五、注入數據源

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
}

六、自定義多數據源切換注解

  設置攔截數據源的注解,可以設置在具體的類上,或者在具體的方法上。

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

七、AOP攔截類的實現

  通過攔截上面的注解,在其執行之前處理設置當前執行SQL的數據源的信息,CONTEXT_HOLDER.set(dataSourceType)這里的數據源信息從我們設置的注解上面獲取信息,如果沒有設置就是用默認的數據源的信息。

@Aspect
@Order(1)
@Component
public class DataSourceAspect {
    private Logger log = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.wlfu.common.annotation.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();
        }
    }
}

  這里使用@Around,在調用目標方法前,進行aop攔截,通過解析注解上的值來切換數據源。在調用方法結束后,清除數據源。也可以使用@Before和@After來編寫,原理一樣,這里就不多說了。

八、使用切換數據源注解

  設置攔截數據源的注解,可以設置在具體的類上,或者在具體的方法上。

@DataSource(value = DataSourceType.SLAVE)
@PostMapping("/list")
@ResponseBody
public TableDataInfo list() {
    
}

  運行項目后,切換的數據源如下圖:

參考資料:

1、https://blog.csdn.net/xgx120413/article/details/80743959

2、https://blog.csdn.net/u013034378/article/details/81455513

3、https://my.oschina.net/simpleton/blog/868608


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM