mybatis-plus @DS實現(xiàn)動態(tài)切換數(shù)據(jù)源原理
1、mybatis-plus @DS實現(xiàn)動態(tài)切換數(shù)據(jù)源原理
首先mybatis-plus使用com.baomidou.dynamic.datasource.AbstractRoutingDataSource繼承 AbstractDataSource接管數(shù)據(jù)源;具體實現(xiàn)類為com.baomidou.dynamic.datasource.DynamicRoutingDataSource。項目初始化調(diào)用public synchronized void addDataSource(String ds, DataSource dataSource)加載數(shù)據(jù)源,數(shù)據(jù)源存進dataSourceMap中。
private Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
private Map<String, DynamicGroupDataSource> groupDataSources = new ConcurrentHashMap<>();
public synchronized void addDataSource(String ds, DataSource dataSource) {
? ? if (p6spy) {
? ? ? dataSource = new P6DataSource(dataSource);
? ? }
? ? dataSourceMap.put(ds, dataSource);
? ? if (ds.contains(UNDERLINE)) {
? ? ? String group = ds.split(UNDERLINE)[0];
? ? ? if (groupDataSources.containsKey(group)) {
? ? ? ? groupDataSources.get(group).addDatasource(dataSource);
? ? ? } else {
? ? ? ? try {
? ? ? ? ? DynamicGroupDataSource groupDatasource = new DynamicGroupDataSource(group,
? ? ? ? ? ? ? strategy.newInstance());
? ? ? ? ? groupDatasource.addDatasource(dataSource);
? ? ? ? ? groupDataSources.put(group, groupDatasource);
? ? ? ? } catch (Exception e) {
? ? ? ? ? log.error("dynamic-datasource - add the datasource named [{}] error", ds, e);
? ? ? ? ? dataSourceMap.remove(ds);
? ? ? ? }
? ? ? }
? ? }
? ? log.info("dynamic-datasource - load a datasource named [{}] success", ds);
? }進行數(shù)據(jù)操作時,方法會被com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor攔截,
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
? /**
? ?* The identification of SPEL.
? ?*/
? private static final String DYNAMIC_PREFIX = "#";
? private static final DynamicDataSourceClassResolver RESOLVER = new DynamicDataSourceClassResolver();
? @Setter
? private DsProcessor dsProcessor;
? @Override
? public Object invoke(MethodInvocation invocation) throws Throwable {
? ? try {
? ? ? DynamicDataSourceContextHolder.push(determineDatasource(invocation));
? ? ? return invocation.proceed();
? ? } finally {
? ? ? DynamicDataSourceContextHolder.poll();
? ? }
? }
? private String determineDatasource(MethodInvocation invocation) throws Throwable {
? ? Method method = invocation.getMethod();
? ? DS ds = method.isAnnotationPresent(DS.class)
? ? ? ? ? method.getAnnotation(DS.class)
? ? ? ? : AnnotationUtils.findAnnotation(RESOLVER.targetClass(invocation), DS.class);
? ? String key = ds.value();
? ? return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor
? ? ? ? .determineDatasource(invocation, key) : key;
? }
}攔截器首先從被攔截的方法或者類(一般@DS注解用于Service,也可用于Mapper和Controller)上尋找@DS注解,獲取到@DS注解的值后將其存入com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;DynamicDataSourceContextHolder使用ThreadLocal存儲當(dāng)前線程的數(shù)據(jù)源名。
public final class DynamicDataSourceContextHolder {
? /**
? ?* 為什么要用鏈表存儲(準(zhǔn)確的是棧)
? ?* 為了支持嵌套切換,如ABC三個service都是不同的數(shù)據(jù)源
? ?* 其中A的某個業(yè)務(wù)要調(diào)B的方法,B的方法需要調(diào)用C的方法。一級一級調(diào)用切換,形成了鏈。
? ?* 傳統(tǒng)的只設(shè)置當(dāng)前線程的方式不能滿足此業(yè)務(wù)需求,必須模擬棧,后進先出。
? ?*/
? @SuppressWarnings("unchecked")
? private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
? ? @Override
? ? protected Object initialValue() {
? ? ? return new ArrayDeque();
? ? }
? };
? private DynamicDataSourceContextHolder() {
? }
? /**
? ?* 獲得當(dāng)前線程數(shù)據(jù)源
? ?* @return 數(shù)據(jù)源名稱
? ?*/
? public static String peek() {
? ? return LOOKUP_KEY_HOLDER.get().peek();
? }
? /**
? ?* 設(shè)置當(dāng)前線程數(shù)據(jù)源
? ?* 如非必要不要手動調(diào)用,調(diào)用后確保最終清除
? ?* @param ds 數(shù)據(jù)源名稱
? ?*/
? public static void push(String ds) {
? ? LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds);
? }
? /**
? ?* 清空當(dāng)前線程數(shù)據(jù)源
? ?* 如果當(dāng)前線程是連續(xù)切換數(shù)據(jù)源 只會移除掉當(dāng)前線程的數(shù)據(jù)源名稱
? ?*/
? public static void poll() {
? ? Deque<String> deque = LOOKUP_KEY_HOLDER.get();
? ? deque.poll();
? ? if (deque.isEmpty()) {
? ? ? LOOKUP_KEY_HOLDER.remove();
? ? }
? }
? /**
? ?* 強制清空本地線程
? ?* 防止內(nèi)存泄漏,如手動調(diào)用了push可調(diào)用此方法確保清除
? ?*/
? public static void clear() {
? ? LOOKUP_KEY_HOLDER.remove();
? }
}進行數(shù)據(jù)操作時,會調(diào)用org.springframework.jdbc.datasource.getConnection()方法;getConnection()方法最終調(diào)用了com.baomidou.dynamic.datasource.AbstractRoutingDataSource的getConnection()方法;
@Override
public Connection getConnection() throws SQLException {
return determineDataSource().getConnection();
}
determineDataSource()由子類com.baomidou.dynamic.datasource.DynamicRoutingDataSource實現(xiàn),可以看到DynamicRoutingDataSource從DynamicDataSourceContextHolder獲取數(shù)據(jù)源名稱,這個在之前攔截器處理存進ThreadLocal中,如果有數(shù)據(jù)源名稱則從dataSourceMap中獲取,沒有則獲取默認的primary數(shù)據(jù)源。
public DataSource determineDataSource() {
? ? return getDataSource(DynamicDataSourceContextHolder.peek());
}
public DataSource getDataSource(String ds) {
? ? if (StringUtils.isEmpty(ds)) {
? ? ? ? return determinePrimaryDataSource();
? ? } else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) {
? ? ? ? log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
? ? ? ? return groupDataSources.get(ds).determineDataSource();
? ? } else if (dataSourceMap.containsKey(ds)) {
? ? ? ? log.debug("dynamic-datasource switch to the datasource named [{}]", ds);
? ? ? ? return dataSourceMap.get(ds);
? ? }
? ? if (strict) {
? ? ? ? throw new RuntimeException("dynamic-datasource could not find a datasource named" + ds);
? ? }
? ? return determinePrimaryDataSource();
}
private DataSource determinePrimaryDataSource() {
? ? log.debug("dynamic-datasource switch to the primary datasource");
? ? return groupDataSources.containsKey(primary) ? groupDataSources.get(primary)
? ? ? ? .determineDataSource() : dataSourceMap.get(primary);
}此時的數(shù)據(jù)源已經(jīng)切換成了我們需要的數(shù)據(jù)源。
數(shù)據(jù)操作完成后,方法返回第二步中的攔截器,執(zhí)行DynamicDataSourceContextHolder.poll();清除掉此次的數(shù)據(jù)源,避免影響后續(xù)數(shù)據(jù)操作。
附上動態(tài)數(shù)據(jù)源相關(guān)配置
spring:
application:
name:
datasource:
dynamic:
primary: dataSource1
datasource:
dataSource1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://localhost:1433;database=dataSource1
username:
password:
dataSource2:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://localhost:1433;instanceName=sqlserver2017;DatabaseName=dataSource2
username:
password:
pom.xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>2.5.6</version>
</dependency>
相應(yīng)類
@Service
//@DS("dataSource2") 放在類上就是類下所有方法都使用這個數(shù)據(jù)源。
public class XXXServiceImpl extends BaseServiceImpl<XXXMapper, XXXBean> implements XXXService {
? ? @DS("dataSource1")
? ? public void selectDataFromSource1() {
? ? ? ?// do somethinng;
? ? }
? ??
? ? @DS("dataSource2")
? ? public void selectDataFromSource1() {
? ? ? ?// do somethinng;
? ? }
}**注意:**不可在事務(wù)中切換數(shù)據(jù)庫,保證事務(wù)需要方法使用同一連接,使用@DS(dataSource1)方法調(diào)用@DS(dataSource2)無法切換連接,會導(dǎo)致方法報錯。
到此這篇關(guān)于mybatis-plus @DS實現(xiàn)動態(tài)切換數(shù)據(jù)源原理的文章就介紹到這了,更多相關(guān)mybatis-plus @DS動態(tài)切換數(shù)據(jù)源內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Security實現(xiàn)接口放通的方法詳解
在用Spring?Security項目開發(fā)中,有時候需要放通某一個接口時,我們需要在配置中把接口地址配置上,這樣做有時候顯得麻煩。本文將通過一個注解的方式快速實現(xiàn)接口放通,感興趣的可以了解一下2022-05-05
java8新特性之stream的collect實戰(zhàn)教程
這篇文章主要介紹了java8新特性之stream的collect實戰(zhàn)教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
SpringBoot整合Web開發(fā)之文件上傳與@ControllerAdvice
@ControllerAdvice注解是Spring3.2中新增的注解,學(xué)名是Controller增強器,作用是給Controller控制器添加統(tǒng)一的操作或處理。對于@ControllerAdvice,我們比較熟知的用法是結(jié)合@ExceptionHandler用于全局異常的處理,但其作用不止于此2022-08-08
詳解Java多線程編程中互斥鎖ReentrantLock類的用法
Java多線程并發(fā)的程序中使用互斥鎖有synchronized和ReentrantLock兩種方式,這里我們來詳解Java多線程編程中互斥鎖ReentrantLock類的用法:2016-07-07
idea中解決maven包沖突的問題(maven helper)
這篇文章主要介紹了idea中解決maven包沖突的問題(maven helper),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
java如何實現(xiàn)自動生成數(shù)據(jù)庫設(shè)計文檔
以前我們還需要手寫數(shù)據(jù)庫設(shè)計文檔、現(xiàn)在可以通過引入screw核心包來實現(xiàn)Java?數(shù)據(jù)庫文檔一鍵生成。本文將具體介紹一下如何通過java自動生成數(shù)據(jù)庫設(shè)計文檔,需要的朋友可以參考下2021-11-11

