SpringBoot實(shí)現(xiàn)數(shù)據(jù)源動態(tài)切換的最佳姿勢
在介紹動態(tài)數(shù)據(jù)源之前,我們先一起來看看多數(shù)據(jù)源在 Spring Boot 中的實(shí)現(xiàn)方式。

1.1數(shù)據(jù)庫準(zhǔn)備
創(chuàng)建兩個(gè)庫,分別是db_test_1和db_test_2。db_test_1數(shù)據(jù)庫中創(chuàng)建一張用戶表,腳本如下:

db_test_2數(shù)據(jù)庫中創(chuàng)建另一張賬戶表,腳本如下:

1.2工程環(huán)境準(zhǔn)備
pom.xml中添加相關(guān)的依賴包,示例如下:
<!--spring boot核心-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring boot 測試-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mysql 驅(qū)動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--aspectj 注解代理-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>1.3編寫多數(shù)據(jù)源

創(chuàng)建動態(tài)數(shù)據(jù)源服務(wù)類
首先,創(chuàng)建一個(gè)DynamicDataSource類,并繼承AbstractRoutingDataSource抽象類,同時(shí)重寫determineCurrentLookupKey()方法,代碼示例如下:

創(chuàng)建動態(tài)數(shù)據(jù)源緩存類
創(chuàng)建一個(gè)DataSourceContextHolder類,用于緩存數(shù)據(jù)源,同時(shí)需要確保線程環(huán)境下安全
package com.example.dynamic.datasource.config;
publicclass DataSourceContextHolder {
/**
* 設(shè)置線程獨(dú)立變量,用于存儲數(shù)據(jù)源唯一標(biāo)記
*/
privatestaticfinal ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
/**
* 設(shè)置數(shù)據(jù)源
* @param dataSourceName 數(shù)據(jù)源名稱
*/
public static void set(String dataSourceName){
DATASOURCE_HOLDER.set(dataSourceName);
}
/**
* 獲取當(dāng)前線程的數(shù)據(jù)源
* @return 數(shù)據(jù)源名稱
*/
public static String get(){
return DATASOURCE_HOLDER.get();
}
/**
* 刪除當(dāng)前數(shù)據(jù)源
*/
public static void remove(){
DATASOURCE_HOLDER.remove();
}
}創(chuàng)建動態(tài)數(shù)據(jù)源配置類
接著,創(chuàng)建一個(gè)DataSourceConfig配置類,設(shè)置動態(tài)數(shù)據(jù)源相關(guān)的參數(shù),并注入到 Bean 工廠,代碼示例如下:
package com.example.dynamic.datasource.config;
@Configuration
publicclass DataSourceConfig {
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.db1.druid")
public DataSource db1(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.db2.druid")
public DataSource db2(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource createDynamicDataSource(){
// 配置數(shù)據(jù)源集合,其中key代表數(shù)據(jù)源名稱,DataSourceContextHolder中緩存的就是這個(gè)key
Map<Object,Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("db1",db1());
dataSourceMap.put("db2",db2());
// 注入動態(tài)數(shù)據(jù)源到bean工廠
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 設(shè)置默認(rèn)數(shù)據(jù)源
dynamicDataSource.setDefaultTargetDataSource(db1());
// 設(shè)置動態(tài)數(shù)據(jù)源集
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
}編寫相關(guān)配置變量
根據(jù)上面的配置變量,我們還需要在application.properties文件中添加相關(guān)的數(shù)據(jù)源變量,內(nèi)容如下:

排除自動裝配數(shù)據(jù)源
需要在注解@SpringBootApplication類上排除自動裝配數(shù)據(jù)源配置,內(nèi)容如下:

1.4利用切面代理類設(shè)置數(shù)據(jù)源
在上文中,我們采用的是手動方式來設(shè)置數(shù)據(jù)源,在實(shí)際的業(yè)務(wù)開發(fā)中,我們通常會采用切面代理類來設(shè)置數(shù)據(jù)源,以便簡化代碼復(fù)雜度。
創(chuàng)建數(shù)據(jù)源注解
首先,定義一個(gè)數(shù)據(jù)源注解來實(shí)現(xiàn)數(shù)據(jù)源的切換,同時(shí)配置一個(gè)默認(rèn)的數(shù)據(jù)源名稱,代碼示例如下:
package com.example.dynamic.datasource.config.aop;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DbSource {
/**
* 數(shù)據(jù)源key值
* @return
*/
String value() default "db1";
}編寫數(shù)據(jù)源代理類
接著,基于@DbSource注解,創(chuàng)建一個(gè) AOP 代理類,所有配置該注解的方法都會被前后攔截,代碼示例如下:
package com.example.dynamic.datasource.config.aop;
@Order(1)
@Aspect
@Component
publicclass DbSourceAspect {
privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(DbSourceAspect.class);
@Pointcut("@annotation(com.example.dynamic.datasource.config.aop.DbSource)")
public void dynamicDataSource(){}
@Around("dynamicDataSource()")
public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
// 獲取要切換的數(shù)據(jù)源名稱
MethodSignature methodSignature = (MethodSignature)point.getSignature();
Method method = methodSignature.getMethod();
DbSource dbSource = method.getAnnotation(DbSource.class);
LOGGER.info("select dataSource:" + dbSource.value());
DataSourceContextHolder.set(dbSource.value());
try {
return point.proceed();
} finally {
DataSourceContextHolder.remove();
}
}
}
使用注解切換數(shù)據(jù)源
最后,在需要的方法上配置相關(guān)的數(shù)據(jù)源注解即可。
@Service
public class UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional
@DbSource(value = "db1")
public void add(UserInfo entity){
userInfoMapper.insert(entity);
}
}賬戶服務(wù)類,代碼示例如下:
@Service
public class AccountInfoService {
@Autowired
private AccountInfoMapper accountInfoMapper;
@Transactional
@DbSource(value = "db2")
public void add(AccountInfo entity){
accountInfoMapper.insert(entity);
}
}采用 aop 代理的方式來切換數(shù)據(jù)源,業(yè)務(wù)實(shí)現(xiàn)上會更加的靈活。
在上文中,我們介紹了多數(shù)據(jù)源的配置實(shí)現(xiàn)方式,這種配置方式有一個(gè)不好的地方在于:配置文件都是寫死的。
能不能改成動態(tài)的加載數(shù)據(jù)源呢,下面我們一起來看看相關(guān)的具體實(shí)現(xiàn)方式
2.1數(shù)據(jù)庫準(zhǔn)備
首先,我們需要準(zhǔn)備一張數(shù)據(jù)源配置表。新建一個(gè)test_db數(shù)據(jù)庫,然后在數(shù)據(jù)庫中創(chuàng)建一張數(shù)據(jù)源配置表,腳本如下:
CREATE TABLE`tb_db_info` ( `id`int(11) unsignedNOTNULL AUTO_INCREMENT, `db_name`varchar(50) DEFAULTNULL, `db_url`varchar(200) DEFAULTNULL, `driver_class_name`varchar(100) DEFAULTNULL, `username`varchar(80) DEFAULTNULL, `password`varchar(80) DEFAULTNULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3DEFAULTCHARSET=utf8mb4;
最后,初始化兩條數(shù)據(jù),方便后續(xù)數(shù)據(jù)源的查詢。
2.2修改全局配置文件
我們還是以上文介紹的工程為例,把之前自定義的配置參數(shù)刪除掉,重新基于 Spring Boot 約定的配置方式,添加相關(guān)的數(shù)據(jù)源參數(shù),內(nèi)容如下:
# 配置默認(rèn)數(shù)據(jù)源 spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
2.3編寫相關(guān)的服務(wù)類
基于數(shù)據(jù)庫中tb_db_info表,編寫相關(guān)的查詢邏輯,代碼示例如下:
package com.example.dynamic.datasource.entity;
publicclass DbInfo {
/**
* 主鍵ID
*/
private Integer id;
/**
* 數(shù)據(jù)庫key,即保存Map中的key
*/
private String dbName;
/**
* 數(shù)據(jù)庫地址
*/
private String dbUrl;
/**
* 數(shù)據(jù)庫驅(qū)動
*/
private String driverClassName;
/**
* 數(shù)據(jù)庫用戶名
*/
private String username;
/**
* 數(shù)據(jù)庫密碼
*/
private String password;
// set、get方法等...
}public interface DbInfoMapper {
List<DbInfo> findAll();
}<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dynamic.datasource.mapper.DbInfoMapper">
<select id="findAll" resultType="com.example.dynamic.datasource.entity.DbInfo">
select
id
,db_name as dbName
,db_url as dbUrl
,driver_class_name as driverClassName
,username
,password
from tb_db_info
order by id
</select>
</mapper>2.4修改動態(tài)數(shù)據(jù)源服務(wù)類
對DynamicDataSource類進(jìn)行一些調(diào)整,代碼如下:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.get();
}
/**
* 重新加載數(shù)據(jù)源集合
* @param dbList
*/
public void loadDataSources(List<DbInfo> dbList){
try {
Map<Object, Object> targetDataSourceMap = new HashMap<>();
for (DbInfo source : dbList) {
// 初始化數(shù)據(jù)源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(source.getDriverClassName());
dataSource.setUrl(source.getDbUrl());
dataSource.setUsername(source.getUsername());
dataSource.setPassword(source.getPassword());
dataSource.setInitialSize(1);
dataSource.setMinIdle(1);
dataSource.setMaxActive(5);
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery("select 1 ");
dataSource.init();
targetDataSourceMap.put(source.getDbName(), dataSource);
}
super.setTargetDataSources(targetDataSourceMap);
// 重新初始化resolvedDataSources對象
super.afterPropertiesSet();
} catch (Exception e){
e.printStackTrace();
}
}
}2.5修改動態(tài)數(shù)據(jù)源配置類
對DataSourceConfig類也需要進(jìn)行一些調(diào)整,通過 Spring Boot 默認(rèn)的數(shù)據(jù)源配置類初始化一個(gè)數(shù)據(jù)源實(shí)例對象,代碼如下:
@Configuration
publicclass DataSourceConfig {
@Autowired
private DataSourceProperties basicProperties;
/**
* 注入動態(tài)數(shù)據(jù)源
* @param dataSource
* @return
*/
@Bean
@Primary
public DynamicDataSource dynamicDataSource(){
// 獲取初始數(shù)據(jù)源
DataSource defaultDataSource = basicProperties.initializeDataSourceBuilder().build();
Map<Object,Object> targetDataSources = new HashMap<>();
targetDataSources.put("defaultDataSource", defaultDataSource);
// 注入動態(tài)數(shù)據(jù)源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 設(shè)置默認(rèn)數(shù)據(jù)源
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
// 設(shè)置動態(tài)數(shù)據(jù)源集
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}2.6配置啟動時(shí)加載數(shù)據(jù)源服務(wù)類
以上的配置調(diào)整完成之后,我們還需要配置一個(gè)服務(wù)啟動監(jiān)聽類,將從數(shù)據(jù)庫中查詢到的數(shù)據(jù)配置信息加載到DynamicDataSource對象中,代碼示例如下:

2.7調(diào)整 SpringBootApplication 注解配置
以上的實(shí)現(xiàn)方式,因?yàn)閱拥臅r(shí)候,采用的是 Spring Boot 默認(rèn)的數(shù)據(jù)源配置實(shí)現(xiàn),因此無需排除DataSourceAutoConfiguration類,可以將相關(guān)參數(shù)移除掉。

