spring實(shí)現(xiàn)動(dòng)態(tài)切換、添加數(shù)據(jù)源及源碼分析
前言
對(duì)于數(shù)據(jù)量在1千萬(wàn),單個(gè)mysql數(shù)據(jù)庫(kù)就可以支持,但是如果數(shù)據(jù)量大于這個(gè)數(shù)的時(shí)候,例如1億,那么查詢的性能就會(huì)很低。此時(shí)需要對(duì)數(shù)據(jù)庫(kù)做水平切分,常見的做法是按照用戶的賬號(hào)進(jìn)行hash,然后選擇對(duì)應(yīng)的數(shù)據(jù)庫(kù)。
最近公司項(xiàng)目需求,由于要兼容老系統(tǒng)的數(shù)據(jù)庫(kù)結(jié)構(gòu),需要搭建一個(gè) 可以動(dòng)態(tài)切換、添加數(shù)據(jù)源的后端服務(wù)。
參考了過(guò)去的項(xiàng)目,通過(guò)配置多個(gè)SqlSessionFactory 來(lái)實(shí)現(xiàn)多數(shù)據(jù)源,這么做的話,未免過(guò)于笨重,而且無(wú)法實(shí)現(xiàn)動(dòng)態(tài)添加數(shù)據(jù)源這個(gè)需求

通過(guò) spring AbstractRoutingDataSource 為我們抽象了一個(gè) DynamicDataSource 解決這一問(wèn)題

簡(jiǎn)單分析下 AbstractRoutingDataSource 的源碼


targetDataSources 就是我們的多個(gè)數(shù)據(jù)源,在初始化的時(shí)候會(huì)調(diào)用afterPropertiesSet(),去解析我們的數(shù)據(jù)源 然后 put 到 resolvedDataSources

實(shí)現(xiàn)了 DataSource 的 getConnection(); 我們看看 determineTargetDataSource(); 做了什么

