概述
在項目中有時會需要根據情況來使用不同的數據源
實現方式
一、配置數據源
spring:
datasource:
master:
password: root
url: jdbc:mysql://localhost:3306/evid_yunyan?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
username: root
slave:
password: root
url: jdbc:mysql://localhost:3306/evid_qingzhen?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
username: root
二、創建數據源類別枚舉或其它標識
// 創建使用的數據源類型,之後根據這個數據源類型切換數據源
public enum DataSourceType {
MASTER { // 主庫
@Override
public String toString() {
return "MASTER";
}
},
SLAVE { // 從庫
@Override
public String toString() {
return "SLAVE";
}
}
}
三、創建數據源處理類
創建一個數據源切換處理類,有對數據源變量的獲取、設置和清空的方法。其中的ThreadLocal用於保存某個線程共享變量
// 創建切換數據源的處理類,主要提供在各個線程中切換線程使用的數據源
public class DynamicDataSourceContext {
// ThreadLocal變量可以將變量線程隔離,對於每個線程這個變量都是獨立的
// 把變量線程隔離開,保證在多線程下該變量的值不會被其它線程所改變
private static ThreadLocal<DataSourceType> dbType = new ThreadLocal<DataSourceType>();
public static void setDbType(DataSourceType changeType){
dbType.set(changeType);
System.out.println("數據源修改爲" + changeType);
}
public static DataSourceType getDbType(){
return dbType.get();
}
public static void clearDbType(){
dbType.remove();
}
}
四、創建動態數據源對象
動態切換數據源主要依靠AbstractRoutingDataSource
。創建一個AbstractRoutingDataSource
的子類,重寫determineCurrentLookupKey
方法,用於決定使用哪一個數據源。這裏主要用到AbstractRoutingDataSource
的兩個屬性defaultTargetDataSource和targetDataSources
。defaultTargetDataSource
默認目標數據源,targetDataSources
(map類型)存放用來切換的數據源
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> DataSources) {
// 將默認使用的放在父類的成員變量上
super.setDefaultTargetDataSource(defaultDataSource);
// 將所有數據源放在父類的MAP上
super.setTargetDataSources(DataSources);
// 必須調用此方法,將targetDataSources的屬性寫入resolvedDataSources中,實現切換數據源
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
// 該方法是必須實現的,因爲該方法控制返回使用哪一個數據源的的key
return DynamicDataSourceContext.getDbType();
}
}
五、注入數據源
Configuration
public class DataBasesConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean
@Primary // 此註解必須,這樣在需要數據源的時候纔會經過這個動態路由數據源對象進行數據源的選擇
public DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource){
// 獲取兩個數據源並放入map中
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put(DataSourceType.MASTER,masterDataSource);
dataSources.put(DataSourceType.SLAVE,slaveDataSource);
// 將這個動態的數據源作爲主數據源,這樣在代碼中使用數據源就會通過這個動態數據源去切換數據源
return new DynamicDataSource(masterDataSource, dataSources);
}
}
六、自定義多數據源切換註解
設置攔截數據源的註解,可以設置在具體的類上,或者在具體的方法上
// 目標是註解在方法上
@Target({ElementType.METHOD})
// 表示該註解在運行期間都是有效的
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
// 切換數據源的枚舉對象,默認爲master數據源
DataSourceType value() default DataSourceType.MASTER;
}
七、AOP實現切換數據源
通過攔截上面的註解,在其執行之前處理設置當前執行SQL的數據源的信息
Aspect
@Order(1) // 聲明執行順序要是第一個
@Component // 放入Spring中管理
public class DataSourceAspect {
// 只針對DataSource註解的方法作爲切入點
@Pointcut("@annotation(com.example.mp.demo1.aop.DataSource)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 獲取這個方法的簽名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 重簽名中獲取方法的註解
DataSource annotation = signature.getMethod().getAnnotation(DataSource.class);
// 設置切換數據源的處理類當前的數據源值
DynamicDataSourceContext.setDbType(annotation.value());
try {
// 執行該方法
return joinPoint.proceed();
} finally {
// 清空使用的數據源類型
DynamicDataSourceContext.clearDbType();
}
}
}
八、在方法上添加自定義多數據源切換註解
@Override
@DataSource(DataSourceType.SLAVE)
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("啓動成功");
DynamicDataSourceContext.setDbType(DataSourceType.SLAVE);
System.out.println(testDao.selectCustom());
applicationReadyEvent.getApplicationContext().stop();
}