在實際開發過程中,可能有這樣的情況,一個項目涉及到多個數據庫.有的數據需要插入這個數據庫,而有的數據需要插入到另一個數據庫裏面.所以接下來,就來教大家怎麼用最簡單的方式來實現這一需求.
我們將會用Spring裏面的AOP設計來實現這一邏輯.通過把註解添加在方法上(DAO方法上)來實現數據源的動態切換.簡單來說就是當前方法操作的是哪個數據源,我們就需要在方法上添加註解標識使用哪個數據源.
我們注意到在Spring2.0.1中引入了AbstractRoutingDataSource,該類充當了DataSource的路由中介,能有在運行時,根據某種key值來動態切換到真正的DataSource上.AbstractRoutingDataSource就是我們實現數據源動態切換的關鍵部分.
接下來,爲了加深大家的理解,我們通過一個簡單的例子一步一步來實現這一過程.假設現在,我們有這樣一個需求.項目裏面需要到三個數據源(三個數據庫).
一 數據源枚舉
我們用一個枚舉來表示這三個數據源.每個枚舉表示一個數據源,三個數據源,三個枚舉.如下:
public enum EDataSourceType {
/**
* 【基礎】數據庫數據源名稱
*/
BASIC,
/**
* 【歷史】數據庫數據源名稱
*/
HISTORY,
/**
* 【統計】數據庫數據源名稱
*/
STATIS
}
二 數據源配置
三個數據源肯定都有各自的url地址,所以配置文件(application.yml)配置如下:
spring:
application:
name: table-shard
datasource:
name: mysql_test
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 多數據源
basic: #數據源 -- 基礎數據庫
url: jdbc:mysql://127.0.0.1:3306/basicdb?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456
history: #數據源 -- 歷史數據庫
url: jdbc:mysql://127.0.0.1:3306/historydb?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456
statis: #數據源 -- 統計數據庫
url: jdbc:mysql://127.0.0.1:3306/statisdb?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456
filters: stat
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 1
min-idle: 1
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
注意三個數據源的配置
三 數據源(DataSource)路由的實現
AbstractRoutingDataSource是DataSource的路由中介的處理器.也是我們實現數據源動切換最關鍵的部分.我們自定義DynamicRoutingDataSource類繼承AbstractRoutingDataSource加入數據源切換的邏輯.
在運行過程中Spring會調用AbstractRoutingDataSource.determineCurrentLookupKey()方法動態獲取到需要的數據源.DynamicRoutingDataSource構造函數需要兩個參數一個是默認的數據源,另一個是多數據源map.key是數據源標識也就是我們上面的數據源枚舉EDataSourceType,value是DataSource(每個數據源對應的DataSource我們會在配置文件裏面添加進來).這樣在運行(AOP)的時候我們只需要調用setDataSource()方法指定到我們需要的數據源上就可以了.
/**
* @name: DynamicRoutingDataSource
* @author: tuacy.
* @date: 2019/6/24.
* @version: 1.0
* @Description: 動態數據源設置,每次訪問之前設置,訪問完成之後在清空
* (AbstractRoutingDataSource相當於數據源路由中介,能有在運行時, 根據某種key值來動態切換到真正的DataSource上)
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<EDataSourceType> contextHolder = new ThreadLocal<>();
/**
* 構造函數
*
* @param defaultTargetDataSource 默認的數據源
* @param targetDataSources 多數據源每個key對應一個數據源
*/
public DynamicRoutingDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
// 設置默認數據源
super.setDefaultTargetDataSource(defaultTargetDataSource);
// 設置多數據源. key value的形式
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
/**
* 多數據源對應的key, 會通過這個key找到我們需要的數據源
*/
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
/**
* 設置使用哪個數據源
*
* @param dataSource 數據源對應的名字
*/
public static void setDataSource(EDataSourceType dataSource) {
contextHolder.set(dataSource);
}
/**
* 獲取數據源對應的名字
* @return 數據源對應的名字
*/
public static EDataSourceType getDataSource() {
return contextHolder.get();
}
/**
* 清空掉
*/
public static void clearDataSource() {
contextHolder.remove();
}
}
四 配置
4.1 DynamicDataSourceConfig配置
把我們自定義的DynamicRoutingDataSource類注入到IOC容器裏面去.特別注意DynamicRoutingDataSource類的構造函需要兩個參數一個是默認的數據源,一個是多數據源map.注意map key是數據源標識(我們定義的數據源枚舉EDataSourceType),value是每個數據源對應的DataSource(url,password等配置信息).
public class DynamicDataSourceConfig {
/**
* 基礎數據庫 application.yml spring.datasource.druid.basic配置信息
*
* @return DataSource
*/
@Bean(name = "basicDataSource")
@ConfigurationProperties("spring.datasource.druid.basic")
public DataSource basicDataSource() {
return new DruidDataSource();
}
/**
* 歷史數據庫 application.yml spring.datasource.druid.history配置信息
*
* @return DataSource
*/
@Bean(name = "historyDataSource")
@ConfigurationProperties("spring.datasource.druid.history")
public DataSource historyDataSource() {
return new DruidDataSource();
}
/**
* 統計數據庫 application.yml spring.datasource.druid.statis配置信息
*
* @return DataSource
*/
@Bean(name = "statisDataSource")
@ConfigurationProperties("spring.datasource.druid.statis")
public DataSource statisDataSource() {
return new DruidDataSource();
}
/**
* 我們我們自定義的數據源DynamicRoutingDataSource添加到Spring容器裏面去
*
* @param basicDataSource 基礎數據庫
* @param historyDataSource 歷史數據庫
* @param statisDataSource 統計數據庫
*/
@Bean
@Primary
public DynamicRoutingDataSource dataSource(DataSource basicDataSource, DataSource historyDataSource, DataSource statisDataSource) {
Map<Object, Object> targetDataSources = Maps.newHashMapWithExpectedSize(3);
// 每個key對應一個數據源
targetDataSources.put(EDataSourceType.BASIC, basicDataSource);
targetDataSources.put(EDataSourceType.HISTORY, historyDataSource);
targetDataSources.put(EDataSourceType.STATIS, statisDataSource);
return new DynamicRoutingDataSource(basicDataSource, targetDataSources);
}
}
4.2 啓動類上的配置
在啓動類上添加.過濾掉DataSourceAutoConfiguration配置,進入我們的配置DynamicDataSourceConfig.就是把默認的數據源配置替換成我們自定義的數據源配置.
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
@Import({DynamicDataSourceConfig.class})
五 AOP
AOP的實現部分就是在運行的時候去調用DynamicRoutingDataSource.setDataSource().我們把AOP註解添加在方法上,在進入方法之前我們先調用DynamicRoutingDataSource.setDataSource()來設置我們需要的數據源.實現邏輯如下.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceAnnotation {
/**
* 數據源類型
* @return 數據源類型
*/
EDataSourceType sourceType();
}
@Aspect
@Component
@Order(value = 1)
public class DataSourceAspect {
/**
* 所有添加了DataSourceAnnotation的方法都進入切面
*/
@Pointcut("@annotation(com.tuacy.tableshard.tableextend.multisource.DataSourceAnnotation)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 在執行方法之前設置使用哪個數據源
DataSourceAnnotation ds = method.getAnnotation(DataSourceAnnotation.class);
if (ds == null) {
DynamicRoutingDataSource.setDataSource(EDataSourceType.BASIC);
} else {
DynamicRoutingDataSource.setDataSource(ds.sourceType());
}
try {
return point.proceed();
} finally {
DynamicRoutingDataSource.clearDataSource();
}
}
}
六 使用
使用很簡單,就是在DAO對應的方法上添加@DataSourceAnnotation註解來指定數據源.這樣這個DAO方法裏面使用到的數據源就是我們指定的數據源.
/**
* DataSourceAnnotation 用於指定數據源,放到統計數據庫裏面
*/
@Override
@DataSourceAnnotation(sourceType = EDataSourceType.STATIS)
@Transactional(rollbackFor = Exception.class)
public int insertItem(AccHour item) {
return accHourMapper.insertItem(item);
}
上述動態數據源的切換整個邏輯我們就已經全部實現了,很簡單的.主要就是AbstractRoutingDataSource以及AOP的使用.更加詳細的代碼可以看下 https://github.com/tuacy/java-study/tree/master/tableshard