根據租戶id切換數據源

花了半天時間,使用spring-boot實現動態數據源,切換自如

??在一個項目中使用多個數據源的情況很多,所以動態切換數據源是項目中標配的功能,當然網上有相關的依賴可以使用,比如動態數據源,其依賴為,

<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.1</version>
</dependency>

??今天,不使用現成的API,手動實現一個動態數據源。

一、環境及依賴

springboot、mybatis-plus的基礎上實現動態數據源切換,

springboot:2.3.3.RELEASE

mybatis-plus-boot-starter:3.5.0

mysql驅動:8.0.32

除了這些依賴外沒有其他的,目標是動態切換數據源。

二、實現思路

??先來看下,單數據源的情況。

??在使用springboot和mybatis-plus時,我們沒有配置數據源(DataSource),只配置了數據庫相關的信息,便可以連接數據庫進行數據庫的操作,這是為什么吶。其實是基于spring-boot的自動配置,也就是autoConfiguration,在自動配置下有DataSourceAutoConfiguration類,該類會生成一個數據源并注入到spring的容器中,這樣就可以使用該數據源提供的連接,訪問數據庫了。

??感興趣的小伙伴可以了解下這個類的具體實現邏輯。

??要實現多數據源,并且可以自動切換。那么肯定就不能再使用DataSourceAutoConfigurtation了,因為它只能產生一個數據源,多個數據源要怎么辦,spring提供了AbstractRoutingDataSource類,該類是一個抽象類,僅有一個抽象方法需要實現

Determine the current lookup key. This will typically be implemented to check a thread-bound transaction context.
Allows for arbitrary keys. The returned key needs to match the stored lookup key type, 
as resolved by the resolveSpecifiedLookupKey method.
@Nullable
protected abstract Object determineCurrentLookupKey();

可以根據該類實現一個動態數據源。好了,現在了解了實現思路,開始實現一個動態數據源,要做以下的準備工作。

1、配置文件;

2、自定義動態數據源;

2.1、配置文件

由于是多數據源,那么在配置文件中肯定是多個配置,不能再是一個數據庫的配置了,這里使用兩個mysql的配置進行演示,

#master 默認數據源
spring:datasource:master:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: 123456
#slave 從數據源slave:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://127.0.0.1:3306/test2?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: 123456

這里使用了一個master一個slave兩個數據源配置,其地址是一致的,但數據庫示例不一樣。 有了數據源的信息下一步要實現自己的數據源,

2.2、自定義動態數據源

??前邊說,spring提供了AbstractRoutingDataSource類可以實現動態數據源,看下實現。

DynamicDatasource.java

