mybatis-plus @DS實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源原理
1、mybatis-plus @DS實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源原理
首先mybatis-plus使用com.baomidou.dynamic.datasource.AbstractRoutingDataSource繼承 AbstractDataSource接管數(shù)據(jù)源;具體實(shí)現(xiàn)類為com.baomidou.dynamic.datasource.DynamicRoutingDataSource。項(xiàng)目初始化調(diào)用public synchronized void addDataSource(String ds, DataSource dataSource)加載數(shù)據(jù)源,數(shù)據(jù)源存進(jìn)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); ? }
進(jìn)行數(shù)據(jù)操作時(shí),方法會(huì)被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存儲(chǔ)當(dāng)前線程的數(shù)據(jù)源名。
public final class DynamicDataSourceContextHolder { ? /** ? ?* 為什么要用鏈表存儲(chǔ)(準(zhǔn)確的是棧) ? ?* 為了支持嵌套切換,如ABC三個(gè)service都是不同的數(shù)據(jù)源 ? ?* 其中A的某個(gè)業(yè)務(wù)要調(diào)B的方法,B的方法需要調(diào)用C的方法。一級(jí)一級(jí)調(diào)用切換,形成了鏈。 ? ?* 傳統(tǒng)的只設(shè)置當(dāng)前線程的方式不能滿足此業(yè)務(wù)需求,必須模擬棧,后進(jìn)先出。 ? ?*/ ? @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ù)源 ? ?* 如非必要不要手動(dòng)調(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ù)源 只會(huì)移除掉當(dāng)前線程的數(shù)據(jù)源名稱 ? ?*/ ? public static void poll() { ? ? Deque<String> deque = LOOKUP_KEY_HOLDER.get(); ? ? deque.poll(); ? ? if (deque.isEmpty()) { ? ? ? LOOKUP_KEY_HOLDER.remove(); ? ? } ? } ? /** ? ?* 強(qiáng)制清空本地線程 ? ?* 防止內(nèi)存泄漏,如手動(dòng)調(diào)用了push可調(diào)用此方法確保清除 ? ?*/ ? public static void clear() { ? ? LOOKUP_KEY_HOLDER.remove(); ? } }
進(jìn)行數(shù)據(jù)操作時(shí),會(huì)調(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實(shí)現(xiàn),可以看到DynamicRoutingDataSource從DynamicDataSourceContextHolder獲取數(shù)據(jù)源名稱,這個(gè)在之前攔截器處理存進(jìn)ThreadLocal中,如果有數(shù)據(jù)源名稱則從dataSourceMap中獲取,沒有則獲取默認(rèn)的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í)的數(shù)據(jù)源已經(jīng)切換成了我們需要的數(shù)據(jù)源。
數(shù)據(jù)操作完成后,方法返回第二步中的攔截器,執(zhí)行DynamicDataSourceContextHolder.poll();清除掉此次的數(shù)據(jù)源,避免影響后續(xù)數(shù)據(jù)操作。
附上動(dòng)態(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") 放在類上就是類下所有方法都使用這個(gè)數(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)無法切換連接,會(huì)導(dǎo)致方法報(bào)錯(cuò)。
到此這篇關(guān)于mybatis-plus @DS實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源原理的文章就介紹到這了,更多相關(guān)mybatis-plus @DS動(dòng)態(tài)切換數(shù)據(jù)源內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Security實(shí)現(xiàn)接口放通的方法詳解
在用Spring?Security項(xiàng)目開發(fā)中,有時(shí)候需要放通某一個(gè)接口時(shí),我們需要在配置中把接口地址配置上,這樣做有時(shí)候顯得麻煩。本文將通過一個(gè)注解的方式快速實(shí)現(xiàn)接口放通,感興趣的可以了解一下2022-05-05java8新特性之stream的collect實(shí)戰(zhàn)教程
這篇文章主要介紹了java8新特性之stream的collect實(shí)戰(zhàn)教程,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08SpringBoot整合Web開發(fā)之文件上傳與@ControllerAdvice
@ControllerAdvice注解是Spring3.2中新增的注解,學(xué)名是Controller增強(qiáng)器,作用是給Controller控制器添加統(tǒng)一的操作或處理。對于@ControllerAdvice,我們比較熟知的用法是結(jié)合@ExceptionHandler用于全局異常的處理,但其作用不止于此2022-08-08詳解Java多線程編程中互斥鎖ReentrantLock類的用法
Java多線程并發(fā)的程序中使用互斥鎖有synchronized和ReentrantLock兩種方式,這里我們來詳解Java多線程編程中互斥鎖ReentrantLock類的用法:2016-07-07idea中解決maven包沖突的問題(maven helper)
這篇文章主要介紹了idea中解決maven包沖突的問題(maven helper),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12java如何實(shí)現(xiàn)自動(dòng)生成數(shù)據(jù)庫設(shè)計(jì)文檔
以前我們還需要手寫數(shù)據(jù)庫設(shè)計(jì)文檔、現(xiàn)在可以通過引入screw核心包來實(shí)現(xiàn)Java?數(shù)據(jù)庫文檔一鍵生成。本文將具體介紹一下如何通過java自動(dòng)生成數(shù)據(jù)庫設(shè)計(jì)文檔,需要的朋友可以參考下2021-11-11