【Java】SpringBoot中实现多数据源切换
前言
在日常项目开发中,某些需求会让不同的数据落实到不同的数据库,也或许是不同的页面需要不同数据库中的数据,在这种场景下,我们可以使用多数据源的配置来完成,通过在springboot中的yml
文件配置多个数据源方式即可完成该需求,接下来看一下我的案例。
这篇案例采用自定义注解
+ aop切面
的方式来完成动态数据源的切换,关于自定义注解的使用可以去我的另一篇文章去查看怎样使用。
自定义注解使用方式:【Java】自定义注解和AOP切面的使用_java自定义切面_保加利亚的风的博客-CSDN博客
使用@DS自定义注解来完成动态数据源的切换,如果不指定则使用默认的数据源。
@GetMapping("/listDb1")
public List<Db1User> listDb1() {
return db1UserService.list();
}
@GetMapping("/listDb2")
@DS("db2")
public List<Db2User> listDb2() {
return db2UserService.list();
}
准备工作
数据库(Mysql):两个数据库,分别是db1_datasource
和db2_datasource
表:每个数据库都有一个user表,分别是db1_user
和db2_user
数据:db1_user
表中数据为4条
,db2_user
表中数据为2条
开始
项目结构图
2 application.yml
配置文件
server:
port: 8111
mybatis:
mapper-locations: classpath:mapper/*.xml #扫描xml文件路径
configuration:
map-underscore-to-camel-case: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志打印
db1: #数据源1
datasource:
jdbc-url: jdbc:mysql://localhost:3306/db1_datasource?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
db2: #数据源2
datasource:
jdbc-url: jdbc:mysql://localhost:3306/db2_datasource?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
注意:这里采用的是Hikari
连接池,因为Hikari没有url
属性,但是有一个jdbcUrl
属性,如果配置多数据源,则数据库连接的url
需要替换为jdbc-url
3 创建 DatasourceConfig
类,读取配置项,加载ioc容器
@Configuration
@EnableConfigurationProperties
public class DatasourceConfig {
//获取前缀为`db1.datasource`的配置项
@Bean
@ConfigurationProperties(prefix = "db1.datasource")
public HikariConfig db1Config() {
return new HikariConfig();
}
//获取前缀为`db2.datasource`的配置项
@Bean
@ConfigurationProperties(prefix = "db2.datasource")
public HikariConfig db2Config() {
return new HikariC1);
return dtaSource;
}
}
4 创建一个DynamicDataSourceContextHolder
类,这个类主要是来维护我们的线程变量的,基于ThreadLocal
方式来进行数据源的存储和获取。
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal维护变量
*/
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
/**
* 存放所有数据源名称
*/
public static List<String> dataSources = new ArrayList<>();
/**
* 设置当前数据源类型
*/
public static void setDataSourceType(String dataSourceType) {
threadLocal.set(dataSourceType);
}
/**
* 获取当前threadLocal变量的数据源
*/
public static String getDataSourceType() {
return threadLocal.get();
}
/**
* 清空当前threadLocal变量中所有数据源
*/
public static void clearDataSourceType() {
threadLocal.remove();
}
/**
* 判断指定DataSource当前是否存在
*
* @param dataSource 数据源
* @return
*/
public static boolean containsDataSource(String dataSource) {
return dataSources.contains(dataSource);
}
}
5 创建DynamicDatasource
类,这个类的主要作用就是来对我们的数据源进行获取,并且该类继承了一个名为AbstractRoutingDataSource
类,它是一个轻量级的数据源切换类,我们只需要继承它并重写它的方法即可。
public class DynamicDatasource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//返回当前threadLocal变量中的数据源类型
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
6 完成维护工作后,我们回到DatasourceConfig
配置类中,去处理设置数据源的功能。
@Bean
public DataSource getDatasource() {
//将数据源存入map集合
Map<Object, Object> dataSourceMap = new HashMap<>();
DataSource db1 = new HikariDataSource(db1Config());
DataSource db2 = new HikariDataSource(db2Config());
dataSourceMap.put("db1", db1);
dataSourceMap.put("db2", db2);
// 设置动态数据源
DynamicDatasource dataSource = new DynamicDatasource();
dataSource.setTargetDataSources(dataSourceMap);
// 记录多数据源名称
DynamicDataSourceContextHolder.dataSources.add("db1");
DynamicDataSourceContextHolder.dataSources.add("db2");
//设置默认的数据源
dataSource.setDefaultTargetDataSource(db1);
return dataSource;
}
到这步我们就完成了动态切换数据源的配置了,那么我们该如何使用呢?
在文章开始我们说到,需要用到AOP切面+注解的方式来实现动态数据源的切换功能,接下来我们看该如何操作。
1 创建一个注解类,名为DS
。
@Target({ElementType.METHOD,ElementType.TYPE}) //加在方法和类型上
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
String value() default "";
}
2 在使用切面编程前,需要先引入相关依赖。
<!--切面-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
3 定义切面类DatasourceAspect
,处理切换数据源的逻辑。
@Aspect
@Component
@Order(5)
public class DatasourceAspect {
//定义切入点,即切入到自定义的注解类的路径
@Pointcut("@annotation(com.it.annotation.DS)")
public void datasourceAspect() {
}
//切入点之前进行的操作
@Before("datasourceAspect() && @annotation(ds)")
public void beforeDatasource(JoinPoint joinPoint, DS ds) {
//获取注解中的值
String value = ds.value();
//判断当前注解里的值是否存在
if (DynamicDataSourceContextHolder.containsDataSource(value)) {
//如果存在,则使用注解中的数据
DynamicDataSourceContextHolder.setDataSourceType(value);
} else {
//不存在则使用默认数据源
DynamicDataSourceContextHolder.setDataSourceType("db1");
}
}
//执行完方法之后,将threadLocal变量的数据源清空
@After("@annotation(ds)")
public void remoteDatasource(JoinPoint point, DS ds) {
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
完成这些配置之后,我们就可以使用注解的方式来完成数据源的动态切换了,接下来看一下代码示例。
测试
我们可以看到文章最开始的使用案例,接下来我们来进行测试查看是否成功。
@GetMapping("/listDb1")
public List<Db1User> listDb1() {
return db1UserService.list();
}
@GetMapping("/listDb2")
@DS("db2")
public List<Db2User> listDb2() {
return db2UserService.list();
}
这里有两个接口,一个是查询db1
数据库的,一个是查询db2
数据库的,db1没有使用注解所以使用默认数据源。
测试结果
db1
{
"id": 1651767622561959937,
"username": "苏秀兰",
"age": 18,
"address": "山东省 烟台市 莱山区",
"createTime": "2023-04-28 09:57:42"
},
{
"id": 1651767632389214210,
"username": "韩静",
"age": 18,
"address": "湖北省 咸宁市 通山县",
"createTime": "2023-04-28 09:57:45"
},
{
"id": 1651767641637654529,
"username": "周刚",
"age": 18,
"address": "甘肃省 定西市 漳县",
"createTime": "2023-04-28 09:57:47"
},
{
"id": 1651781461856866306,
"username": "白秀兰",
"age": 18,
"address": "湖北省 黄石市 西塞山区",
"createTime": "2023-04-28 10:52:42"
}
db2
{
"id": 1651768320674619394,
"username": "孟涛",
"age": 18,
"address": "台湾 台南市 将军区",
"createTime": "2023-04-28 10:00:29"
},
{
"id": 1651781496703143938,
"username": "徐军",
"age": 18,
"address": "陕西省 延安市 黄陵县",
"createTime": "2023-04-28 10:52:50"
}
总结
使用动态数据源可以减轻数据库很多的压力,不同的数据可以存入不同的数据库中,常常应用在一些读写分离、分库分表的模式下。
如果这篇文章对你有帮助的话,请点个赞吧。