詳解SpringBoot+Mybatis實現動態數據源切換
電商訂單項目分正向和逆向兩個部分:其中正向數據庫記錄了訂單的基本信息,包括訂單基本信息、訂單商品信息、優惠卷信息、發票信息、賬期信息、結算信息、訂單備注信息、收貨人信息等;逆向數據庫主要包含了商品的退貨信息和維修信息。數據量超過500萬行就要考慮分庫分表和讀寫分離,那么我們在正向操作和逆向操作的時候,就需要動態的切換到相應的數據庫,進行相關的操作。
解決思路現在項目的結構設計基本上是基于MVC的,那么數據庫的操作集中在dao層完成,主要業務邏輯在service層處理,controller層處理請求。假設在執行dao層代碼之前能夠將數據源(DataSource)換成我們想要執行操作的數據源,那么這個問題就解決了
環境準備:1.實體類
@Datapublic 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); } }具體實現
第一步:配置多數據源
首先,我們在application.properties中配置兩個數據源
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的配置代碼中,我們初始化兩個數據源:@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; }}
第二步:編寫RoutingDataSource然后,我們用Spring內置的RoutingDataSource,把兩個真實的數據源代理為一個動態數據源:
public class RoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return RoutingDataSourceContext.getDataSourceRoutingKey(); } }
第三步:編寫RoutingDataSourceContext用于存儲當前需要切換為哪個數據源
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(); }}
測試(一下代碼為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';}
以上代碼即可實現數據源動態切換
優化:以上代碼是可行的,但是,需要讀數據庫的地方,就需要加上一大段RoutingDataSourceContext
ctx = ...代碼,使用起來十分不便。以下是優化方案
我們可以申明一個自定義注解,將以上RoutingDataSourceContext中的值,放在注解的value屬性中,
然后定義一個切面類,當我們在方法上標注自定義注解的時候,執行切面邏輯,獲取到注解中的值,set到RoutingDataSourceContext中,從而實現通過注解的方式,來動態切換數據源
以下是代碼實現:
注解類
@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'; }
到此這篇關于詳解SpringBoot+Mybatis實現動態數據源切換的文章就介紹到這了,更多相關SpringBoot+Mybatis動態數據源切換內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!
相關文章: