詳細(xì)聊聊SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的方法
其實(shí)這個(gè)表示有點(diǎn)不太對(duì),應(yīng)該是 Druid 動(dòng)態(tài)切換數(shù)據(jù)源的方法,只是應(yīng)用在了 springboot 框架中,準(zhǔn)備代碼準(zhǔn)備了半天,之前在一次數(shù)據(jù)庫(kù)遷移中使用了,發(fā)現(xiàn) Druid 還是很強(qiáng)大的,用來(lái)做動(dòng)態(tài)數(shù)據(jù)源切換很方便。
首先這里的場(chǎng)景跟我原來(lái)用的有點(diǎn)點(diǎn)區(qū)別,在項(xiàng)目中使用的是通過(guò)配置中心控制數(shù)據(jù)源切換,統(tǒng)一切換,而這里的例子多加了個(gè)可以根據(jù)接口注解配置
第一部分是最核心的,如何基于 Spring JDBC 和 Druid 來(lái)實(shí)現(xiàn)數(shù)據(jù)源切換,是繼承了org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 這個(gè)類,他的determineCurrentLookupKey方法會(huì)被調(diào)用來(lái)獲得用來(lái)決定選擇那個(gè)數(shù)據(jù)源的對(duì)象,也就是 lookupKey,也可以通過(guò)這個(gè)類看到就是通過(guò)這個(gè) lookupKey 來(lái)路由找到數(shù)據(jù)源。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
if (DatabaseContextHolder.getDatabaseType() != null) {
return DatabaseContextHolder.getDatabaseType().getName();
}
return DatabaseType.MASTER1.getName();
}
}
而如何使用這個(gè) lookupKey 呢,就涉及到我們的 DataSource 配置了,原來(lái)就是我們可以直接通過(guò)spring 的 jdbc 配置數(shù)據(jù)源,像這樣

