SpringBoot自定義動(dòng)態(tài)數(shù)據(jù)源的流程步驟
1. 原理
動(dòng)態(tài)數(shù)據(jù)源,本質(zhì)上是把多個(gè)數(shù)據(jù)源存儲(chǔ)在一個(gè) Map
中,當(dāng)需要使用某一個(gè)數(shù)據(jù)源時(shí),使用 key
獲取指定數(shù)據(jù)源進(jìn)行處理。而在 Spring
中已提供了抽象類 AbstractRoutingDataSource
來(lái)實(shí)現(xiàn)此功能,繼承 AbstractRoutingDataSource
類并覆寫其 determineCurrentLookupKey()
方法監(jiān)聽(tīng)獲取 key
即可,該方法只需要返回?cái)?shù)據(jù)源 key
即可,也就是存放數(shù)據(jù)源的 Map
的 key
。
因此,我們?cè)趯?shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源的,只需要繼承它,實(shí)現(xiàn)自己的獲取數(shù)據(jù)源邏輯即可。AbstractRoutingDataSource
頂級(jí)繼承了 DataSource
,所以它也是可以做為數(shù)據(jù)源對(duì)象,因此項(xiàng)目中使用它作為主數(shù)據(jù)源。
1.1. AbstractRoutingDataSource 源碼解析
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { // 目標(biāo)數(shù)據(jù)源 map 集合,存儲(chǔ)將要切換的多數(shù)據(jù)源 bean 信息,可以通過(guò) setTargetDataSource(Map<Object, Object> mp) 設(shè)置 @Nullable private Map<Object, Object> targetDataSources; // 未指定數(shù)據(jù)源時(shí)的默認(rèn)數(shù)據(jù)源對(duì)象,可以通過(guò) setDefaultTargetDataSouce(Object obj) 設(shè)置 @Nullable private Object defaultTargetDataSource; ... // 數(shù)據(jù)源查找接口,通過(guò)該接口的 getDataSource(String dataSourceName) 獲取數(shù)據(jù)源信息 private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); //解析 targetDataSources 之后的 DataSource 的 map 集合 @Nullable private Map<Object, DataSource> resolvedDataSources; @Nullable private DataSource resolvedDefaultDataSource; //將 targetDataSources 的內(nèi)容轉(zhuǎn)化一下放到 resolvedDataSources 中,將 defaultTargetDataSource 轉(zhuǎn)為 DataSource 賦值給 resolvedDefaultDataSource public void afterPropertiesSet() { //如果目標(biāo)數(shù)據(jù)源為空,會(huì)拋出異常,在系統(tǒng)配置時(shí)應(yīng)至少傳入一個(gè)數(shù)據(jù)源 if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } else { //初始化 resolvedDataSources 的大小 this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size()); //遍歷目標(biāo)數(shù)據(jù)源信息 map 集合,對(duì)其中的 key,value 進(jìn)行解析 this.targetDataSources.forEach((key, value) -> { // resolveSpecifiedLookupKey 方法沒(méi)有做任何處理,只是將 key 繼續(xù)返回 Object lookupKey = this.resolveSpecifiedLookupKey(key); // 將目標(biāo)數(shù)據(jù)源 map 集合中的 value 值(Druid 數(shù)據(jù)源信息)轉(zhuǎn)為 DataSource 類型 DataSource dataSource = this.resolveSpecifiedDataSource(value); // 將解析之后的 key,value 放入 resolvedDataSources 集合中 this.resolvedDataSources.put(lookupKey, dataSource); }); if (this.defaultTargetDataSource != null) { // 將默認(rèn)目標(biāo)數(shù)據(jù)源信息解析并賦值給 resolvedDefaultDataSource this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource); } } } protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException { if (dataSource instanceof DataSource) { return (DataSource)dataSource; } else if (dataSource instanceof String) { return this.dataSourceLookup.getDataSource((String)dataSource); } else { throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource); } } // 因?yàn)?AbstractRoutingDataSource 繼承 AbstractDataSource,而 AbstractDataSource 實(shí)現(xiàn)了 DataSource 接口,所有存在獲取數(shù)據(jù)源連接的方法 public Connection getConnection() throws SQLException { return this.determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return this.determineTargetDataSource().getConnection(username, password); } // 最重要的一個(gè)方法,也是 DynamicDataSource 需要實(shí)現(xiàn)的方法 protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); // 調(diào)用實(shí)現(xiàn)類中重寫的 determineCurrentLookupKey 方法拿到當(dāng)前線程要使用的數(shù)據(jù)源的名稱 Object lookupKey = this.determineCurrentLookupKey(); // 去解析之后的數(shù)據(jù)源信息集合中查詢?cè)摂?shù)據(jù)源是否存在,如果沒(méi)有拿到則使用默認(rèn)數(shù)據(jù)源 resolvedDefaultDataSource DataSource 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 + "]"); } else { return dataSource; } } @Nullable protected abstract Object determineCurrentLookupKey(); }
1.2. 關(guān)鍵類說(shuō)明
忽略掉 controller
/service
/entity
/mapper
/xml
介紹。
application.yml
:數(shù)據(jù)源配置文件。但是如果數(shù)據(jù)源比較多的話,根據(jù)實(shí)際使用,最佳的配置方式還是獨(dú)立配置比較好。DynamicDataSourceRegister
:動(dòng)態(tài)數(shù)據(jù)源注冊(cè)配置文件DynamicDataSource
:動(dòng)態(tài)數(shù)據(jù)源配置類,繼承自AbstractRoutingDataSource
TargetDataSource
:動(dòng)態(tài)數(shù)據(jù)源注解,切換當(dāng)前線程的數(shù)據(jù)源DynamicDataSourceAspect
:動(dòng)態(tài)數(shù)據(jù)源設(shè)置切面,環(huán)繞通知,切換當(dāng)前線程數(shù)據(jù)源,方法注解優(yōu)先DynamicDataSourceContextHolder
:動(dòng)態(tài)數(shù)據(jù)源上下文管理器,保存當(dāng)前數(shù)據(jù)源的key
,默認(rèn)數(shù)據(jù)源名,所有數(shù)據(jù)源key
1.3. 開(kāi)發(fā)流程
- 添加配置文件,設(shè)置默認(rèn)數(shù)據(jù)源配置,和其他數(shù)據(jù)源配置
- 編寫
DynamicDataSource
類,繼承AbstractRoutingDataSource
類,并實(shí)現(xiàn)determineCurrentLookupKey()
方法 - 編寫
DynamicDataSourceHolder
上下文管理類,管理當(dāng)前線程的使用的數(shù)據(jù)源,及所有數(shù)據(jù)源的key
; - 編寫
DynamicDataSourceRegister
類通過(guò)讀取配置文件動(dòng)態(tài)注冊(cè)多數(shù)據(jù)源,并在啟動(dòng)類上導(dǎo)入(@Import
)該類 - 自定義數(shù)據(jù)源切換注解
TargetDataSource
,并實(shí)現(xiàn)相應(yīng)的切面,環(huán)繞通知切換當(dāng)前線程數(shù)據(jù)源,注解優(yōu)先級(jí)(DynamicDataSourceHolder.setDynamicDataSourceKey()
>Method
>Class
)
2. 實(shí)現(xiàn)
2.1. 引入 Maven 依賴
<!-- web 模塊依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- spring 核心 aop 模塊依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- Druid 數(shù)據(jù)源連接池依賴 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!-- mybatis 依賴 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!-- mysql驅(qū)動(dòng) --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.24</version> </dependency> <!-- lombok 模塊依賴 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-text</artifactId> <version>1.10.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
2.2. application.yml 配置文件
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding-utf8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root custom: datasource: names: ds1,ds2 ds1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/content_center?useUnicode username: root password: root ds2: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/trade?useUnicode username: root password: root
2.3. 創(chuàng)建 DynamicDataSource 繼承 AbstractRoutingDataSource 類
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; /** * @Description: 繼承Spring AbstractRoutingDataSource 實(shí)現(xiàn)路由切換 */ @Data @NoArgsConstructor @AllArgsConstructor public class DynamicDataSource extends AbstractRoutingDataSource { /** * 決定當(dāng)前線程使用哪種數(shù)據(jù)源 * @return 數(shù)據(jù)源 key */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
2.4. 編寫 DynamicDataSourceHolder 類,管理 DynamicDataSource 上下文
import java.util.ArrayList; import java.util.List; /** * @Description: 動(dòng)態(tài)數(shù)據(jù)源上下文管理 */ public class DynamicDataSourceHolder { // 存放當(dāng)前線程使用的數(shù)據(jù)源類型信息 private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<String>(); // 存放數(shù)據(jù)源 key private static final List<String> DATASOURCE_KEYS = new ArrayList<String>(); // 默認(rèn)數(shù)據(jù)源 key public static final String DEFAULT_DATESOURCE_KEY = "master"; //設(shè)置數(shù)據(jù)源 public static void setDynamicDataSourceType(String key) { DYNAMIC_DATASOURCE_KEY.set(key); } //獲取數(shù)據(jù)源 public static String getDynamicDataSourceType() { return DYNAMIC_DATASOURCE_KEY.get(); } //清除數(shù)據(jù)源 public static void removeDynamicDataSourceType() { DYNAMIC_DATASOURCE_KEY.remove(); } public static void addDataSourceKey(String key) { DATASOURCE_KEYS.add(key) } /** * 判斷指定 key 當(dāng)前是否存在 * * @param key * @return boolean */ public static boolean containsDataSource(String key){ return DATASOURCE_KEYS.contains(key); } }
2.5. 編寫 DynamicDataSourceRegister 讀取配置文件注冊(cè)多數(shù)據(jù)源
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.StringUtils; import java.util.Objects; /** * @Description: 注冊(cè)動(dòng)態(tài)數(shù)據(jù)源 * 初始化數(shù)據(jù)源和提供了執(zhí)行動(dòng)態(tài)切換數(shù)據(jù)源的工具類 * EnvironmentAware(獲取配置文件配置的屬性值) */ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceRegister.class); // 指定默認(rèn)數(shù)據(jù)源類型 (springboot2.0 默認(rèn)數(shù)據(jù)源是 hikari 如何想使用其他數(shù)據(jù)源可以自己配置) // private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource"; private static final String DEFAULT_DATASOURCE_TYPE = "com.alibaba.druid.pool.DruidDataSource"; // 默認(rèn)數(shù)據(jù)源 private DataSource defaultDataSource; // 用戶自定義數(shù)據(jù)源 private Map<String, DataSource> customDataSources = new HashMap<>(); /** * 加載多數(shù)據(jù)源配置 * @param env 當(dāng)前環(huán)境 */ @Override public void setEnvironment(Environment env) { initDefaultDataSource(env); initCustomDataSources(env); } /** * 初始化主數(shù)據(jù)源 * @param env */ private void initDefaultDataSource(Environment env) { // 讀取主數(shù)據(jù)源 Map<String, Object> dsMap = new HashMap<>(); dsMap.put("type", env.getProperty("spring.datasource.type", DEFAULT_DATASOURCE_TYPE)); dsMap.put("driver", env.getProperty("spring.datasource.driver-class-name")); dsMap.put("url", env.getProperty("spring.datasource.url")); dsMap.put("username", env.getProperty("spring.datasource.username")); dsMap.put("password", env.getProperty("spring.datasource.password")); defaultDataSource = buildDataSource(dsMap); } /** * 初始化更多數(shù)據(jù)源 * @param env */ private void initCustomDataSources(Environment env) { // 讀取配置文件獲取更多數(shù)據(jù)源 String dsPrefixs = env.getProperty("custom.datasource.names"); if (!StringUtils.isBlank(dsPrefixs)) { for (String dsPrefix : dsPrefixs.split(",")) { dsPrefix = fsPrefix.trim() if (!StringUtils.isBlank(dsPrefix)) { Map<String, Object> dsMap = new HashMap<>(); dsMap.put("type", env.getProperty("custom.datasource." + dsPrefix + ".type", DEFAULT_DATASOURCE_TYPE)); dsMap.put("driver", env.getProperty("custom.datasource." + dsPrefix + ".driver-class-name")); dsMap.put("url", env.getProperty("custom.datasource." + dsPrefix + ".url")); dsMap.put("username", env.getProperty("custom.datasource." + dsPrefix + ".username")); dsMap.put("password", env.getProperty("custom.datasource." + dsPrefix + ".password")); DataSource ds = buildDataSource(dsMap); customDataSources.put(dsPrefix, ds); } } } } @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); // 將主數(shù)據(jù)源添加到更多數(shù)據(jù)源中 targetDataSources.put(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY, defaultDataSource); DynamicDataSourceHolder.addDataSourceKey(DynamicDataSourceHolder.DEFAULT_DATASOURCE_KEY); // 添加更多數(shù)據(jù)源 targetDataSources.putAll(customDataSources); for (String key : customDataSources.keySet()) { DynamicDataSourceContextHolder.addDataSourceKey(key); } // 創(chuàng)建 DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); registry.registerBeanDefinition("dataSource", beanDefinition); // 注冊(cè)到 Spring 容器中 LOGGER.info("Dynamic DataSource Registry"); } /** * 創(chuàng)建 DataSource * @param dsMap 數(shù)據(jù)庫(kù)配置參數(shù) * @return DataSource */ public DataSource buildDataSource(Map<String, Object> dsMap) { try { Object type = dsMap.get("type"); if (type == null) type = DEFAULT_DATASOURCE_TYPE;// 默認(rèn)DataSource Class<? extends DataSource> dataSourceType = (Class<? extends DataSource>)Class.forName((String)type); String driverClassName = String.valueOf(dsMap.get("driver")); String url = String.valueOf(dsMap.get("url")); String username = String.valueOf(dsMap.get("username")); String password = String.valueOf(dsMap.get("password")); // 自定義 DataSource 配置 DataSourceBuilder<? extends DataSource> factory = DataSourceBuilder.create() .driverClassName(driverClassName) .url(url) .username(username) .password(password) .type(dataSourceType); return factory.build(); }catch (ClassNotFoundException e) { e.printStackTrace(); } } }
2.6. 在啟動(dòng)器類上添加 @Import,導(dǎo)入 register 類
// 注冊(cè)動(dòng)態(tài)多數(shù)據(jù)源 @Import({ DynamicDataSourceRegister.class }) @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
2.7. 自定義注解 @TargetDataSource
/** * 自定義多數(shù)據(jù)源切換注解 * 優(yōu)先級(jí):DynamicDataSourceHolder.setDynamicDataSourceKey() > Method > Class */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource { /** * 切換數(shù)據(jù)源名稱 */ public String value() default DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY; }
2.8. 定義切面攔截 @TargetDataSource
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.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Objects; @Aspect // 保證在 @Transactional 等注解前面執(zhí)行 @Order(-1) @Component public class DataSourceAspect { // 設(shè)置 DataSource 注解的切點(diǎn)表達(dá)式 @Pointcut("@annotation(com.ayi.config.datasource.DynamicDataSource)") public void dynamicDataSourcePointCut(){ } //環(huán)繞通知 @Around("dynamicDataSourcePointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ String key = getDefineAnnotation(joinPoint).value(); if (!DynamicDataSourceHolder.containsDataSource(key)) { LOGGER.error("數(shù)據(jù)源[{}]不存在,使用默認(rèn)數(shù)據(jù)源[{}]", key, DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY) key = DynamicDataSourceHolder.DEFAULT_DATESOURCE_KEY; } DynamicDataSourceHolder.setDynamicDataSourceKey(key); try { return joinPoint.proceed(); } finally { DynamicDataSourceHolder.removeDynamicDataSourceKey(); } } /** * 先判斷方法的注解,后判斷類的注解,以方法的注解為準(zhǔn) * @param joinPoint 切點(diǎn) * @return TargetDataSource */ private TargetDataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); TargetDataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(TargetDataSource.class); if (Objects.nonNull(methodSignature)) { return dataSourceAnnotation; } else { Class<?> dsClass = joinPoint.getTarget().getClass(); return dsClass.getAnnotation(TargetDataSource.class); } } }
以上就是SpringBoot自定義動(dòng)態(tài)數(shù)據(jù)源的流程步驟的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot動(dòng)態(tài)數(shù)據(jù)源的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis實(shí)現(xiàn)插入數(shù)據(jù)后返回主鍵過(guò)程解析
這篇文章主要介紹了Mybatis實(shí)現(xiàn)插入數(shù)據(jù)后返回主鍵過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06MyBatis中example.createCriteria()方法的具體使用
本文詳細(xì)介紹了MyBatis的Example工具的使用方法,包括鏈?zhǔn)秸{(diào)用指定字段、設(shè)置查詢條件、支持多種查詢方式等,還介紹了mapper的crud方法、and/or方法的使用,以及如何進(jìn)行多條件和多重條件查詢,感興趣的可以了解一下2024-10-10Java中使用增強(qiáng)for循環(huán)的實(shí)例方法
在本篇文章里小編給大家整理是的關(guān)于Java中如何使用增強(qiáng)for循環(huán)的實(shí)例內(nèi)容以及相關(guān)代碼,需要的朋友們可以學(xué)習(xí)下。2019-08-08Java多線程案例實(shí)戰(zhàn)之定時(shí)器的實(shí)現(xiàn)
在Java中可以使用多線程和定時(shí)器來(lái)實(shí)現(xiàn)定時(shí)任務(wù),下面這篇文章主要給大家介紹了關(guān)于Java多線程案例之定時(shí)器實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01mybatis中bind標(biāo)簽和concat的使用說(shuō)明
這篇文章主要介紹了mybatis中bind標(biāo)簽和concat的使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12