本文簡單的介紹一下基於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(); } }
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; }
通過攔截上面的注解,在其執行之前處理設置當前執行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