package com.wcj.my.config.dynamic.source;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 動態數據源* @date 2023/6/8 19:18*/
public class DynamicDatasource extends AbstractRoutingDataSource {/*** Determine the current lookup key. This will typically be* implemented to check a thread-bound transaction context.* <p>Allows for arbitrary keys. The returned key needs* to match the stored lookup key type, as resolved by the* {@link #resolveSpecifiedLookupKey} method.*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDatasourceHolder.getDataSource();}
}

這里的determineCurrentLookupKey方法,需要返回一個數據源,也就是說返回一個數據源的映射,這里返回一個DynamicDatasourceHolder.getDataSource()方法的返回值,DynamicDatasourceHolder是一個保存多個數據源的地方,

DynamicDatasourceHolder.java

package com.wcj.my.config.dynamic.source;import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;/*** @date 2023/6/8 19:42*/
public class DynamicDatasourceHolder {//保存數據源的映射private static Queue<String> queue = new ArrayBlockingQueue<String>(1);public static String getDataSource() {return queue.peek();}public static void setDataSource(String dataSourceKey) {queue.add(dataSourceKey);}public static void removeDataSource(String dataSourceKey) {queue.remove(dataSourceKey);}
}

該類很簡單,使用一個隊列保存數據源的映射,提供獲取/設置數據源的方法。

這里使用ThreadLocal類更合適,這樣可以實現線程的隔離,一個請求會有一個線程來處理,保證每隔線程使用的數據源是一樣的。

到現在為止依舊沒有出現如何創建多數據源,下面就來了,不著急。

DynamicDatasourceConfig.java

package com.wcj.my.config.dynamic.source;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @date 2023/6/8 19:51*/
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDatasourceConfig {@Bean("master")@ConfigurationProperties(prefix = "spring.datasource.master")public DataSource masterDatasource(){return DataSourceBuilder.create().build();}@Bean("slave")@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSource slaveDatasource(){return DataSourceBuilder.create().build();}@Bean@Primarypublic DataSource dataSource(){Map<Object, Object> dataSourceMap = new HashMap<>(2);dataSourceMap.put("master", masterDatasource());dataSourceMap.put("slave", slaveDatasource());DynamicDatasource dynamicDatasource=new DynamicDatasource();dynamicDatasource.setTargetDataSources(dataSourceMap);dynamicDatasource.setDefaultTargetDataSource(masterDatasource());return dynamicDatasource;}
}

首先,在該類上有個一個@Configuration注解,標明這是一個配置類;

其次,有一個@EnableAutonConfiguration注解,該注解中有個數組類型的exclude屬性,排除不需要自動配置的類,這里排除的是當然就是DataSourceAutoConfiguration類了;因為下面會自動生成數據源,不需要自動配置了;

然后,在類中是標有@Bean的方法,這些方法便是生成數據源類,且映射為”master“、”slave“,可以有多個。使用的是DataSourceBuilder類幫助生成;

最后,生成一個DynamicDatasource,且標有@Primary注解,這里需要設置”master“、”slave“兩個映射代表的數據源;

這樣便向spring容器中注入了三個數據源,分別是”master“、”slave“代表的數據源,他們是需要實際使用的數據源。還有一個是DynamicDatasource,提供數據源的設置。這三個都是DataSource的子類。

三、使用多數據源

??上面已經完成了多數據源的配置,下面看怎么使用吧,還記得DynamicDatasourceHolder類中有set/get方法嗎,就是使用這個類提供的方法,

UserSerivce.java

package com.wcj.my.service;import com.wcj.my.config.dynamic.source.DynamicDatasourceHolder;
import com.wcj.my.dto.UserDto;
import com.wcj.my.entity.User;
import com.wcj.my.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @date 2023/6/8 15:19*/
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;/**默認使用master數據源*/public boolean saveUser(UserDto userDto) {User user = new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num = userMapper.insert(user);if (num > 0) {return true;}return false;}/***使用slave數據源*/public boolean saveUserSlave(UserDto userDto) {DynamicDatasourceHolder.setDataSource("slave");User user = new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num = userMapper.insert(user);DynamicDatasourceHolder.removeDataSource("slave");if (num > 0) {return true;}return false;}
}

??上面的service層方法在調用dao層方法的時候,使用DynamicDatasourceHolder.setDataSource()方法設置了需要使用的數據源, 通過這樣的方式便可以實現動態數據源了。

??不知道,小伙伴們有沒有感覺到,這樣每次在調用方法的時候都需要設置數據源是不是很麻煩,有沒有一種更方面的方式,比如說注解。

四、動態數據源注解@DDS

???現在來實現一個動態數據源的注解來代替上面的每次都調用DynamicDatasourceHolder.setDataSource()方法來設置數據源。

??先看下,@DDS注解的定義

DDS.java

