详解SpringBoot+Mybatis实现动态数据源切换
业务背景
电商订单项目分正向和逆向两个部分:其中正向数据库记录了订单的基本信息,包括订单基本信息、订单商品信息、优惠卷信息、发票信息、账期信息、结算信息、订单备注信息、收货人信息等;逆向数据库主要包含了商品的退货信息和维修信息。数据量超过500万行就要考虑分库分表和读写分离,那么我们在正向操作和逆向操作的时候,就需要动态的切换到相应的数据库,进行相关的操作。
解决思路
现在项目的结构设计基本上是基于MVC的,那么数据库的操作集中在dao层完成,主要业务逻辑在service层处理,controller层处理请求。假设在执行dao层代码之前能够将数据源(DataSource)换成我们想要执行操作的数据源,那么这个问题就解决了
环境准备:
1.实体类
@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); } }
具体实现
第一步:配置多数据源
首先,我们在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动态数据源切换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!