思路
- 自定义一个注解 @DataSource,将来可以将该注解加在 service 层方法或者类上面,表示方法或者类中的所有方法都使用某一个数据源。
- 对于第一步,如果某个方法上面有 @DataSource 注解,那么就将该方法需要使用的数据源名称存入到 ThreadLocal。
- 自定义切面,在切面中解析 @DataSource 注解,当一个方法或者类上面有 @DataSource 注解的时候,将 @DataSource 注解所标记的数据源存入到 ThreadLocal 中。
- 最后,当 Mapper 执行的时候,需要 DataSource,他会自动去 AbstractRoutingDataSource 类中查找需要的数据源,我们只需要在 AbstractRoutingDataSource 中返回 ThreadLocal 中的值即可。
项目代码链接:https://github.com/1040580896/dynamic_datasourece
1. 预备知识
想要自定义动态数据源切换,得先了解一个类 AbstractRoutingDataSource
:
AbstractRoutingDataSource
是在 Spring2.0.1 中引入的(注意是 Spring2.0.1 不是 Spring Boot2.0.1,所以这其实也算是 Spring 一个非常古老的特性了), 该类充当了 DataSource 的路由中介,它能够在运行时, 根据某种 key 值来动态切换到真正的 DataSource 上。
大致的用法就是你提前准备好各种数据源,存入到一个 Map 中,Map 的 key 就是这个数据源的名字,Map 的 value 就是这个具体的数据源,然后再把这个 Map 配置到 AbstractRoutingDataSource
中,最后,每次执行数据库查询的时候,拿一个 key 出来,AbstractRoutingDataSource
会找到具体的数据源去执行这次数据库操作。
2、引入依赖
首先我们创建一个 Spring Boot 项目,引入 Web、MyBatis 以及 MySQL 依赖,项目创建成功之后,再手动加入 Druid 和 AOP 依赖,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
3、配置文件
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
ds:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: root
password: th123456
# 从库数据源
slave:
url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: root
password: th123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: tienchin
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: tru
都是 Druid 的常规配置,也没啥好说的,唯一需要注意的是我们整个配置文件的格式。ds 里边配置我们的所有数据源,每个数据源都有一个名字,master 是默认数据源的名字,不可修改,其他数据源都可以自定义名字。最后面我们还配置了 Druid 的监控功能,如果小伙伴们还不懂 Druid 的监控功能,可以查看Spring Boot 如何监控 SQL 运行情况?。
4、自定义注解和切面
注解
这个注解将来加在 Service 层的方法上,使用该注解的时候,需要指定一个数据源名称,不指定的话,默认就使用 master 作为数据源。
/**
* 将来可以加在某一个service 类上或者方法上,通过value属性来指定类或者方法应该使用哪一个数据源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
/**
* 如果一个方法上加了 @DataSource 但是却没有指定数据源名称,那么默认使用 master 数据源
* @return
*/
String value() default DataSourceType.DEFAULT_DS_NAME;
}
切面
过 AOP 来解析当前的自定义注解
这里使用了ThreadLocal
来保存需要哪种数据源
MethodSignature signature = (MethodSignature) pjb.getSignature();
AnnotationUtils.findAnnotation
工具类
ThreadLocal 的特点,简单说就是在哪个线程中存入的数据,在哪个线程才能取出来,换一个线程就取不出来了,这样可以确保多线程环境下的数据安全。
@Component
@Aspect
public class DataSourceAspect {
/**
* 切点
*
* @annotation(com.th.annotation.DataSource 表示方法上有 @DataSource 注解就将方法拦截下来
* @within(com.th.annotation.DataSource) 表示如果类上面有 @DataSource 注解,就将类中的方法拦截下来
*/
@Pointcut("@annotation(com.th.annotation.DataSource) || @within(com.th.annotation.DataSource)")
public void pc() {
}
@Around("pc()")
public Object around(ProceedingJoinPoint pjb) {
//获取方法上面的注解
DataSource dataSource = getDataSourece(pjb);
if (dataSource != null) {
//数据源的名称
String value = dataSource.value();
DynmaicDataSourceContextHolder.setDataSourceType(value);
}
try {
return pjb.proceed();
} catch (Throwable e) {
e.printStackTrace();
} finally {
DynmaicDataSourceContextHolder.clearDataSourceType();
}
return null;
}
private DataSource ge