現(xiàn)在我們要使用 Druid 作為數(shù)據(jù)源了,然后配置 DynamicDataSource的參數(shù),通過(guò) key 來(lái)選擇對(duì)應(yīng)的 DataSource,也就是下面配的 master1 和 master2
<bean id="master1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close"
p:driverClassName="com.mysql.cj.jdbc.Driver"
p:url="${master1.demo.datasource.url}"
p:username="${master1.demo.datasource.username}"
p:password="${master1.demo.datasource.password}"
p:initialSize="5"
p:minIdle="1"
p:maxActive="10"
p:maxWait="60000"
p:timeBetweenEvictionRunsMillis="60000"
p:minEvictableIdleTimeMillis="300000"
p:validationQuery="SELECT 'x'"
p:testWhileIdle="true"
p:testOnBorrow="false"
p:testOnReturn="false"
p:poolPreparedStatements="false"
p:maxPoolPreparedStatementPerConnectionSize="20"
p:connectionProperties="config.decrypt=true"
p:filters="stat,config"/>
<bean id="master2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close"
p:driverClassName="com.mysql.cj.jdbc.Driver"
p:url="${master2.demo.datasource.url}"
p:username="${master2.demo.datasource.username}"
p:password="${master2.demo.datasource.password}"
p:initialSize="5"
p:minIdle="1"
p:maxActive="10"
p:maxWait="60000"
p:timeBetweenEvictionRunsMillis="60000"
p:minEvictableIdleTimeMillis="300000"
p:validationQuery="SELECT 'x'"
p:testWhileIdle="true"
p:testOnBorrow="false"
p:testOnReturn="false"
p:poolPreparedStatements="false"
p:maxPoolPreparedStatementPerConnectionSize="20"
p:connectionProperties="config.decrypt=true"
p:filters="stat,config"/>
<bean id="dataSource" class="com.nicksxs.springdemo.config.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- master -->
<entry key="master1" value-ref="master1"/>
<!-- slave -->
<entry key="master2" value-ref="master2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="master1"/>
</bean>
現(xiàn)在就要回到頭上,介紹下這個(gè)DatabaseContextHolder,這里使用了 ThreadLocal 存放這個(gè) DatabaseType,為啥要用這個(gè)是因?yàn)榍懊嬲f(shuō)的我們想要讓接口層面去配置不同的數(shù)據(jù)源,要把持相互隔離不受影響,就使用了 ThreadLocal,關(guān)于它也可以看我前面寫(xiě)的一篇文章聊聊傳說(shuō)中的 ThreadLocal,而 DatabaseType 就是個(gè)簡(jiǎn)單的枚舉
public class DatabaseContextHolder {
public static final ThreadLocal<DatabaseType> databaseTypeThreadLocal = new ThreadLocal<>();
public static DatabaseType getDatabaseType() {
return databaseTypeThreadLocal.get();
}
public static void putDatabaseType(DatabaseType databaseType) {
databaseTypeThreadLocal.set(databaseType);
}
public static void clearDatabaseType() {
databaseTypeThreadLocal.remove();
}
}
public enum DatabaseType {
MASTER1("master1", "1"),
MASTER2("master2", "2");
private final String name;
private final String value;
DatabaseType(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
public static DatabaseType getDatabaseType(String name) {
if (MASTER2.name.equals(name)) {
return MASTER2;
}
return MASTER1;
}
}
這邊可以看到就是通過(guò)動(dòng)態(tài)地通過(guò)putDatabaseType設(shè)置lookupKey來(lái)進(jìn)行數(shù)據(jù)源切換,要通過(guò)接口注解配置來(lái)進(jìn)行設(shè)置的話,我們就需要一個(gè)注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
這個(gè)注解可以配置在我的接口方法上,比如這樣
public interface StudentService {
@DataSource("master1")
public Student queryOne();
@DataSource("master2")
public Student queryAnother();
}
通過(guò)切面來(lái)進(jìn)行數(shù)據(jù)源的設(shè)置
@Aspect
@Component
@Order(-1)
public class DataSourceAspect {
@Pointcut("execution(* com.nicksxs.springdemo.service..*.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void before(JoinPoint point)
{
Object target = point.getTarget();
System.out.println(target.toString());
String method = point.getSignature().getName();
System.out.println(method);
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz[0].getMethod(method, parameterTypes);
System.out.println("method"+ m.getName());
if (m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
System.out.println("dataSource:"+data.value());
DatabaseContextHolder.putDatabaseType(DatabaseType.getDatabaseType(data.value()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
@After("pointCut()")
public void after() {
DatabaseContextHolder.clearDatabaseType();
}
}
通過(guò)接口判斷是否帶有注解跟是注解的值,DatabaseType 的配置不太好,不過(guò)先忽略了,然后在切點(diǎn)后進(jìn)行清理
這是我 master1 的數(shù)據(jù),

master2 的數(shù)據(jù)

然后跑一下簡(jiǎn)單的 demo,
@Override
public void run(String...args) {
LOGGER.info("run here");
System.out.println(studentService.queryOne());
System.out.println(studentService.queryAnother());
}
看一下運(yùn)行結(jié)果

其實(shí)這個(gè)方法應(yīng)用場(chǎng)景不止可以用來(lái)遷移數(shù)據(jù)庫(kù),還能實(shí)現(xiàn)精細(xì)化的讀寫(xiě)數(shù)據(jù)源分離之類的,算是做個(gè)簡(jiǎn)單記錄和分享。
總結(jié)
到此這篇關(guān)于SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的文章就介紹到這了,更多相關(guān)SpringBoot動(dòng)態(tài)切換數(shù)據(jù)源內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot集成@DS注解實(shí)現(xiàn)數(shù)據(jù)源切換的方法示例
- springboot+dynamicDataSource動(dòng)態(tài)添加切換數(shù)據(jù)源方式
- SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過(guò)程
- Springboot實(shí)現(xiàn)根據(jù)用戶ID切換動(dòng)態(tài)數(shù)據(jù)源
- Springboot動(dòng)態(tài)切換數(shù)據(jù)源的具體實(shí)現(xiàn)與原理分析
- Spring配置多數(shù)據(jù)源切換
- SpringBoot AOP方式實(shí)現(xiàn)多數(shù)據(jù)源切換的方法
- Spring配置多個(gè)數(shù)據(jù)源并實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換功能
相關(guān)文章
SpringBoot實(shí)現(xiàn)Word轉(zhuǎn)PDF和TXT的實(shí)踐分享
研發(fā)工作中難免會(huì)遇到一些奇奇怪怪的需求,就比如最近,客戶提了個(gè)新需求:上傳一個(gè)WORD文檔,要求通過(guò)系統(tǒng)把該文檔轉(zhuǎn)換成PDF和TXT,所以本文給大家分享了SpringBoot實(shí)現(xiàn)Word轉(zhuǎn)PDF和TXT的實(shí)踐,感興趣的朋友可以參考下2024-08-08
SpringBoot如何通過(guò)Map實(shí)現(xiàn)策略模式
策略模式是一種行為設(shè)計(jì)模式,它允許在運(yùn)行時(shí)選擇算法的行為,在Spring框架中,我們可以利用@Resource注解和Map集合來(lái)優(yōu)雅地實(shí)現(xiàn)策略模式,這篇文章主要介紹了SpringBoot如何通過(guò)Map實(shí)現(xiàn)策略模式,需要的朋友可以參考下2025-03-03
Java必須學(xué)會(huì)的類的繼承與多態(tài)
這篇文章主要介紹了Java類的繼承與多態(tài)的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java,感興趣的朋友可以了解下2020-08-08
Jeecg-Boot異常處理'jeecg-boot.QRTZ_LOCKS'?doesn'
這篇文章主要介紹了Jeecg-Boot異常處理'jeecg-boot.QRTZ_LOCKS'?doesn't?exist問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
百度翻譯API使用詳細(xì)教程(前端vue+后端springboot)
這篇文章主要給大家介紹了關(guān)于百度翻譯API使用的相關(guān)資料,百度翻譯API是百度面向開(kāi)發(fā)者推出的免費(fèi)翻譯服務(wù)開(kāi)放接口,任何第三方應(yīng)用或網(wǎng)站都可以通過(guò)使用百度翻譯API為用戶提供實(shí)時(shí)優(yōu)質(zhì)的多語(yǔ)言翻譯服務(wù),需要的朋友可以參考下2024-02-02
Java遠(yuǎn)程debug調(diào)試的操作步驟
日常項(xiàng)目中,通常我們的代碼都是部署到遠(yuǎn)程的服務(wù)器,有時(shí)線上出現(xiàn)了問(wèn)題,經(jīng)常需要我們?cè)黾右恍┤罩緛?lái)排查問(wèn)題,但是如果是測(cè)試環(huán)境的場(chǎng)景下或者我們能進(jìn)行遠(yuǎn)程調(diào)試的話,我們就可以使用遠(yuǎn)程的Debug方式進(jìn)行遠(yuǎn)程調(diào)試,下面給大家介紹一下Java遠(yuǎn)程debug調(diào)試的操作步驟2025-04-04
SpringMVC深入講解文件的上傳下載實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了springMVC實(shí)現(xiàn)文件上傳和下載的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06

