spring實現(xiàn)動態(tài)切換、添加數(shù)據(jù)源及源碼分析
前言
對于數(shù)據(jù)量在1千萬,單個mysql數(shù)據(jù)庫就可以支持,但是如果數(shù)據(jù)量大于這個數(shù)的時候,例如1億,那么查詢的性能就會很低。此時需要對數(shù)據(jù)庫做水平切分,常見的做法是按照用戶的賬號進(jìn)行hash,然后選擇對應(yīng)的數(shù)據(jù)庫。
最近公司項目需求,由于要兼容老系統(tǒng)的數(shù)據(jù)庫結(jié)構(gòu),需要搭建一個 可以動態(tài)切換、添加數(shù)據(jù)源的后端服務(wù)。
參考了過去的項目,通過配置多個SqlSessionFactory 來實現(xiàn)多數(shù)據(jù)源,這么做的話,未免過于笨重,而且無法實現(xiàn)動態(tài)添加數(shù)據(jù)源這個需求
通過 spring AbstractRoutingDataSource 為我們抽象了一個 DynamicDataSource 解決這一問題
簡單分析下 AbstractRoutingDataSource 的源碼
targetDataSources 就是我們的多個數(shù)據(jù)源,在初始化的時候會調(diào)用afterPropertiesSet(),去解析我們的數(shù)據(jù)源 然后 put 到 resolvedDataSources
實現(xiàn)了 DataSource 的 getConnection(); 我們看看 determineTargetDataSource(); 做了什么
通過下面的 determineCurrentLookupKey();(這個方法需要我們實現(xiàn)) 返回一個key,然后從 resolvedDataSources (其實也就是 targetDataSources) 中 get 一個數(shù)據(jù)源,實現(xiàn)了每次調(diào)用 getConnection(); 打開連接 切換數(shù)據(jù)源,如果想動態(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 自動配置并注冊 * * @return data source */ @Bean("db0") @Primary @ConfigurationProperties(prefix = "datasource.db0") public DataSource dataSource0() { return DruidDataSourceBuilder.create().build(); } /** * DataSource 自動配置并注冊 * * @return data source */ @Bean("db1") @ConfigurationProperties(prefix = "datasource.db1") public DataSource dataSource1() { return DruidDataSourceBuilder.create().build(); } /** * 注冊動態(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(); // 必須將動態(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(); } }
動態(tài) 添加、切換數(shù)據(jù)源
/** * 動態(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); } /** * 動態(tài)增加數(shù)據(jù)源 * * @param map 數(shù)據(jù)源屬性 * @return */ public synchronized boolean addDataSource(Map<String, String> map) { try { Connection connection = null; // 排除連接不上的錯誤 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 或者 手動 DynamicDataSourceContextHolder.setDataSourceKey(String key) 切換數(shù)據(jù)源
需要注意的:當(dāng)我們開啟了事務(wù)之后,是無法在去切換數(shù)據(jù)源的
本文項目源碼:https://gitee.com/yintianwen7/spring-dynamic-datasource (本地下載)
參考文獻(xiàn):https://github.com/helloworlde/SpringBoot-DynamicDataSource
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
新手小白學(xué)JAVA 日期類Date SimpleDateFormat Calendar(入門)
本文主要介紹了JAVA 日期類Date SimpleDateFormat Calendar,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10springboot中websocket簡單實現(xiàn)
本文主要介紹了springboot中websocket簡單實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Spring七大事務(wù)傳遞機(jī)制深入分析實現(xiàn)原理
實際項目開發(fā)中,如果涉及到多張表操作時,為了保證業(yè)務(wù)數(shù)據(jù)的一致性,大家一般都會采用事務(wù)機(jī)制,好多小伙伴可能只是簡單了解一下,遇到事務(wù)失效的情況,便會無從下手,下面這篇文章主要給大家介紹了關(guān)于Spring事務(wù)傳遞機(jī)制的相關(guān)資料,需要的朋友可以參考下2023-03-03Spring Boot MyBatis 連接數(shù)據(jù)庫配置示例
本篇文章主要介紹了Spring Boot MyBatis 連接數(shù)據(jù)庫示例的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-02-02JavaEE組件commons-fileupload實現(xiàn)文件上傳、下載
這篇文章主要介紹了JavaEE組件commons-fileupload實現(xiàn)文件上傳、下載,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10