springboot 動態(tài)數(shù)據源的實現(xiàn)方法(Mybatis+Druid)
Spring多數(shù)據源實現(xiàn)的方式大概有2中,一種是新建多個MapperScan掃描不同包,另外一種則是通過繼承AbstractRoutingDataSource實現(xiàn)動態(tài)路由。今天作者主要基于后者做的實現(xiàn),且方式1的實現(xiàn)比較簡單這里不做過多探討。
實現(xiàn)方式
方式1的實現(xiàn)(核心代碼):
@Configuration @MapperScan(basePackages = "com.goofly.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate") public class DataSource1Config1 { @Bean(name = "dataSource1") @ConfigurationProperties(prefix = "spring.datasource.test1") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } // .....略 } @Configuration @MapperScan(basePackages = "com.goofly.test2", sqlSessionTemplateRef = "test1SqlSessionTemplate") public class DataSourceConfig2 { @Bean(name = "dataSource2") @ConfigurationProperties(prefix = "spring.datasource.test2") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } // .....略 }
方式2的實現(xiàn)(核心代碼):
public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private static final Logger log = Logger.getLogger(DynamicRoutingDataSource.class); @Override protected Object determineCurrentLookupKey() { //從ThreadLocal中取值 return DynamicDataSourceContextHolder.get(); } }
第1種方式雖然實現(xiàn)比較加單,劣勢就是不同數(shù)據源的mapper文件不能在同一包名,就顯得不太靈活了。所以為了更加靈活的作為一個組件的存在,作者采用的第二種方式實現(xiàn)。
設計思路
- 當請求經過被注解修飾的類后,此時會進入到切面邏輯中。
- 切面邏輯會獲取注解中設置的key值,然后將該值存入到ThreadLocal中
- 執(zhí)行完切面邏輯后,會執(zhí)行AbstractRoutingDataSource.determineCurrentLookupKey()方法,然后從ThreadLocal中獲取之前設置的key值,然后將該值返回。
- 由于AbstractRoutingDataSource的targetDataSources是一個map,保存了數(shù)據源key和數(shù)據源的對應關系,所以能夠順利的找到該對應的數(shù)據源。
源碼解讀
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,如下:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { private Map<Object, Object> targetDataSources; private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); private Map<Object, DataSource> resolvedDataSources; private DataSource resolvedDefaultDataSource; 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; } /** * 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. */ protected abstract Object determineCurrentLookupKey(); //........略
targetDataSources是一個map結構,保存了key與數(shù)據源的對應關系;
dataSourceLookup是一個DataSourceLookup類型,默認實現(xiàn)是JndiDataSourceLookup。點開該類源碼會發(fā)現(xiàn),它實現(xiàn)了通過key獲取DataSource的邏輯。當然,這里可以通過setDataSourceLookup()來改變其屬性,因為關于此處有一個坑,后面會講到。
public class JndiDataSourceLookup extends JndiLocatorSupport implements DataSourceLookup { public JndiDataSourceLookup() { setResourceRef(true); } @Override public DataSource getDataSource(String dataSourceName) throws DataSourceLookupFailureException { try { return lookup(dataSourceName, DataSource.class); } catch (NamingException ex) { throw new DataSourceLookupFailureException( "Failed to look up JNDI DataSource with name '" + dataSourceName + "'", ex); } } }
組件使用
多數(shù)據源
# db1 spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.master.username = root spring.datasource.master.password = 123456 spring.datasource.master.driverClassName = com.mysql.jdbc.Driver spring.datasource.master.validationQuery = true spring.datasource.master.testOnBorrow = true ## db2 spring.datasource.slave.url = jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.slave.username = root spring.datasource.slave.password = 123456 spring.datasource.slave.driverClassName = com.mysql.jdbc.Driver spring.datasource.slave.validationQuery = true spring.datasource.slave.testOnBorrow = true #主數(shù)據源名稱 spring.maindb=master #mapperper包路徑 mapper.basePackages =com.btps.xli.multidb.demo.mapper
單數(shù)據源
為了讓使用者能夠用最小的改動實現(xiàn)最好的效果,作者對單數(shù)據源的多種配置做了兼容。
示例配置1(配置數(shù)據源名稱):
spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.master.username = root spring.datasource.master.password = 123456 spring.datasource.master.driverClassName = com.mysql.jdbc.Driver spring.datasource.master.validationQuery = true spring.datasource.master.testOnBorrow = true # mapper包路徑 mapper.basePackages = com.goofly.xli.multidb.demo.mapper # 主數(shù)據源名稱 spring.maindb=master
示例配置2(不配置數(shù)據源名稱):
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.username = root spring.datasource.password = 123456 spring.datasource.driverClassName = com.mysql.jdbc.Driver spring.datasource.validationQuery = true spring.datasource.testOnBorrow = true # mapper包路徑 mapper.basePackages = com.goofly.xli.multidb.demo.mapper
踩坑之路
多數(shù)據源的循環(huán)依賴
Description: The dependencies of some of the beans in the application context form a cycle: happinessController (field private com.db.service.HappinessService com.db.controller.HappinessController.happinessService) ↓ happinessServiceImpl (field private com.db.mapper.MasterDao com.db.service.HappinessServiceImpl.masterDao) ↓ masterDao defined in file [E:\GitRepository\framework-gray\test-db\target\classes\com\db\mapper\MasterDao.class] ↓ sqlSessionFactory defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class] ┌─────┐ | dynamicDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class] ↑ ↓ | firstDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class] ↑ ↓ | dataSourceInitializer
解決方案:
在Spring boot啟動的時候排除DataSourceAutoConfiguration即可。如下:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class DBMain { public static void main(String[] args) { SpringApplication.run(DBMain.class, args); } }
但是作者在創(chuàng)建多數(shù)據源的時候由于并未創(chuàng)建多個DataSource的Bean,而是只創(chuàng)建了一個即需要做動態(tài)數(shù)據源的那個Bean。 其他的DataSource則直接創(chuàng)建實例然后存放在Map里面,然后再設置到DynamicRoutingDataSource#setTargetDataSources即可。
因此這種方式也不會出現(xiàn)循環(huán)依賴的問題!
動態(tài)刷新數(shù)據源
筆者在設計之初是想構建一個動態(tài)刷新數(shù)據源的方案,所以利用了SpringCloud的@RefreshScope去標注數(shù)據源,然后利用RefreshScope#refresh實現(xiàn)刷新。但是在實驗的時候發(fā)現(xiàn)由Druid創(chuàng)建的數(shù)據源會因此而關閉,由Spring的DataSourceBuilder創(chuàng)建的數(shù)據源則不會發(fā)生任何變化。
最后關于此也沒能找到解決方案。同時思考,如果只能的可以實現(xiàn)動態(tài)刷新的話,那么數(shù)據源的原有連接會因為刷新而中斷嗎還是會有其他處理?
多數(shù)據源事務
有這么一種特殊情況,一個事務中調用了兩個不同數(shù)據源,這個時候動態(tài)切換數(shù)據源會因此而失效。
翻閱了很多文章,大概找了2中解決方案,一種是Atomikos進行事務管理,但是貌似性能并不是很理想。
另外一種則是通過優(yōu)先級控制,切面的的優(yōu)先級必須要大于數(shù)據源的優(yōu)先級,用注解@Order控制。
此處留一個坑!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- SpringBoot集成Druid實現(xiàn)多數(shù)據源的兩種方式
- SpringBoot整合Mybatis-Plus+Druid實現(xiàn)多數(shù)據源配置功能
- springboot mybatis druid配置多數(shù)據源教程
- 關于springboot配置druid數(shù)據源不生效問題(踩坑記)
- 使用springboot+druid雙數(shù)據源動態(tài)配置操作
- springboot配置多數(shù)據源并集成Druid和mybatis的操作
- SpringBoot環(huán)境Druid數(shù)據源使用及特點
- Springboot mybatis plus druid多數(shù)據源解決方案 dynamic-datasource的使用詳解
- SpringBoot整合Druid數(shù)據源過程詳解
- 通過springboot+mybatis+druid配置動態(tài)數(shù)據源
- Spring Boot+Mybatis+Druid+PageHelper實現(xiàn)多數(shù)據源并分頁的方法
- 詳解Spring Boot整合Mybatis實現(xiàn) Druid多數(shù)據源配置
- spring使用xml方式整合Druid數(shù)據源連接池
相關文章
利用Spring Social輕松搞定微信授權登錄的方法示例
這篇文章主要介紹了利用Spring Social輕松搞定微信授權登錄的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12一文徹底弄懂Java中MultipartFile接口和File類
MultipartFile是一個接口,我們可以理解為是Spring?給我們綁定的一個在使用文件上傳等時簡便實現(xiàn)的口子,這篇文章主要給大家介紹了關于如何通過一文徹底弄懂Java中MultipartFile接口和File類的相關資料,需要的朋友可以參考下2023-11-11spring security中的csrf防御原理(跨域請求偽造)
這篇文章主要介紹了spring security中的csrf防御機制原理解析(跨域請求偽造),本文通過實例代碼詳解的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12Spring @Async無法實現(xiàn)異步的解決方案
這篇文章主要介紹了Spring @Async無法實現(xiàn)異步的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10