SpringBoot多數(shù)據(jù)源解決方案:dynamic-datasource-spring-boot-starter
概要
自己閑暇時(shí)間想實(shí)現(xiàn)一個(gè)多租戶平臺(tái),需要實(shí)現(xiàn)數(shù)據(jù)分離,動(dòng)態(tài)配置生成數(shù)據(jù)源,憑著自己的感覺搭建了一套簡(jiǎn)單的方案
dynamic-datasource-spring-boot-starter 是一個(gè)用于在 Spring Boot 項(xiàng)目中實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的工具,這里主要通過這個(gè)工具實(shí)現(xiàn)在系統(tǒng)運(yùn)行中創(chuàng)建數(shù)據(jù)庫(kù),生成數(shù)據(jù)庫(kù)連接。
整體架構(gòu)構(gòu)想
- 【創(chuàng)建數(shù)據(jù)源】使用DefaultDataSourceCreator類下的createDataSource方法創(chuàng)建數(shù)據(jù)源。
- 【存儲(chǔ)數(shù)據(jù)源】使用DynamicRoutingDataSource方法的addDataSource方法以鍵值對(duì)存儲(chǔ)數(shù)據(jù)源。
- 【切換數(shù)據(jù)源】使用**@DS**注解動(dòng)態(tài)切換數(shù)據(jù)源。
操作步驟
創(chuàng)建數(shù)據(jù)源
創(chuàng)建數(shù)據(jù)源方法大致如下,主要就是創(chuàng)建數(shù)據(jù)庫(kù)的參數(shù)配置,讓生成器生成數(shù)據(jù)源。
//默認(rèn)使用Mysql8.0,并且連接mysql數(shù)據(jù)庫(kù)表,通過這個(gè)連接創(chuàng)建新的數(shù)據(jù)庫(kù)數(shù)據(jù) DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; // 數(shù)據(jù)源參數(shù)配置 DataSourceProperty dataSourceProperty = new DataSourceProperty(); //固定使用mysql8 String dbClassType = "com.mysql.cj.jdbc.Driver"; String dbUrl = String.format("jdbc:mysql://%s:%s/%s?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8", dbIp, dbPort, dbName); //這里使用IP進(jìn)行區(qū)分 dataSourceProperty.setPoolName(dbIp); dataSourceProperty.setUsername(userName); dataSourceProperty.setPassword(passWord); dataSourceProperty.setUrl(dbUrl); dataSourceProperty.setDriverClassName(dbClassType); // 數(shù)據(jù)源全部懶加載,避免一次性聲明過多連接 dataSourceProperty.setLazy(true); // 生成數(shù)據(jù)源 DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty); //最后存儲(chǔ)到ds中,后面可以用個(gè)不同的key去連接不同的數(shù)據(jù)源 ds.addDataSource(dbIp,dataSource);
后面在調(diào)用不同數(shù)據(jù)庫(kù)的時(shí)候只需要使用**@DS**注解,代碼如下:
/** * 調(diào)用不同的數(shù)據(jù)連接生成新的數(shù)據(jù)庫(kù) * @param onlySignIp * @param dbName * @return */ @DS(value = "#onlySignIp") public int createDataSource(String onlySignIp,@Param("dbName")String dbName);
切換數(shù)據(jù)源
一開始我覺得這樣就可以直接在系統(tǒng)中自定義創(chuàng)建數(shù)據(jù)庫(kù),dynamic-datasource底層在初始化加載的時(shí)候會(huì)生成一條責(zé)任鏈,一共為三個(gè)節(jié)點(diǎn)分別為DsHeaderProcessor、DsSessionProcessor、DsSpelExpressionProcessor分別對(duì)應(yīng)#header、#session和spel表達(dá)式,源碼如下:
@Bean @ConditionalOnMissingBean public DsProcessor dsProcessor(BeanFactory beanFactory) { DsHeaderProcessor headerProcessor = new DsHeaderProcessor(); DsSessionProcessor sessionProcessor = new DsSessionProcessor(); DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor(); spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory)); headerProcessor.setNextProcessor(sessionProcessor); sessionProcessor.setNextProcessor(spelExpressionProcessor); return headerProcessor; }
在這里根據(jù)底層代碼理論上來說是不會(huì)應(yīng)該有問題的,好像是用它自定義的責(zé)任鏈不行,這里我就自己重寫了這條責(zé)任鏈(也就自己復(fù)制源代碼將他注入到spring容器中),然后在重寫DsSpelExpressionProcessor,通過spel表達(dá)式來選擇數(shù)據(jù)源。
public class DsSpelExpressionProcessor extends DsProcessor { /** * 參數(shù)發(fā)現(xiàn)器 */ private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); /** * Express語法解析器 */ private static final ExpressionParser PARSER = new SpelExpressionParser(); /** * 解析上下文的模板 * 對(duì)于默認(rèn)不設(shè)置的情況下,從參數(shù)中取值的方式 #param1 * 設(shè)置指定模板 ParserContext.TEMPLATE_EXPRESSION 后的取值方式: #{#param1} * issues: https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/199 */ private ParserContext parserContext = new ParserContext() { @Override public boolean isTemplate() { return false; } @Override public String getExpressionPrefix() { return null; } @Override public String getExpressionSuffix() { return null; } }; private BeanResolver beanResolver; @Override public boolean matches(String key) { return true; } @Override public String doDetermineDatasource(MethodInvocation invocation, String key) { Object[] arguments = invocation.getArguments(); StandardEvaluationContext context = new StandardEvaluationContext(arguments); //默認(rèn)使用第一個(gè)參數(shù) String replace = key.replace("#", ""); context.setVariable(replace,arguments[0]); final Object value = PARSER.parseExpression(key, parserContext).getValue(context); return value == null ? null : value.toString(); } public void setParserContext(ParserContext parserContext) { this.parserContext = parserContext; } public void setBeanResolver(BeanResolver beanResolver) { this.beanResolver = beanResolver; } }
這樣配合@DS注解就可以實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換了。
后續(xù)問題
在需要切換數(shù)據(jù)源的情況下,將對(duì)應(yīng)數(shù)據(jù)源Key信息作為Mapper第一個(gè)參數(shù)傳遞
例如:
/** * 調(diào)用不同的數(shù)據(jù)連接生成新的數(shù)據(jù)庫(kù) * @param onlySignIp * @param dbName * @return */ @DS(value = "#onlySignIp") public int createDataSource(String onlySignIp,@Param("dbName")String dbName);
- 在切換數(shù)據(jù)源的情況下必須要加上**@DS**注解,而且還必須將對(duì)應(yīng)數(shù)據(jù)源的Key作為第一個(gè)參數(shù)進(jìn)行傳入
小結(jié)
通過對(duì) dynamic-datasource-spring-boot-starter 的剖析,我們簡(jiǎn)單的實(shí)現(xiàn)了自定義創(chuàng)建數(shù)據(jù)源,切換數(shù)據(jù)源的操作,后續(xù)問題如果大家有對(duì)應(yīng)的解決方法,希望評(píng)論告知
到此這篇關(guān)于SpringBoot多數(shù)據(jù)源解決方案:dynamic-datasource-spring-boot-starter的文章就介紹到這了,更多相關(guān)SpringBoot多數(shù)據(jù)源內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot利用dynamic-datasource-spring-boot-starter解決多數(shù)據(jù)源問題
- springboot項(xiàng)目如何配置多數(shù)據(jù)源
- springboot多數(shù)據(jù)源實(shí)現(xiàn)方式
- SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源配置的示例詳解
- SpringBoot框架DataSource多數(shù)據(jù)源配置方式
- SpringBoot 多數(shù)據(jù)源及事務(wù)解決方案小結(jié)
- springboot Jpa多數(shù)據(jù)源(不同庫(kù))配置過程
- 淺析SpringBoot多數(shù)據(jù)源實(shí)現(xiàn)方案
相關(guān)文章
springboot與vue實(shí)現(xiàn)簡(jiǎn)單的CURD過程詳析
這篇文章主要介紹了springboot與vue實(shí)現(xiàn)簡(jiǎn)單的CURD過程詳析,圍繞springboot與vue的相關(guān)資料展開實(shí)現(xiàn)CURD過程的過程介紹,需要的小伙伴可以參考一下2022-01-01詳解簡(jiǎn)單基于spring的redis配置(單機(jī)和集群模式)
這篇文章主要介紹了詳解簡(jiǎn)單基于spring的redis配置(單機(jī)和集群模式),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02Spring Junit測(cè)試找不到SpringJUnit4ClassRunner.class的解決
這篇文章主要介紹了Spring Junit測(cè)試找不到SpringJUnit4ClassRunner.class的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04java使用gzip實(shí)現(xiàn)文件解壓縮示例
這篇文章主要介紹了java使用gzip實(shí)現(xiàn)文件解壓縮示例,需要的朋友可以參考下2014-03-03Java套接字(Socket)網(wǎng)絡(luò)編程入門
這篇文章主要介紹了Java套接字(Socket)網(wǎng)絡(luò)編程入門,Socket可以理解為是對(duì)TCP/IP協(xié)議的抽象,需要的朋友可以參考下2015-10-10Spring?Boot中使用Spring?Retry重試框架的操作方法
這篇文章主要介紹了Spring?Retry?在SpringBoot?中的應(yīng)用,介紹了RetryTemplate配置的時(shí)候,需要設(shè)置的重試策略和退避策略,需要的朋友可以參考下2022-04-04MyBatis-Plus?中?typeHandler?的使用實(shí)例詳解
本文介紹了在MyBatis-Plus中如何使用typeHandler處理json格式字段和自定義typeHandler,通過使用JacksonTypeHandler,可以簡(jiǎn)單實(shí)現(xiàn)將實(shí)體類字段轉(zhuǎn)換為json格式存儲(chǔ),感興趣的朋友跟隨小編一起看看吧2024-10-10