详解SpringBoot+Mybatis实现动态数据源切换
作者:马小诺QAQ 发布时间:2022-06-04 02:42:02
业务背景
电商订单项目分正向和逆向两个部分:其中正向数据库记录了订单的基本信息,包括订单基本信息、订单商品信息、优惠卷信息、发票信息、账期信息、结算信息、订单备注信息、收货人信息等;逆向数据库主要包含了商品的退货信息和维修信息。数据量超过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";
}
来源:https://www.jianshu.com/p/af0801ac95aa


猜你喜欢
- Handler是什么?Handler 是一个可以实现多线程间切换的类,通过 Handler 可以轻松地将一个任务切换到 Handler 所在
- 原因分析@Anysc注解会开启一个新的线程,主线程的Request和子线程是不共享的,所以获取为null在使用springboot的自定带的
- 背景众所周知,所有被打开的系统资源,比如流、文件或者Socket连接等,都需要被开发者手动关闭,否则随着程序的不断运行,资源泄露将会累积成重
- 一、自己封装URLConnection 连接请求类 public void downloadFile1() { try{ &nb
- 今天聊一个小伙伴在星球上的提问:问题不难,解决方案也有很多,因此我决定撸一篇文章和大家仔细说说这个问题。1. 配置文件位置首先小伙伴们要明白
- 目录Sonar概述一、 搭建sona服务二、idea配置三、 配置maven的setting.xml文件四、idea中 mvn sonar:
- 理解并使用设计模式,能够培养我们良好的面向对象编程习惯,同时在实际应用中,可以如鱼得水,享受游刃有余的乐趣。Proxy是比较有用途的一种模式
- 在java项目开发过程中,使用properties文件作为配置基本上是必不可少的,很多如系统配置信息,文件上传配置信息等等都是以这种方式进行
- 知乎的广告效果一直想写,无奈最近才有时间。先看效果:肯定要自定义view了,一个类似imageView的控件,还要给它一个值用来指定广告图片
- 不少朋友自己下载了一个Android SDK,怎样在Android studio中默认的Android SDK路径呢?打开Android s
- 题目要求java实现字符串中的字母排序并输出排序后的结果分析1、创建一个字符串,赋值并将字符逐个存进数组中。String str = &qu
- 查看代码执行mybatis的sql语句File–>Settings–>Plugins 搜索 MyBatis Log Plugin
- 在本篇博文中,我们主要讲解一下 IntelliJ IDEA 安装目录中的一些核心文件的功能及用法:如上图所示,我们定位到了 IntelliJ
- 本文实例讲述了Android使用httpPost向服务器发送请求的方法。分享给大家供大家参考,具体如下:import java.util.L
- 环境: idea2020.1插件: LeetCode-editor 6.7一、IDEA安装LeetCode插件安装完成重启idea打开插件U
- 上一篇简单介绍了SurfaceView的基本使用,这次就介绍SurfaceView与多线程的混搭。SurfaceView与多线程混搭,是为了
- 以下是介绍利用List的subList方法实现对List分页,废话不多说了,直接看代码把/** *//** * List分页 &
- 在前面的文章中有介绍到我们在微信web开发过程中常常用到的 【微信JSSDK中Config配置】 ,但是我们在真正的使用中我们不仅仅只是为了
- 本文主要是通过一个银行用户取钱的实例,演示java编程多线程并发处理场景,具体如下。从一个例子入手:实现一个银行账户取钱场景的实例代码。第一
- 前言最近,新来的同事写接口,需要知道lua怎么调用C#脚本,趁这个机会也给大家分享一下。道理我也不多少,直接上干货。框架介绍本项目采用lua