詳解SpringBoot+Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換
業(yè)務(wù)背景
電商訂單項(xiàng)目分正向和逆向兩個(gè)部分:其中正向數(shù)據(jù)庫(kù)記錄了訂單的基本信息,包括訂單基本信息、訂單商品信息、優(yōu)惠卷信息、發(fā)票信息、賬期信息、結(jié)算信息、訂單備注信息、收貨人信息等;逆向數(shù)據(jù)庫(kù)主要包含了商品的退貨信息和維修信息。數(shù)據(jù)量超過(guò)500萬(wàn)行就要考慮分庫(kù)分表和讀寫(xiě)分離,那么我們?cè)谡虿僮骱湍嫦虿僮鞯臅r(shí)候,就需要?jiǎng)討B(tài)的切換到相應(yīng)的數(shù)據(jù)庫(kù),進(jìn)行相關(guān)的操作。
解決思路
現(xiàn)在項(xiàng)目的結(jié)構(gòu)設(shè)計(jì)基本上是基于MVC的,那么數(shù)據(jù)庫(kù)的操作集中在dao層完成,主要業(yè)務(wù)邏輯在service層處理,controller層處理請(qǐng)求。假設(shè)在執(zhí)行dao層代碼之前能夠?qū)?shù)據(jù)源(DataSource)換成我們想要執(zhí)行操作的數(shù)據(jù)源,那么這個(gè)問(wèn)題就解決了
環(huán)境準(zhǔn)備:
1.實(shí)體類
@Data public class Product { private Integer id; private String name; private Double price; }
2.ProductMapper
public interface ProductMapper { @Select("select * from product") public List<Product> findAllProductM(); @Select("select * from product") public List<Product> findAllProductS(); }
3.ProductService
@Service public class ProductService { @Autowired private ProductMapper productMapper; public void findAllProductM(){ // 查詢Master List<Product> allProductM = productMapper.findAllProductM(); System.out.println(allProductM); } public void findAllProductS(){ // 查詢Slave List<Product> allProductS = productMapper.findAllProductS(); System.out.println(allProductS); } }
具體實(shí)現(xiàn)
第一步:配置多數(shù)據(jù)源
首先,我們?cè)赼pplication.properties中配置兩個(gè)數(shù)據(jù)源
spring.druid.datasource.master.password=root spring.druid.datasource.master.username=root spring.druid.datasource.master.jdbc- url=jdbc:mysql://localhost:3306/product_master? useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC spring.druid.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.druid.datasource.slave.password=root spring.druid.datasource.slave.username=root spring.druid.datasource.slave.jdbc- url=jdbc:mysql://localhost:3306/product_slave? useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC spring.druid.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver 在SpringBoot的配置代碼中,我們初始化兩個(gè)數(shù)據(jù)源: @Configuration public class MyDataSourceConfiguratioin { Logger logger = LoggerFactory.getLogger(MyDataSourceConfiguratioin.class); /*** Master data source. */ @Bean("masterDataSource") @ConfigurationProperties(prefix = "spring.druid.datasource.master") DataSource masterDataSource() { logger.info("create master datasource..."); return DataSourceBuilder.create().build(); } /*** Slave data source. */ @Bean("slaveDataSource") @ConfigurationProperties(prefix = "spring.druid.datasource.slave") DataSource slaveDataSource() { logger.info("create slave datasource..."); return DataSourceBuilder.create().build(); } @Bean @Primary DataSource primaryDataSource(@Autowired @Qualifier("masterDataSource")DataSource masterDataSource, @Autowired @Qualifier("masterDataSource")DataSource slaveDataSource){ logger.info("create routing datasource..."); Map<Object, Object> map = new HashMap<>(); map.put("masterDataSource", masterDataSource); map.put("slaveDataSource", slaveDataSource); RoutingDataSource routing = new RoutingDataSource(); routing.setTargetDataSources(map); routing.setDefaultTargetDataSource(masterDataSource); return routing; } }
第二步:編寫(xiě)RoutingDataSource
然后,我們用Spring內(nèi)置的RoutingDataSource,把兩個(gè)真實(shí)的數(shù)據(jù)源代理為一個(gè)動(dòng)態(tài)數(shù)據(jù)源:
public class RoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return RoutingDataSourceContext.getDataSourceRoutingKey(); } }
第三步:編寫(xiě)RoutingDataSourceContext
用于存儲(chǔ)當(dāng)前需要切換為哪個(gè)數(shù)據(jù)源
public class RoutingDataSourceContext { // holds data source key in thread local: static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>(); public static String getDataSourceRoutingKey() { String key = threadLocalDataSourceKey.get(); return key == null ? "masterDataSource" : key; } public RoutingDataSourceContext(String key) { threadLocalDataSourceKey.set(key); } public void close() { threadLocalDataSourceKey.remove(); } }
測(cè)試(一下代碼為controller中代碼)
@GetMapping("/findAllProductM") public String findAllProductM() { String key = "masterDataSource"; RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key); productService.findAllProductM(); return "master"; } @GetMapping("/findAllProductS") public String findAllProductS() { String key = "slaveDataSource"; RoutingDataSourceContext routingDataSourceContext = new RoutingDataSourceContext(key); productService.findAllProductS(); return "slave"; }
以上代碼即可實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)切換
優(yōu)化:
以上代碼是可行的,但是,需要讀數(shù)據(jù)庫(kù)的地方,就需要加上一大段RoutingDataSourceContext
ctx = ...代碼,使用起來(lái)十分不便。以下是優(yōu)化方案
我們可以申明一個(gè)自定義注解,將以上RoutingDataSourceContext中的值,放在注解的value屬性中,
然后定義一個(gè)切面類,當(dāng)我們?cè)诜椒ㄉ蠘?biāo)注自定義注解的時(shí)候,執(zhí)行切面邏輯,獲取到注解中的值,set到RoutingDataSourceContext中,從而實(shí)現(xiàn)通過(guò)注解的方式,來(lái)動(dòng)態(tài)切換數(shù)據(jù)源
以下是代碼實(shí)現(xiàn):
注解類
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RoutingWith { String value() default "master"; }
切面類:
@Aspect @Component public class RoutingAspect { @Around("@annotation(routingWith)") public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable { String key = routingWith.value(); RoutingDataSourceContext ctx = new RoutingDataSourceContext(key); return joinPoint.proceed(); } }
改造Controller方法
@RoutingWith("masterDataSource") @GetMapping("/findAllProductM") public String findAllProductM() { productService.findAllProductM(); return "lagou"; } @RoutingWith("slaveDataSource") @GetMapping("/findAllProductS") public String findAllProductS() { productService.findAllProductS(); return "lagou"; }
到此這篇關(guān)于詳解SpringBoot+Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的文章就介紹到這了,更多相關(guān)SpringBoot+Mybatis動(dòng)態(tài)數(shù)據(jù)源切換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot動(dòng)態(tài)數(shù)據(jù)源連接測(cè)試的操作詳解
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的方法總結(jié)
- springboot配置多數(shù)據(jù)源(靜態(tài)和動(dòng)態(tài)數(shù)據(jù)源)
- SpringBoot中動(dòng)態(tài)數(shù)據(jù)源是實(shí)現(xiàn)與用途
- Springboot實(shí)現(xiàn)根據(jù)用戶ID切換動(dòng)態(tài)數(shù)據(jù)源
- 如何在Java SpringBoot項(xiàng)目中配置動(dòng)態(tài)數(shù)據(jù)源你知道嗎
- SpringBoot Mybatis動(dòng)態(tài)數(shù)據(jù)源切換方案實(shí)現(xiàn)過(guò)程
- 通過(guò)springboot+mybatis+druid配置動(dòng)態(tài)數(shù)據(jù)源
- SpringBoot整合MyBatisPlus配置動(dòng)態(tài)數(shù)據(jù)源的方法
- springboot 動(dòng)態(tài)數(shù)據(jù)源的實(shí)現(xiàn)方法(Mybatis+Druid)
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐
相關(guān)文章
第三方網(wǎng)站微信登錄java代碼實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了第三方網(wǎng)站微信登錄的java代碼實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04IntelliJ IDEA 2020.1.2激活工具下載及破解方法免費(fèi)可用至2089年(強(qiáng)烈推薦)
這篇文章主要介紹了IntelliJ IDEA 2020.1.2激活工具下載及破解方法免費(fèi)可用至2089年(強(qiáng)烈推薦),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09SpringBoot在項(xiàng)目停止(服務(wù)停止/關(guān)閉退出)之后執(zhí)行的方法
這篇文章主要給大家介紹了SpringBoot在項(xiàng)目停止(服務(wù)停止/關(guān)閉退出)之后執(zhí)行的兩種方法,實(shí)現(xiàn)DisposableBean接口和使用@PreDestroy注解,文中有詳細(xì)的代碼講解,具有一定的參考價(jià)值,需要的朋友可以參考下2023-12-12Java批量轉(zhuǎn)換文件編碼格式的實(shí)現(xiàn)方法及實(shí)例代碼
這篇文章主要介紹了Java實(shí)現(xiàn) 批量轉(zhuǎn)換文件編碼格式的方法及實(shí)例代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04深入理解SpringCloud之Eureka注冊(cè)過(guò)程分析
eureka是一種去中心化的服務(wù)治理應(yīng)用,其顯著特點(diǎn)是既可以作為服務(wù)端又可以作為服務(wù)向自己配置的地址進(jìn)行注冊(cè),這篇文章主要介紹了深入理解SpringCloud之Eureka注冊(cè)過(guò)程分析2018-05-05JAVA面試題String產(chǎn)生了幾個(gè)對(duì)象
這篇文章主要介紹了JAVA面試題 String s = new String("xyz");產(chǎn)生了幾個(gè)對(duì)象?,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07