springboot 動(dòng)態(tài)數(shù)據(jù)源的實(shí)現(xiàn)方法(Mybatis+Druid)
Spring多數(shù)據(jù)源實(shí)現(xiàn)的方式大概有2中,一種是新建多個(gè)MapperScan掃描不同包,另外一種則是通過繼承AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)路由。今天作者主要基于后者做的實(shí)現(xiàn),且方式1的實(shí)現(xiàn)比較簡(jiǎn)單這里不做過多探討。
實(shí)現(xiàn)方式
方式1的實(shí)現(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的實(shí)現(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種方式雖然實(shí)現(xiàn)比較加單,劣勢(shì)就是不同數(shù)據(jù)源的mapper文件不能在同一包名,就顯得不太靈活了。所以為了更加靈活的作為一個(gè)組件的存在,作者采用的第二種方式實(shí)現(xiàn)。
設(shè)計(jì)思路

- 當(dāng)請(qǐng)求經(jīng)過被注解修飾的類后,此時(shí)會(huì)進(jìn)入到切面邏輯中。
- 切面邏輯會(huì)獲取注解中設(shè)置的key值,然后將該值存入到ThreadLocal中
- 執(zhí)行完切面邏輯后,會(huì)執(zhí)行AbstractRoutingDataSource.determineCurrentLookupKey()方法,然后從ThreadLocal中獲取之前設(shè)置的key值,然后將該值返回。
- 由于AbstractRoutingDataSource的targetDataSources是一個(gè)map,保存了數(shù)據(jù)源key和數(shù)據(jù)源的對(duì)應(yīng)關(guān)系,所以能夠順利的找到該對(duì)應(yīng)的數(shù)據(jù)源。
源碼解讀
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是一個(gè)map結(jié)構(gòu),保存了key與數(shù)據(jù)源的對(duì)應(yīng)關(guān)系;
dataSourceLookup是一個(gè)DataSourceLookup類型,默認(rèn)實(shí)現(xiàn)是JndiDataSourceLookup。點(diǎn)開該類源碼會(huì)發(fā)現(xiàn),它實(shí)現(xiàn)了通過key獲取DataSource的邏輯。當(dāng)然,這里可以通過setDataSourceLookup()來改變其屬性,因?yàn)殛P(guān)于此處有一個(gè)坑,后面會(huì)講到。
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ù)據(jù)源
# 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ù)據(jù)源名稱 spring.maindb=master #mapperper包路徑 mapper.basePackages =com.btps.xli.multidb.demo.mapper
單數(shù)據(jù)源
為了讓使用者能夠用最小的改動(dòng)實(shí)現(xiàn)最好的效果,作者對(duì)單數(shù)據(jù)源的多種配置做了兼容。
示例配置1(配置數(shù)據(jù)源名稱):
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ù)據(jù)源名稱 spring.maindb=master
示例配置2(不配置數(shù)據(jù)源名稱):
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ù)據(jù)源的循環(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啟動(dòng)的時(shí)候排除DataSourceAutoConfiguration即可。如下:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DBMain {
public static void main(String[] args) {
SpringApplication.run(DBMain.class, args);
}
}
但是作者在創(chuàng)建多數(shù)據(jù)源的時(shí)候由于并未創(chuàng)建多個(gè)DataSource的Bean,而是只創(chuàng)建了一個(gè)即需要做動(dòng)態(tài)數(shù)據(jù)源的那個(gè)Bean。 其他的DataSource則直接創(chuàng)建實(shí)例然后存放在Map里面,然后再設(shè)置到DynamicRoutingDataSource#setTargetDataSources即可。
因此這種方式也不會(huì)出現(xiàn)循環(huán)依賴的問題!
動(dòng)態(tài)刷新數(shù)據(jù)源
筆者在設(shè)計(jì)之初是想構(gòu)建一個(gè)動(dòng)態(tài)刷新數(shù)據(jù)源的方案,所以利用了SpringCloud的@RefreshScope去標(biāo)注數(shù)據(jù)源,然后利用RefreshScope#refresh實(shí)現(xiàn)刷新。但是在實(shí)驗(yàn)的時(shí)候發(fā)現(xiàn)由Druid創(chuàng)建的數(shù)據(jù)源會(huì)因此而關(guān)閉,由Spring的DataSourceBuilder創(chuàng)建的數(shù)據(jù)源則不會(huì)發(fā)生任何變化。
最后關(guān)于此也沒能找到解決方案。同時(shí)思考,如果只能的可以實(shí)現(xiàn)動(dòng)態(tài)刷新的話,那么數(shù)據(jù)源的原有連接會(huì)因?yàn)樗⑿露袛鄦徇€是會(huì)有其他處理?
多數(shù)據(jù)源事務(wù)
有這么一種特殊情況,一個(gè)事務(wù)中調(diào)用了兩個(gè)不同數(shù)據(jù)源,這個(gè)時(shí)候動(dòng)態(tài)切換數(shù)據(jù)源會(huì)因此而失效。
翻閱了很多文章,大概找了2中解決方案,一種是Atomikos進(jìn)行事務(wù)管理,但是貌似性能并不是很理想。
另外一種則是通過優(yōu)先級(jí)控制,切面的的優(yōu)先級(jí)必須要大于數(shù)據(jù)源的優(yōu)先級(jí),用注解@Order控制。
此處留一個(gè)坑!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- SpringBoot中動(dòng)態(tài)數(shù)據(jù)源配置與使用詳解
- SpringBoot配置動(dòng)態(tài)數(shù)據(jù)源的實(shí)戰(zhàn)詳解
- SpringBoot自定義動(dòng)態(tài)數(shù)據(jù)源的流程步驟
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐
- SpringBoot動(dòng)態(tài)數(shù)據(jù)源連接測(cè)試的操作詳解
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的方法總結(jié)
- springboot配置多數(shù)據(jù)源(靜態(tài)和動(dòng)態(tài)數(shù)據(jù)源)
- SpringBoot中動(dòng)態(tài)數(shù)據(jù)源是實(shí)現(xiàn)與用途
- springboot動(dòng)態(tài)數(shù)據(jù)源+分布式事務(wù)的實(shí)現(xiàn)
相關(guān)文章
java反編譯工具jd-gui-osx?for?mac?M1芯片無法使用的問題及解決
這篇文章主要介紹了java反編譯工具jd-gui-osx?for?mac?M1芯片無法使用的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Java Testcontainers庫實(shí)現(xiàn)測(cè)試功能
這篇文章主要介紹了Java Testcontainers庫實(shí)現(xiàn)測(cè)試功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
mybatis-plus動(dòng)態(tài)數(shù)據(jù)源讀寫分離方式
在分布式項(xiàng)目開發(fā)中,動(dòng)態(tài)數(shù)據(jù)源的配置與使用至關(guān)重要,通過創(chuàng)建DynamicDatasourceService,實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)添加與調(diào)用,有效管理主從庫操作,減輕數(shù)據(jù)庫壓力,此外,通過配置類與@DS注解,實(shí)現(xiàn)了靈活的分庫查詢功能,為高效處理數(shù)據(jù)提供了強(qiáng)有力的支持2024-10-10

