SpringBoot多数据源切换(二)(依旧超级简单)
背景:主从架构下,数据库的读写分离1。依赖dependenciesdependencygroupIdorg。springframework。bootgroupIdspringbootstarterwebartifactIddependencydependencygroupIdorg。projectlombokgroupIdlombokartifactIdoptionaltrueoptionaldependencydependencygroupIdorg。springframework。bootgroupIdspringbootstartertestartifactIdscopetestscopedependencydependencygroupIdcom。baomidougroupIdmybatisplusbootstarterartifactIdversion3。5。3versiondependencydependencygroupIdorg。springframework。bootgroupIdspringbootstarteraopartifactIddependencydependencygroupIdcom。alibabagroupIddruidspringbootstarterartifactIdversion1。2。16versiondependencydependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIdversion8。0。29versiondependencydependencies2。配置数据源spring:datasource:druid:type:com。alibaba。druid。pool。DruidDataSourcemaster:url:jdbc:mysql:127。0。0。1:3307user1?useUnicodetruecharacterEncodingutf8useSSLfalseusername:rootpassword:rootdriverclassname:com。mysql。cj。jdbc。Driverslave1:enabled:trueurl:jdbc:mysql:127。0。0。1:3308user1?useUnicodetruecharacterEncodingutf8useSSLfalseusername:rootpassword:rootdriverclassname:com。mysql。cj。jdbc。Driverotmstariff:enabled:falseurl:jdbc:mysql:127。0。0。1:3306user1?useUnicodetruecharacterEncodingutf8useSSLfalseusername:rootpassword:rootdriverclassname:com。mysql。cj。jdbc。Driver3。注册数据源
1)创建一个数据源枚举类publicenumDataSourceType{主库MASTER,从库SLAVE1,SLAVE2}
2)我们切换数据库所需要的bean全部交给spring容器中ConfigurationpublicclassDynamicDataSourceConfig{BeanQualifier(masterDataSource)ConfigurationProperties(spring。datasource。druid。master)publicDataSourcemasterDataSource(){returnDruidDataSourceBuilder。create()。build();}Qualifier(slave1DataSource)BeanConfigurationProperties(spring。datasource。druid。slave1)根据配置文件enabled属性,判断该配置是否生效ConditionalOnProperty(prefixspring。datasource。druid。slave1,nameenabled,havingValuetrue)publicDataSourceslave1DataSource(){returnDruidDataSourceBuilder。create()。build();}Qualifier(slave2DataSource)BeanConfigurationProperties(spring。datasource。druid。slave2)ConditionalOnProperty(prefixspring。datasource。druid。slave2,nameenabled,havingValuetrue)publicDataSourceslave2DataSource(){returnDruidDataSourceBuilder。create()。build();}Bean(namedynamicDataSource)PrimarypublicDynamicDataSourcedataSource(DataSourcemasterDataSource){MapObject,ObjecttargetDataSourcesnewHashMap();targetDataSources。put(DataSourceType。MASTER。name(),masterDataSource);setDataSource(targetDataSources,DataSourceType。SLAVE1。name(),slave1DataSource);setDataSource(targetDataSources,DataSourceType。SLAVE2。name(),slave2DataSource);DynamicDataSourcedynamicDataSourcenewDynamicDataSource();dynamicDataSource。setTargetDataSources(targetDataSources);dynamicDataSource。setDefaultTargetDataSource(masterDataSource);returndynamicDataSource;}设置数据源paramtargetDataSources备选数据源集合paramsourceName数据源名称parambeanNamebean名称publicvoidsetDataSource(MapObject,ObjecttargetDataSources,StringsourceName,StringbeanName){try{DataSourcedataSourceSpringUtils。getBean(beanName);targetDataSources。put(sourceName,dataSource);}catch(Exceptione){}}}4。切换数据源publicclassDynamicDataSourceContextHolder{线程安全privatestaticfinalThreadLocalStringCONTEXTHOLDERnewThreadLocal();设置数据源变量paramdataSourceEnum数据源变量publicstaticvoidsetDataSourceType(Stringtype){CONTEXTHOLDER。set(type);}获取数据源变量return数据源变量publicstaticStringgetDataSourceType(){returnCONTEXTHOLDER。get();}清理数据源return数据源变量publicstaticvoidclearDataSourceType(){CONTEXTHOLDER。remove();}}5。设置数据源
新建DynamicDataSource类继承AbstractRoutingDataSource类,并实现determineCurrentLookupKey方法,该方法是指定当前默认数据源的方法,该类是实现动态切换数据源的关键publicclassDynamicDataSourceextendsAbstractRoutingDataSource{OverrideprotectedObjectdetermineCurrentLookupKey(){returnDynamicDataSourceContextHolder。getDataSourceType();}}6。自定义多数据源切换注解Target({ElementType。METHOD,ElementType。TYPE})Retention(RetentionPolicy。RUNTIME)DocumentedpublicinterfaceDataSource{DataSourceTypevalue()defaultDataSourceType。MASTER;}7。AOP拦截器的实现Slf4jAspectComponentpublicclassDataSourceAspect{Pointcut(annotation(com。example。datasourceprimordialdemo2。datasource。annotation。DataSource))publicvoiddoPointCut(){}Around(doPointCut())publicObjectaround(ProceedingJoinPointpointcut)throwsThrowable{MethodSignaturesignature(MethodSignature)pointcut。getSignature();Methodmethodsignature。getMethod();DataSourcedataSourcemethod。getAnnotation(DataSource。class);if(Objects。nonNull(dataSource)){DynamicDataSourceContextHolder。setDataSourceType(dataSource。value()。name());}try{returnpointcut。proceed();}finally{销毁数据源在执行方法之后DynamicDataSourceContextHolder。clearDataSourceType();}}}8。启动类修改SpringBootApplication(excludeDataSourceAutoConfiguration。class)9。使用
此处为了测试,直接放在controller使用GetMapping(0)DataSource(DataSourceType。MASTER)publicResponseEntityListUserquery(){returnResponseEntity。ok(this。userService。query());}GetMapping(1)DataSource(DataSourceType。SLAVE1)publicResponseEntityListUserquery2(){returnResponseEntity。ok(this。userService。query());}GetMapping(2)DataSource(DataSourceType。SLAVE2)publicResponseEntityListUserquery3(){returnResponseEntity。ok(this。userService。query());}为了区分数据不一样,数据库未做主从同步
master的数据
slave1的数据
slave2的数据
5。用postman进行测试
获取master的数据
获取slave1的数据
获取slave2的数据
完成~~~源码:〔gitee〕(https:gitee。comTZWwdatasourceprimordialdemo2)〔github〕(https:github。com1137854811datasourceprimordialdemo2)
注:为什么配置了三个数据源,是为了展示我在做的过程中遇到的一个问题
刚开始我是按照网上查到的方式注册数据源
这样做,在全部数据源都注入的时候没有问题,当我在配置中心停掉其中一个数据源时就会出现问题
3)问题(在masterDataSource添加Primary又会出现其他的错误)Parameter1ofmethoddataSourceincom。example。datasourceprimordialdemo2。datasource。config。DynamicDataSourceConfigrequiredasinglebean,but2werefound:masterDataSource:definedbymethodmasterDataSourceinclasspathresource〔comexampledatasourceprimordialdemo2datasourceconfigDynamicDataSourceConfig。class〕slave2DataSource:definedbymethodslave2DataSourceinclasspathresource〔comexampledatasourceprimordialdemo2datasourceconfigDynamicDataSourceConfig。class〕Action:ConsidermarkingoneofthebeansasPrimary,updatingtheconsumertoacceptmultiplebeans,orusingQualifiertoidentifythebeanthatshouldbeconsumed
4。解决办法(此方式只针对我代码的解决办法,可能会有其他问题导致此报错,请再寻找其他方法)
大佬们如果有其他方式请在评论区告知,万分感谢
我是Tz,想把我遇到的问题都分享给你,想看更多精彩内容,请关注我的wx公众号zhuangtian