package com.wcj.my.config.dynamic.source.aspect;import org.springframework.stereotype.Component;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/**動態數據源的注解* 用在類和方法上,方法上的優先級大于類上的* 默認值是master* @date 2023/6/9 16:19*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DDS {String value() default "master";
}

注解@DDS使用在類和方法上,切方法上的優先級大于類上的。有一個value的屬性,指明使用的數據源,默認是”master“。

實現一個切面,來切@DDS注解

?DynamicDatasourceAspect.java

package com.wcj.my.config.dynamic.source.aspect;import com.wcj.my.config.dynamic.source.DynamicDatasourceHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.util.Objects;/*** 動態數據源切面* @date 2023/6/9 16:23*/
@Aspect
@Component
public class DynamicDatasourceAspect {/*** 切點,切的是帶有@DDS的注解*/@Pointcut("@annotation(com.wcj.my.config.dynamic.source.aspect.DDS)")public void dynamicDatasourcePointcut(){}/*** 環繞通知* @param joinPoint* @return* @throws Throwable*/@Around("dynamicDatasourcePointcut()")public Object around(ProceedingJoinPoint joinPoint)throws Throwable{String datasourceKey="master";//類上的注解Class<?> targetClass=joinPoint.getTarget().getClass();DDS annotation=targetClass.getAnnotation(DDS.class);//方法上的注解MethodSignature methodSignature=(MethodSignature)joinPoint.getSignature();DDS annotationMethod=methodSignature.getMethod().getAnnotation(DDS.class);if(Objects.nonNull(annotationMethod)){datasourceKey=annotationMethod.value();}else{datasourceKey=annotation.value();}//設置數據源DynamicDatasourceHolder.setDataSource(datasourceKey);try{return joinPoint.proceed();}finally {DynamicDatasourceHolder.removeDataSource(datasourceKey);}}
}

?這樣一個動態數據源的注解便可以了,看下怎么使用,

UserServiceByAnnotation.java

package com.wcj.my.service;import com.wcj.my.config.dynamic.source.DynamicDatasourceHolder;
import com.wcj.my.config.dynamic.source.aspect.DDS;
import com.wcj.my.dto.UserDto;
import com.wcj.my.entity.User;
import com.wcj.my.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @date 2023/6/8 15:19*/
@Service
public class UserServiceByAnnotation {@Autowiredprivate UserMapper userMapper;@DDS("master")public boolean saveUser(UserDto userDto){User user=new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num=userMapper.insert(user);if(num>0){return true;}return false;}@DDS("slave")public boolean saveUserSlave(UserDto userDto){User user=new User();user.setUName(userDto.getName());user.setUCode(userDto.getCode());user.setUAge(userDto.getAge());user.setUAddress(userDto.getAddress());int num=userMapper.insert(user);if(num>0){return true;}return false;}
}

使用起來很簡單,在需要切換數據源的方法或類上使用@DDS注解即可,使用value來改變數據源就好了。

五、動態數據源的原理

??很多小伙伴可能和我有一樣的疑惑,使用DynamicDatasourceHolder.setDataSource或@DDS就可以設置數據源了,是怎么實現的,下面分析下,我們指定dao層的Mapper其實是一個代理對象,其會使用mybatis中的sqlSessionTempalte進行數據庫的操作,在sqlSessionTemplate中會使用DefaultSqlSession對象,最終會使用DataSource,而使用了動態數據源的對象中會注入一個DynamicDataSource,在進行數據庫操作時最終會獲得一個數據庫連接,這里便會使用DynamicDataSource獲得一個連接,由于它繼承了AbstractRoutingDataSource類,看下其getConnection方法,

@Overridepublic Connection getConnection() throws SQLException {return determineTargetDataSource().getConnection();}

看下determineTargetDataSource()方法,

protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");、//自己實現的,在調用方法時進行了設置,實現動態數據源的目的    Object lookupKey = determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource;}

看上面的注釋,determineCurrentLookupkey()方法便是在DynamicDatasource類中進行了實現,從而實現了動態設置數據源的目的。

六、總結?

本文動手實現了一個動態數據源,并切提供了注解的方式,主要有以下幾點

1、繼承AbstractRoutingDataSource類的determineCurrentLookupkey()方法,動態設置數據源;

2、取消DataSourceAutoConfiguration的自動配置,手動向spring容器中注入多個數據源;

3、基于@DDS注解動態設置數據源;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/22926.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/22926.shtml
英文地址,請注明出處:http://en.pswp.cn/web/22926.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

銀河麒麟解壓命令

銀河麒麟&#xff08;Kylin&#xff09;操作系統是基于Linux的操作系統分支之一&#xff0c;其使用的解壓命令與Linux系統中的命令基本相同。 在銀河麒麟系統中&#xff0c;常用的解壓命令有以下幾種&#xff1a; 對于.tar文件&#xff1a; tar -xvf file.tar對于.tar.gz或.…

探索營銷系統業務架構的設計與應用

隨著市場競爭的日益激烈和消費者需求的不斷變化&#xff0c;營銷系統作為企業營銷管理的重要組成部分&#xff0c;扮演著至關重要的角色。本文將深入探討營銷系統業務架構的設計與應用&#xff0c;從客戶關系管理、營銷活動管理、數據分析和智能化服務等方面進行全面解析&#…

Innodb Buffer Pool緩存機制(四)預讀與Mysql改進的LRU策略

一、什么是預讀 InnoDB提供了預讀(read ahead)。所謂預讀&#xff0c;就是InnoDB認為執行當前的請求可能之后會讀取某些頁面&#xff0c;就預先把它們加載到Buffer Pool中。根據觸發方式的不同&#xff0c;預讀又可以細分為下邊兩種&#xff1a; 1.1 線性預讀 InnoDB提供了一…

掘金AI商戰寶典-高階班:如何用AI制作視頻(11節視頻課)

課程下載&#xff1a;掘金AI商戰寶典-高階班&#xff1a;如何用AI制作視頻(11節視頻課)-課程網盤鏈接提取碼下載.txt資源-CSDN文庫 更多資源下載&#xff1a;關注我。 課程目錄&#xff1a; 1-第一講用AI自動做視頻(上)_1.mp4 2-第二講用AI自動做視頻(中)_1.mp4 3-第四講A…

U9C的數據查詢視圖Sql

U9C的數據查詢視圖Sql if object_id(TEMPDB..#priceTable) is not null begin drop table #priceTable endcreate table #priceTable (polineCreatedOn date,price varchar(max),itemid varchar(max),purchaseOrderdocno varchar(max),)insert into #priceTable select max(…

阿里云郵件推送服務配置教程:怎么做批發?

阿里云郵件推送的API配置步驟&#xff1f;配置教程有哪些步驟&#xff1f; 阿里云郵件推送服務憑借其高并發、穩定性強和安全性高等特點&#xff0c;成為眾多企業的首選。Aok將詳細介紹如何使用阿里云郵件推送服務進行批發配置&#xff0c;并簡要提及AokSend的優勢。 阿里云郵…

UE4_環境_材質函數

學習筆記&#xff0c;不喜勿噴&#xff0c;歡迎指正&#xff0c;侵權立刪&#xff01; 1、建立材質函數Distance_Fun&#xff0c;勾選公開到庫。 2、添加函數輸入節點FunctionInput&#xff0c; 這個輸入我們想作為混合材質屬性BlendMaterialAttributes的alpha輸入節點&#x…

022、鍵管理_遍歷鍵

Redis提供了兩個命令遍歷所有的鍵,分別是keys和scan 1.全量遍歷鍵 keys patternkeys命令是支持pattern匹配的 127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> mset hello world redis best jedis best hill high OK如果要獲取所有的鍵,可以使用keys pattern命…

手擼 串口交互命令行 及 AT應用層協議解析框架

在嵌入式系統開發中&#xff0c;命令行接口&#xff08;CLI&#xff09;和AT命令解析是常見的需求。CLI提供了方便的調試接口&#xff0c;而AT命令則常用于模塊間的通信控制。本文將介紹如何手動實現一個串口交互的命令行及AT應用層協議解析框架&#xff0c;適用于FreeRTOS系統…

06Docker-Compose和微服務部署

Docker-Compose 概述 Docker Compose通過一個單獨的docker-compose.yml模板文件來定義一組相關聯的應用容器&#xff0c;幫助我們實現多個相互關聯的Docker容器的快速部署 一般一個docker-compose.yml對應完整的項目,項目中的服務和中間件對應不同的容器 Compose文件實質就…

鋰電池壽命預測 | Matlab基于SSA-SVR麻雀優化支持向量回歸的鋰離子電池剩余壽命預測

目錄 預測效果基本介紹程序設計參考資料 預測效果 基本介紹 【鋰電池剩余壽命RUL預測案例】 鋰電池壽命預測 | Matlab基于SSA-SVR麻雀優化支持向量回歸的鋰離子電池剩余壽命預測&#xff08;完整源碼和數據&#xff09; 1、提取NASA數據集的電池容量&#xff0c;以歷史容量作…

【C++課程學習】:類和對象(上)(類的基礎詳細講解)

&#x1f381;個人主頁&#xff1a;我們的五年 &#x1f50d;系列專欄&#xff1a;C課程學習 &#x1f389;歡迎大家點贊&#x1f44d;評論&#x1f4dd;收藏?文章 目錄 &#x1f35f;1.1類的引出&#xff1a; &#x1f35f;1.2類的結構&#xff1a; &#x1f35f;1.3類的…

LeetCode-82. 刪除排序鏈表中的重復元素 II【鏈表 雙指針】

LeetCode-82. 刪除排序鏈表中的重復元素 II【鏈表 雙指針】 題目描述&#xff1a;解題思路一&#xff1a;用一個cur即可實現去重cur.next cur.next.next背誦版&#xff1a;解題思路三&#xff1a;0 題目描述&#xff1a; 給定一個已排序的鏈表的頭 head &#xff0c; 刪除原始…

【java前端課堂】02_類和方法的定義區別

目錄 簡介&#xff1a; 類 方法 類和方法之間的主要區別如下&#xff1a; 定義與結構&#xff1a; 實例化&#xff1a; 作用范圍&#xff1a; 生命周期&#xff1a; 下面是一個簡單的Java類和方法示例&#xff1a; 簡介&#xff1a; 類 在Java&#xff08;以及許多其他面向…

十大排序-冒泡排序

算法原理如下&#xff1a; 給出一組數據&#xff1b;比較相鄰的元素。如果第一個比第二個大&#xff0c;互換兩個值。對每一組相鄰元素同樣方式比較&#xff0c;從開始的第一組到結束的最后一組。最后的元素會是最大數。除了排列好的最大數&#xff0c;針對所有元素重復以上步…

臺式機ubuntu22.04安裝nvidia驅動

總結一個極簡易的安裝方法 正常安裝ubuntu 22.04正常更新軟件 sudo apt update sudo apt upgrade -y參考ubuntu官方網站的說明https://ubuntu.com/server/docs/nvidia-drivers-installation#/ # 首先檢查系統支持驅動的版本號 sudo ubuntu-drivers list我顯示的內容如下&…

前端應用開發實驗:組件應用

目錄 實驗目的相關知識點實驗內容及要求代碼實現效果 實驗目的 &#xff08;1&#xff09;掌握組件的創建方法&#xff08;全局組件、局部組件&#xff09;&#xff1b; &#xff08;2&#xff09;重點學會組件之間的數據傳遞&#xff08;prop傳值、自定義事件&#xff09;&am…

SAP 用事務碼SQVI 制作簡單的ALV報表

我們在項目實施和運維的過程中經常會接到用戶的很多需求&#xff0c;有很大的一部分需求可能都是一些報表的需求&#xff0c;有些報表的需求需要開發人員使用ABAP編寫&#xff0c;但是有些報表僅僅只是兩個或者多個報表的表關聯就可以實現。這個時候我們就可以用SQVI這個事物代…

揭秘!寵物空氣凈化器對抗貓毛過敏,效果真的超乎想象?

貓毛過敏困擾著不少愛貓人士。盡管網絡上充斥著各種緩解策略&#xff0c;但究竟哪種方法效果最佳&#xff1f;作為一位經驗豐富的寵物主人&#xff0c;我搜集了大量信息&#xff0c;對比了幾種主流的貓毛過敏應對策略&#xff0c;比如藥物治療、日常清潔和寵物空氣凈化器的使用…

阿里云私有CA使用教程

點擊免費生成 根CA詳情 啟用根CA -----BEGIN CERTIFICATE----- MIIDpzCCAogAwIBAgISBZ2QPcfDqvfI8fqoPkOq6AoMA0GCSqGSIb3DQEBCwUA MFwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdiZWlqaW5nMRAwDgYDVQQHDAdiZWlq aW5nMQ0wCwYDVQQKDARDU0REMQ0wCwYDVQQLDARDU0REMQswCQYDVQQDDAJDTjA…