到此這篇關(guān)于SpringBoot實(shí)現(xiàn)數(shù)據(jù)源動態(tài)切換的最佳姿勢的文章就介紹到這了,更多相關(guān)SpringBoot數(shù)據(jù)源動態(tài)切換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 在SpringBoot項(xiàng)目中動態(tài)切換數(shù)據(jù)源和數(shù)據(jù)庫的詳細(xì)步驟
- SpringBoot項(xiàng)目中如何動態(tài)切換數(shù)據(jù)源、數(shù)據(jù)庫
- SpringBoot實(shí)現(xiàn)動態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐
- SpringBoot實(shí)現(xiàn)動態(tài)數(shù)據(jù)源切換的方法總結(jié)
- 使用SpringBoot動態(tài)切換數(shù)據(jù)源的實(shí)現(xiàn)方式
- Springboot實(shí)現(xiàn)多數(shù)據(jù)源切換詳情
- SpringBoot多數(shù)據(jù)源切換實(shí)現(xiàn)代碼(Mybaitis)
- SpringBoot實(shí)現(xiàn)動態(tài)切換數(shù)據(jù)源的示例代碼
相關(guān)文章
java算法之靜態(tài)內(nèi)部類實(shí)現(xiàn)雪花算法
這篇文章主要介紹了java算法之靜態(tài)內(nèi)部類實(shí)現(xiàn)雪花算法,對算法感興趣的同學(xué),一定要看一下2021-05-05
JAVA中字符串函數(shù)subString的用法小結(jié)
本篇文章主要是對JAVA中字符串函數(shù)subString的用法進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-02-02
關(guān)于Hibernate的一些學(xué)習(xí)心得總結(jié)
Hibernate是一個(gè)優(yōu)秀的Java 持久化層解決方案,是當(dāng)今主流的對象—關(guān)系映射(ORM)工具2013-07-07
Apache POI導(dǎo)出Excel遇NoClassDefFoundError的原因分析與解決方案
在日常的Java開發(fā)中,我們經(jīng)常需要實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出到Excel的功能,本文將簡單介紹Apache POI導(dǎo)出Excel遇NoClassDefFoundError錯(cuò)誤的原因與解決,希望對大家有所幫助2025-10-10
Java實(shí)現(xiàn)域名解析的示例詳解(附帶源碼)
這篇文章將從理論到實(shí)踐和從代碼到測試,全方位地講解如何利用?Java?實(shí)現(xiàn)一個(gè)簡單的域名解析器,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-03-03
使用Stargate訪問K8ssandra的過程之Springboot整合Cassandra
這篇文章主要介紹了使用Stargate訪問K8ssandra的過程之Springboot整合Cassandra,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10
mybatis-plus報(bào)錯(cuò)Not Found TableInfoCache異常問題
在集成百度uid-generator過程中,MyBatis-Plus報(bào)錯(cuò)NotFoundTableInfoCache異常,解決方法:檢查實(shí)體類是否繼承了官方model,確保實(shí)體類對應(yīng)的mapper已正確注入,在使用@Component注解時(shí),應(yīng)保證相關(guān)依賴已注入2024-09-09
詳解使用Maven開發(fā)Web應(yīng)用詳細(xì)步驟
這篇文章主要介紹了詳解使用Maven開發(fā)Web應(yīng)用詳細(xì)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

