详细聊聊SpringBoot中动态切换数据源的方法
作者:Nicksxs 发布时间:2023-11-24 04:07:49
其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便。
首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置
第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 这个类,他的determineCurrentLookupKey方法会被调用来获得用来决定选择那个数据源的对象,也就是 lookupKey,也可以通过这个类看到就是通过这个 lookupKey 来路由找到数据源。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
if (DatabaseContextHolder.getDatabaseType() != null) {
return DatabaseContextHolder.getDatabaseType().getName();
}
return DatabaseType.MASTER1.getName();
}
}
而如何使用这个 lookupKey 呢,就涉及到我们的 DataSource 配置了,原来就是我们可以直接通过spring 的 jdbc 配置数据源,像这样
现在我们要使用 Druid 作为数据源了,然后配置 DynamicDataSource的参数,通过 key 来选择对应的 DataSource,也就是下面配的 master1 和 master2
<bean id="master1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close"
p:driverClassName="com.mysql.cj.jdbc.Driver"
p:url="${master1.demo.datasource.url}"
p:username="${master1.demo.datasource.username}"
p:password="${master1.demo.datasource.password}"
p:initialSize="5"
p:minIdle="1"
p:maxActive="10"
p:maxWait="60000"
p:timeBetweenEvictionRunsMillis="60000"
p:minEvictableIdleTimeMillis="300000"
p:validationQuery="SELECT 'x'"
p:testWhileIdle="true"
p:testOnBorrow="false"
p:testOnReturn="false"
p:poolPreparedStatements="false"
p:maxPoolPreparedStatementPerConnectionSize="20"
p:connectionProperties="config.decrypt=true"
p:filters="stat,config"/>
<bean id="master2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close"
p:driverClassName="com.mysql.cj.jdbc.Driver"
p:url="${master2.demo.datasource.url}"
p:username="${master2.demo.datasource.username}"
p:password="${master2.demo.datasource.password}"
p:initialSize="5"
p:minIdle="1"
p:maxActive="10"
p:maxWait="60000"
p:timeBetweenEvictionRunsMillis="60000"
p:minEvictableIdleTimeMillis="300000"
p:validationQuery="SELECT 'x'"
p:testWhileIdle="true"
p:testOnBorrow="false"
p:testOnReturn="false"
p:poolPreparedStatements="false"
p:maxPoolPreparedStatementPerConnectionSize="20"
p:connectionProperties="config.decrypt=true"
p:filters="stat,config"/>
<bean id="dataSource" class="com.nicksxs.springdemo.config.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- master -->
<entry key="master1" value-ref="master1"/>
<!-- slave -->
<entry key="master2" value-ref="master2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="master1"/>
</bean>
现在就要回到头上,介绍下这个DatabaseContextHolder,这里使用了 ThreadLocal 存放这个 DatabaseType,为啥要用这个是因为前面说的我们想要让接口层面去配置不同的数据源,要把持相互隔离不受影响,就使用了 ThreadLocal,关于它也可以看我前面写的一篇文章聊聊传说中的 ThreadLocal,而 DatabaseType 就是个简单的枚举
public class DatabaseContextHolder {
public static final ThreadLocal<DatabaseType> databaseTypeThreadLocal = new ThreadLocal<>();
public static DatabaseType getDatabaseType() {
return databaseTypeThreadLocal.get();
}
public static void putDatabaseType(DatabaseType databaseType) {
databaseTypeThreadLocal.set(databaseType);
}
public static void clearDatabaseType() {
databaseTypeThreadLocal.remove();
}
}
public enum DatabaseType {
MASTER1("master1", "1"),
MASTER2("master2", "2");
private final String name;
private final String value;
DatabaseType(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
public static DatabaseType getDatabaseType(String name) {
if (MASTER2.name.equals(name)) {
return MASTER2;
}
return MASTER1;
}
}
这边可以看到就是通过动态地通过putDatabaseType设置lookupKey来进行数据源切换,要通过接口注解配置来进行设置的话,我们就需要一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
这个注解可以配置在我的接口方法上,比如这样
public interface StudentService {
@DataSource("master1")
public Student queryOne();
@DataSource("master2")
public Student queryAnother();
}
通过切面来进行数据源的设置
@Aspect
@Component
@Order(-1)
public class DataSourceAspect {
@Pointcut("execution(* com.nicksxs.springdemo.service..*.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void before(JoinPoint point)
{
Object target = point.getTarget();
System.out.println(target.toString());
String method = point.getSignature().getName();
System.out.println(method);
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz[0].getMethod(method, parameterTypes);
System.out.println("method"+ m.getName());
if (m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
System.out.println("dataSource:"+data.value());
DatabaseContextHolder.putDatabaseType(DatabaseType.getDatabaseType(data.value()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
@After("pointCut()")
public void after() {
DatabaseContextHolder.clearDatabaseType();
}
}
通过接口判断是否带有注解跟是注解的值,DatabaseType 的配置不太好,不过先忽略了,然后在切点后进行清理
这是我 master1 的数据,
master2 的数据
然后跑一下简单的 demo,
@Override
public void run(String...args) {
LOGGER.info("run here");
System.out.println(studentService.queryOne());
System.out.println(studentService.queryAnother());
}
看一下运行结果
其实这个方法应用场景不止可以用来迁移数据库,还能实现精细化的读写数据源分离之类的,算是做个简单记录和分享。
来源:https://nicksxs.me/2021/09/26/聊一下-SpringBoot-中动态切换数据源的方法/
猜你喜欢
- 走马灯是一种常见的效果,本文讲一下如何用 PageView 在 Flutter 里实现一个走马灯, 效果如下,当前页面的高度比其它页面高,切
- 英文设置加粗可以在xml里面设置: <SPAN style="FONT-SIZE: 18px">androi
- Json的简介JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个
- 最近要给一个 Winform 项目添加功能,需要一个能显示进度条的弹窗,还要求能够中止任务,所以就做了一个,在此做个记录总结。虽然用的是比较
- 图像滤波在opencv中可以有多种实现形式自定义滤波如使用3×3的掩模:对图像进行处理.使用函数filter2D()实现#include&l
- 概述什么是动态编程?动态编程解决什么问题?Java中如何使用?什么原理?如何改进?(需要我们一起探索,由于自己也是比较菜,一般深入不到这个程
- 首先写一个测试文件然后点击IDEA右侧的maven,然后选择package,之后点击上面运行或者直接双击即可,等下方控制台构建成功即可:然后
- Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,
- 用了MyBatis的同行,应该见过foreach,它一般是这样用的:<select id="foreachTest"
- 面试中会经常遇到手撕代码的情况,而求TopK的是经常遇到的题目。下面我就用Java来实现。主要通过两种方法实现,快排思想以及堆排序的思想,两
- 下载maven 解压路径: 打开环境变量:右键此电脑-属性-高级系统设置-高级-环境变量添加以下系统变量:测试:win+
- 在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的具体执行过程,我画了一幅图。Java 源代码文件经过编译器编译后生成字节码文
- spring mvc中的@PathVariable是用来获得请求url中的动态参数的,十分方便,复习下: @Controller publ
- 前言上一篇我们介绍了使用 sqflite 这个数据库工具在 Flutter 的应用中建立本地数据库的实例应用。了解过数据库的同学应该会知道,
- 我们在学习接口的时候。能够在里面做一些方法的调用。不过今天所要讲的JDBC,虽然也是连接数据库的一种接口,不过与类接口有着很大的区别,大家要
- 1. 为什么要进行参数校验在后端进行工作时,需要接收前端传来的数据去数据库查询,但是如果有些数据过于离谱,我们就可以直接把它pass掉,不让
- 本文实例为大家分享了java实现微信扫码支付的具体代码,供大家参考,具体内容如下1、maven项目的pom.xml中添加如下jar包:<
- 1. 在原有工程目录右键-> new ->Module->:2. 选择library:3. 一路next,最后finish
- 系列文章已完成,目录如下:jdk-logging log4j logback日志系统实现机制原理介绍commons-lo
- Java Collection API提供了一些列的类和接口来帮助我们存储和管理对象集合。其实Java中的集合工作起来像是一个数组,不过集合