SpringBoot實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)切換的最佳姿勢(shì)
在介紹動(dòng)態(tài)數(shù)據(jù)源之前,我們先一起來(lái)看看多數(shù)據(jù)源在 Spring Boot 中的實(shí)現(xiàn)方式。
1.1數(shù)據(jù)庫(kù)準(zhǔn)備
創(chuàng)建兩個(gè)庫(kù),分別是db_test_1
和db_test_2
。db_test_1
數(shù)據(jù)庫(kù)中創(chuàng)建一張用戶表,腳本如下:
db_test_2
數(shù)據(jù)庫(kù)中創(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 測(cè)試--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--mysql 驅(qū)動(dòng)--> <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編寫(xiě)多數(shù)據(jù)源
創(chuàng)建動(dòng)態(tài)數(shù)據(jù)源服務(wù)類
首先,創(chuàng)建一個(gè)DynamicDataSource
類,并繼承AbstractRoutingDataSource
抽象類,同時(shí)重寫(xiě)determineCurrentLookupKey()
方法,代碼示例如下:
創(chuàng)建動(dòng)態(tài)數(shù)據(jù)源緩存類
創(chuàng)建一個(gè)DataSourceContextHolder
類,用于緩存數(shù)據(jù)源,同時(shí)需要確保線程環(huán)境下安全
package com.example.dynamic.datasource.config; publicclass DataSourceContextHolder { /** * 設(shè)置線程獨(dú)立變量,用于存儲(chǔ)數(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)建動(dòng)態(tài)數(shù)據(jù)源配置類
接著,創(chuàng)建一個(gè)DataSourceConfig
配置類,設(shè)置動(dòng)態(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()); // 注入動(dòng)態(tài)數(shù)據(jù)源到bean工廠 DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 設(shè)置默認(rèn)數(shù)據(jù)源 dynamicDataSource.setDefaultTargetDataSource(db1()); // 設(shè)置動(dòng)態(tài)數(shù)據(jù)源集 dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } }
編寫(xiě)相關(guān)配置變量
根據(jù)上面的配置變量,我們還需要在application.properties
文件中添加相關(guān)的數(shù)據(jù)源變量,內(nèi)容如下:
排除自動(dòng)裝配數(shù)據(jù)源
需要在注解@SpringBootApplication
類上排除自動(dòng)裝配數(shù)據(jù)源配置,內(nèi)容如下:
1.4利用切面代理類設(shè)置數(shù)據(jù)源
在上文中,我們采用的是手動(dòng)方式來(lái)設(shè)置數(shù)據(jù)源,在實(shí)際的業(yè)務(wù)開(kāi)發(fā)中,我們通常會(huì)采用切面代理類來(lái)設(shè)置數(shù)據(jù)源,以便簡(jiǎn)化代碼復(fù)雜度。
創(chuàng)建數(shù)據(jù)源注解
首先,定義一個(gè)數(shù)據(jù)源注解來(lái)實(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"; }
編寫(xiě)數(shù)據(jù)源代理類
接著,基于@DbSource
注解,創(chuàng)建一個(gè) AOP 代理類,所有配置該注解的方法都會(huì)被前后攔截,代碼示例如下:
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 代理的方式來(lái)切換數(shù)據(jù)源,業(yè)務(wù)實(shí)現(xiàn)上會(huì)更加的靈活。
在上文中,我們介紹了多數(shù)據(jù)源的配置實(shí)現(xiàn)方式,這種配置方式有一個(gè)不好的地方在于:配置文件都是寫(xiě)死的。
能不能改成動(dòng)態(tài)的加載數(shù)據(jù)源呢,下面我們一起來(lái)看看相關(guān)的具體實(shí)現(xiàn)方式
2.1數(shù)據(jù)庫(kù)準(zhǔn)備
首先,我們需要準(zhǔn)備一張數(shù)據(jù)源配置表。新建一個(gè)test_db
數(shù)據(jù)庫(kù),然后在數(shù)據(jù)庫(kù)中創(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編寫(xiě)相關(guān)的服務(wù)類
基于數(shù)據(jù)庫(kù)中tb_db_info
表,編寫(xiě)相關(guān)的查詢邏輯,代碼示例如下:
package com.example.dynamic.datasource.entity; publicclass DbInfo { /** * 主鍵ID */ private Integer id; /** * 數(shù)據(jù)庫(kù)key,即保存Map中的key */ private String dbName; /** * 數(shù)據(jù)庫(kù)地址 */ private String dbUrl; /** * 數(shù)據(jù)庫(kù)驅(qū)動(dòng) */ private String driverClassName; /** * 數(shù)據(jù)庫(kù)用戶名 */ private String username; /** * 數(shù)據(jù)庫(kù)密碼 */ 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修改動(dòng)態(tài)數(shù)據(jù)源服務(wù)類
對(duì)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對(duì)象 super.afterPropertiesSet(); } catch (Exception e){ e.printStackTrace(); } } }
2.5修改動(dòng)態(tài)數(shù)據(jù)源配置類
對(duì)DataSourceConfig
類也需要進(jìn)行一些調(diào)整,通過(guò) Spring Boot 默認(rèn)的數(shù)據(jù)源配置類初始化一個(gè)數(shù)據(jù)源實(shí)例對(duì)象,代碼如下:
@Configuration publicclass DataSourceConfig { @Autowired private DataSourceProperties basicProperties; /** * 注入動(dòng)態(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); // 注入動(dòng)態(tài)數(shù)據(jù)源 DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 設(shè)置默認(rèn)數(shù)據(jù)源 dynamicDataSource.setDefaultTargetDataSource(defaultDataSource); // 設(shè)置動(dòng)態(tài)數(shù)據(jù)源集 dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } }
2.6配置啟動(dòng)時(shí)加載數(shù)據(jù)源服務(wù)類
以上的配置調(diào)整完成之后,我們還需要配置一個(gè)服務(wù)啟動(dòng)監(jiān)聽(tīng)類,將從數(shù)據(jù)庫(kù)中查詢到的數(shù)據(jù)配置信息加載到DynamicDataSource
對(duì)象中,代碼示例如下:
2.7調(diào)整 SpringBootApplication 注解配置
以上的實(shí)現(xiàn)方式,因?yàn)閱?dòng)的時(shí)候,采用的是 Spring Boot 默認(rèn)的數(shù)據(jù)源配置實(shí)現(xiàn),因此無(wú)需排除DataSourceAutoConfiguration
類,可以將相關(guān)參數(shù)移除掉。
到此這篇關(guān)于SpringBoot實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)切換的最佳姿勢(shì)的文章就介紹到這了,更多相關(guān)SpringBoot數(shù)據(jù)源動(dòng)態(tài)切換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的方法總結(jié)
- 使用SpringBoot動(dòng)態(tài)切換數(shù)據(jù)源的實(shí)現(xiàn)方式
- springboot使用DynamicDataSource動(dòng)態(tài)切換數(shù)據(jù)源的實(shí)現(xiàn)過(guò)程
- Springboot動(dòng)態(tài)切換數(shù)據(jù)源的具體實(shí)現(xiàn)與原理分析
- 詳細(xì)聊聊SpringBoot中動(dòng)態(tài)切換數(shù)據(jù)源的方法
相關(guān)文章
java獲取兩個(gè)數(shù)組中不同數(shù)據(jù)的方法
這篇文章主要介紹了java獲取兩個(gè)數(shù)組中不同數(shù)據(jù)的方法,實(shí)例分析了java操作數(shù)組的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-03-03Java實(shí)現(xiàn)短信驗(yàn)證碼服務(wù)的完整代碼示例
這篇文章主要介紹了Java實(shí)現(xiàn)短信驗(yàn)證碼服務(wù)的完整代碼示例,文中使用阿里云的短信服務(wù)進(jìn)行應(yīng)用開(kāi)發(fā)的流程,包括將屬性寫(xiě)入application.yml配置文件,定義類并指定配置文件,注入實(shí)體類對(duì)象等等,需要的朋友可以參考下2024-09-09java枚舉enum,根據(jù)value值獲取key鍵的操作
這篇文章主要介紹了java枚舉enum,根據(jù)value值獲取key鍵的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Java數(shù)據(jù)結(jié)構(gòu)之雙向鏈表的實(shí)現(xiàn)
相較單鏈表,雙向鏈表除了data與next域,還多了一個(gè)pre域用于表示每個(gè)節(jié)點(diǎn)的前一個(gè)元素。這樣做給雙向鏈表帶來(lái)了很多優(yōu)勢(shì)。本文主要介紹了雙向鏈表的實(shí)現(xiàn),需要的可以參考一下2022-10-10MyBatis游標(biāo)Cursor在Oracle數(shù)據(jù)庫(kù)上的測(cè)試方式
這篇文章主要介紹了MyBatis游標(biāo)Cursor在Oracle數(shù)據(jù)庫(kù)上的測(cè)試方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01MySQL中關(guān)鍵字UNION和UNION ALL的區(qū)別
本文主要介紹了MySQL中關(guān)鍵字UNION和UNION ALL的區(qū)別,深入探討UNION和UNION ALL的定義、用法、主要區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06