通過(guò)下面的 determineCurrentLookupKey();(這個(gè)方法需要我們實(shí)現(xiàn)) 返回一個(gè)key,然后從 resolvedDataSources (其實(shí)也就是 targetDataSources) 中 get 一個(gè)數(shù)據(jù)源,實(shí)現(xiàn)了每次調(diào)用 getConnection(); 打開連接 切換數(shù)據(jù)源,如果想動(dòng)態(tài)添加的話 只需要重新 set targetDataSources 再調(diào)用 afterPropertiesSet() 即可
Talk is cheap. Show me the code
我使用的springboot版本為 1.5.x,下面是核心代碼
完整代碼:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下載)
/**
* 多數(shù)據(jù)源配置
*
* @author Taven
*
*/
@Configuration
@MapperScan("com.gitee.taven.mapper")
public class DataSourceConfigurer {
/**
* DataSource 自動(dòng)配置并注冊(cè)
*
* @return data source
*/
@Bean("db0")
@Primary
@ConfigurationProperties(prefix = "datasource.db0")
public DataSource dataSource0() {
return DruidDataSourceBuilder.create().build();
}
/**
* DataSource 自動(dòng)配置并注冊(cè)
*
* @return data source
*/
@Bean("db1")
@ConfigurationProperties(prefix = "datasource.db1")
public DataSource dataSource1() {
return DruidDataSourceBuilder.create().build();
}
/**
* 注冊(cè)動(dòng)態(tài)數(shù)據(jù)源
*
* @return
*/
@Bean("dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("dynamic_db0", dataSource0());
dataSourceMap.put("dynamic_db1", dataSource1());
dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource0());// 設(shè)置默認(rèn)數(shù)據(jù)源
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
/**
* Sql session factory bean.
* Here to config datasource for SqlSessionFactory
* <p>
* You need to add @{@code @ConfigurationProperties(prefix = "mybatis")}, if you are using *.xml file,
* the {@code 'mybatis.type-aliases-package'} and {@code 'mybatis.mapper-locations'} should be set in
* {@code 'application.properties'} file, or there will appear invalid bond statement exception
*
* @return the sql session factory bean
*/
@Bean
@ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 必須將動(dòng)態(tài)數(shù)據(jù)源添加到 sqlSessionFactoryBean
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
return sqlSessionFactoryBean;
}
/**
* 事務(wù)管理器
*
* @return the platform transaction manager
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
通過(guò) ThreadLocal 獲取線程安全的數(shù)據(jù)源 key
package com.gitee.taven.config;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "dynamic_db0";
}
};
/**
* To switch DataSource
*
* @param key the key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* Get current DataSource
*
* @return data source key
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* To set DataSource as default
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
動(dòng)態(tài) 添加、切換數(shù)據(jù)源
/**
* 動(dòng)態(tài)數(shù)據(jù)源
*
* @author Taven
*
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static Map<Object, Object> targetDataSources = new HashMap<>();
/**
* 設(shè)置當(dāng)前數(shù)據(jù)源
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
DynamicRoutingDataSource.targetDataSources = targetDataSources;
}
/**
* 是否存在當(dāng)前key的 DataSource
*
* @param key
* @return 存在返回 true, 不存在返回 false
*/
public static boolean isExistDataSource(String key) {
return targetDataSources.containsKey(key);
}
/**
* 動(dòng)態(tài)增加數(shù)據(jù)源
*
* @param map 數(shù)據(jù)源屬性
* @return
*/
public synchronized boolean addDataSource(Map<String, String> map) {
try {
Connection connection = null;
// 排除連接不上的錯(cuò)誤
try {
Class.forName(map.get(DruidDataSourceFactory.PROP_DRIVERCLASSNAME));
connection = DriverManager.getConnection(
map.get(DruidDataSourceFactory.PROP_URL),
map.get(DruidDataSourceFactory.PROP_USERNAME),
map.get(DruidDataSourceFactory.PROP_PASSWORD));
System.out.println(connection.isClosed());
} catch (Exception e) {
return false;
} finally {
if (connection != null && !connection.isClosed())
connection.close();
}
String database = map.get("database");//獲取要添加的數(shù)據(jù)庫(kù)名
if (StringUtils.isBlank(database)) return false;
if (DynamicRoutingDataSource.isExistDataSource(database)) return true;
DruidDataSource druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(map);
druidDataSource.init();
Map<Object, Object> targetMap = DynamicRoutingDataSource.targetDataSources;
targetMap.put(database, druidDataSource);
// 當(dāng)前 targetDataSources 與 父類 targetDataSources 為同一對(duì)象 所以不需要set
// this.setTargetDataSources(targetMap);
this.afterPropertiesSet();
logger.info("dataSource {} has been added", database);
} catch (Exception e) {
logger.error(e.getMessage());
return false;
}
return true;
}
}
可以通過(guò) AOP 或者 手動(dòng) DynamicDataSourceContextHolder.setDataSourceKey(String key) 切換數(shù)據(jù)源
需要注意的:當(dāng)我們開啟了事務(wù)之后,是無(wú)法在去切換數(shù)據(jù)源的
本文項(xiàng)目源碼:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下載)
參考文獻(xiàn):https://github.com/helloworlde/SpringBoot-DynamicDataSource
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
SpringBoot使用hutool-captcha實(shí)現(xiàn)驗(yàn)證碼生成與驗(yàn)證
在springboot的登陸頁(yè)面中為了防止機(jī)器大規(guī)模注冊(cè),機(jī)器暴力破解數(shù)據(jù)密碼等危害,需要驗(yàn)證隨機(jī)生成的驗(yàn)證碼,本文主要介紹了SpringBoot使用hutool-captcha實(shí)現(xiàn)驗(yàn)證碼生成與驗(yàn)證,感興趣的可以了解一下2023-12-12
SpringBoot中使用SpringSecurity進(jìn)行權(quán)限控制的示例代碼
本文將詳細(xì)介紹如何在Spring Boot應(yīng)用程序中使用Spring Security進(jìn)行權(quán)限控制,我們將探討Spring Security的基本概念,以及如何使用Spring Security實(shí)現(xiàn)認(rèn)證和授權(quán),需要的朋友可以參考下2024-02-02
Java中的CopyOnWriteArrayList你了解嗎
CopyOnWriteArrayList是Java集合框架中的一種線程安全的List實(shí)現(xiàn),這篇文章主要來(lái)和大家聊聊CopyOnWriteArrayList的簡(jiǎn)單使用,需要的可以參考一下2023-06-06
java 對(duì)象參數(shù)去空格方式代碼實(shí)例
這篇文章主要介紹了java 對(duì)象參數(shù)去空格方式代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
100-200之間所有素?cái)?shù)求和程序代碼(二個(gè)版本)
寫一個(gè)求100-200之間素?cái)?shù),并求和的程序,大家參考使用吧2013-11-11
Springcloud Config支持本地配置文件的方法示例
這篇文章主要介紹了Springcloud Config支持本地配置文件的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Java中數(shù)據(jù)庫(kù)常用的兩把鎖之樂(lè)觀鎖和悲觀鎖
這篇文章主要介紹了數(shù)據(jù)庫(kù)常用的兩把鎖之樂(lè)觀鎖和悲觀鎖,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07

