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

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

簡單分析下 AbstractRoutingDataSource 的源碼


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

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

通過下面的 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)配置并注冊
*
* @return data source
*/
@Bean("db0")
@Primary
@ConfigurationProperties(prefix = "datasource.db0")
public DataSource dataSource0() {
return DruidDataSourceBuilder.create().build();
}
/**
* DataSource 自動(dòng)配置并注冊
*
* @return data source
*/
@Bean("db1")
@ConfigurationProperties(prefix = "datasource.db1")
public DataSource dataSource1() {
return DruidDataSourceBuilder.create().build();
}
/**
* 注冊動(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());
}
}
通過 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ù)庫名
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 為同一對象 所以不需要set
// this.setTargetDataSources(targetMap);
this.afterPropertiesSet();
logger.info("dataSource {} has been added", database);
} catch (Exception e) {
logger.error(e.getMessage());
return false;
}
return true;
}
}
可以通過 AOP 或者 手動(dòng) DynamicDataSourceContextHolder.setDataSourceKey(String key) 切換數(shù)據(jù)源
需要注意的:當(dāng)我們開啟了事務(wù)之后,是無法在去切換數(shù)據(jù)源的
本文項(xiàng)目源碼:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下載)
參考文獻(xiàn):https://github.com/helloworlde/SpringBoot-DynamicDataSource
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
新手小白學(xué)JAVA 日期類Date SimpleDateFormat Calendar(入門)
本文主要介紹了JAVA 日期類Date SimpleDateFormat Calendar,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
springboot中websocket簡單實(shí)現(xiàn)
本文主要介紹了springboot中websocket簡單實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
Spring七大事務(wù)傳遞機(jī)制深入分析實(shí)現(xiàn)原理
實(shí)際項(xiàng)目開發(fā)中,如果涉及到多張表操作時(shí),為了保證業(yè)務(wù)數(shù)據(jù)的一致性,大家一般都會(huì)采用事務(wù)機(jī)制,好多小伙伴可能只是簡單了解一下,遇到事務(wù)失效的情況,便會(huì)無從下手,下面這篇文章主要給大家介紹了關(guān)于Spring事務(wù)傳遞機(jī)制的相關(guān)資料,需要的朋友可以參考下2023-03-03
Spring MVC Annotation驗(yàn)證的方法
這篇文章主要介紹了Spring MVC Annotation驗(yàn)證的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03
Spring Boot MyBatis 連接數(shù)據(jù)庫配置示例
本篇文章主要介紹了Spring Boot MyBatis 連接數(shù)據(jù)庫示例的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02
JavaEE組件commons-fileupload實(shí)現(xiàn)文件上傳、下載
這篇文章主要介紹了JavaEE組件commons-fileupload實(shí)現(xiàn)文件上傳、下載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10

