详解SpringBoot+Mybatis实现动态数据源切换

网友投稿 308 2022-10-26


详解SpringBoot+Mybatis实现动态数据源切换

业务背景

电商订单项目分正向和逆向两个部分:其中正向数nTVmip据库记录了订单的基本信息,包括订单基本信息、订单商品信息、优惠卷信息、发票信息、账期信息、结算信息、订单备注信息、收货人信息等;逆向数据库主要包含了商品的退货信息和维修信息。数据量超过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 findAllProductM();

@Select("select * from product")

public List findAllProductS();

}

3.ProductService

@Service

public class ProductService {

@Autowired

private ProductMapper productMapper;

public void findAllProductM(){

// 查询Master

List allProductM = productMapper.findAllProductM();

System.out.println(allProductM);

}

public void findAllProductS(){

// 查询Slave

List 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 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 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";

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:实体框架(Entity Framework)简介
下一篇:Entity Framework 的事务 DbTransaction
相关文章

 发表评论

评论列表