大家好,我是小富~

简介

项目开发中经常会遇到多数据源同时使用的场景,比如冷热数据的查询等情况,我们可以使用类似现成的工具包来解决问题,但在多数据源的使用中通常伴随着定制化的业务,所以一般的公司还是会自行实现多数据源切换的功能,接下来一起使用实现自定义注解的形式来实现一下。

基础配置

yml配置

pom.xml文件引入必要的Jar

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
</parent>
<groupId>com.dynamic</groupId>
<artifactId>springboot-dynamic-datasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<mybatis.plus.version>3.5.3.1</mybatis.plus.version>
<mysql.connector.version>8.0.32</mysql.connector.version>
<druid.version>1.2.6</druid.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- springboot核心包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
<!-- lombok工具包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

管理数据源

我们应用ThreadLocal来管理数据源信息,通过其中内容的get,set,remove方法来获取、设置、删除当前线程对应的数据源。

/**
* ThreadLocal存放数据源变量
*
* @author 公众号:程序员小富
* @date 2023/11/27 11:02
*/
public class DataSourceContextHolder { private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>(); /**
* 获取当前线程的数据源
*
* @return 数据源名称
*/
public static String getDataSource() {
return DATASOURCE_HOLDER.get();
} /**
* 设置数据源
*
* @param dataSourceName 数据源名称
*/
public static void setDataSource(String dataSourceName) {
DATASOURCE_HOLDER.set(dataSourceName);
} /**
* 删除当前数据源
*/
public static void removeDataSource() {
DATASOURCE_HOLDER.remove();
}
}

重置数据源

创建 DynamicDataSource 类并继承 AbstractRoutingDataSource,这样我们就可以重置当前的数据库路由,实现切换成想要执行的目标数据库。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map; /**
* 重置当前的数据库路由,实现切换成想要执行的目标数据库
*
* @author 公众号:程序员小富
* @date 2023/11/27 11:02
*/
public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDataSources);
} /**
* 这一步是关键,获取注册的数据源信息
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}

配置数据库

在 application.yml 中配置数据库信息,使用dynamic_datasource_1dynamic_datasource_2两个数据库

spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
url: jdbc:mysql://127.0.0.1:3306/dynamic_datasource_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 12345
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://127.0.0.1:3306/dynamic_datasource_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 12345
driver-class-name: com.mysql.cj.jdbc.Driver

再将多个数据源注册到DataSource.

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; /**
* 注册多个数据源
*
* @author 公众号:程序员小富
* @date 2023/11/27 11:02
*/
@Configuration
public class DateSourceConfig { @Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource dynamicDatasourceMaster() {
return DruidDataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource dynamicDatasourceSlave() {
return DruidDataSourceBuilder.create().build();
} @Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource createDynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>();
// 设置默认的数据源为Master
DataSource defaultDataSource = dynamicDatasourceMaster();
dataSourceMap.put("master", defaultDataSource);
dataSourceMap.put("slave", dynamicDatasourceSlave());
return new DynamicDataSource(defaultDataSource, dataSourceMap);
}
}

启动类配置

在启动类的@SpringBootApplication注解中排除DataSourceAutoConfiguration,否则会报错。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

到这多数据源的基础配置就结束了,接下来测试一下

测试切换

准备SQL

创建两个库dynamic_datasource_1、dynamic_datasource_2,库中均创建同一张表 t_dynamic_datasource_data。

CREATE TABLE `t_dynamic_datasource_data` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`source_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);

dynamic_datasource_1.t_dynamic_datasource_data表中插入

insert into t_dynamic_datasource_data (source_name) value ('dynamic_datasource_master');

dynamic_datasource_2.t_dynamic_datasource_data表中插入

insert into t_dynamic_datasource_data (source_name) value ('dynamic_datasource_slave');

手动切换数据源

这里我准备了一个接口来验证,传入的 datasourceName 参数值就是刚刚注册的数据源的key。

/**
* 动态数据源切换
*
* @author 公众号:程序员小富
* @date 2023/11/27 11:02
*/
@RestController
public class DynamicSwitchController { @Resource
private DynamicDatasourceDataMapper dynamicDatasourceDataMapper; @GetMapping("/switchDataSource/{datasourceName}")
public String switchDataSource(@PathVariable("datasourceName") String datasourceName) {
DataSourceContextHolder.setDataSource(datasourceName);
DynamicDatasourceData dynamicDatasourceData = dynamicDatasourceDataMapper.selectOne(null);
DataSourceContextHolder.removeDataSource();
return dynamicDatasourceData.getSourceName();
}
}

传入参数master时:127.0.0.1:9004/switchDataSource/master

传入参数slave时:127.0.0.1:9004/switchDataSource/slave

通过执行结果,我们看到传递不同的数据源名称,已经实现了查询对应的数据库数据。

注解切换数据源

上边已经成功实现了手动切换数据源,但这种方式顶多算是半自动,下边我们来使用注解方式实现动态切换。

定义注解

我们先定一个名为DS的注解,作用域为METHOD方法上,由于@DS中设置的默认值是:master,因此在调用主数据源时,可以不用进行传值。

/**
* 定于数据源切换注解
*
* @author 公众号:程序员小富
* @date 2023/11/27 11:02
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DS {
// 默认数据源master
String value() default "master";
}

实现AOP

定义了@DS注解后,紧接着实现注解的AOP逻辑,拿到注解传递值,然后设置当前线程的数据源

import com.dynamic.config.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects; /**
* 实现@DS注解的AOP切面
*
* @author 公众号:程序员小富
* @date 2023/11/27 11:02
*/
@Aspect
@Component
@Slf4j
public class DSAspect { @Pointcut("@annotation(com.dynamic.aspect.DS)")
public void dynamicDataSource() {
} @Around("dynamicDataSource()")
public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DS ds = method.getAnnotation(DS.class);
if (Objects.nonNull(ds)) {
DataSourceContextHolder.setDataSource(ds.value());
}
try {
return point.proceed();
} finally {
DataSourceContextHolder.removeDataSource();
}
}
}

测试注解

再添加两个接口测试,使用@DS注解标注,使用不同的数据源名称,内部执行相同的查询条件,看看结果如何?

@DS(value = "master")
@GetMapping("/dbMaster")
public String dbMaster() {
DynamicDatasourceData dynamicDatasourceData = dynamicDatasourceDataMapper.selectOne(null);
return dynamicDatasourceData.getSourceName();
}

@DS(value = "slave")
@GetMapping("/dbSlave")
public String dbSlave() {
DynamicDatasourceData dynamicDatasourceData = dynamicDatasourceDataMapper.selectOne(null);
return dynamicDatasourceData.getSourceName();
}

通过执行结果,看到通过应用@DS注解也成功的进行了数据源的切换。

事务管理

在动态切换数据源的时候有一个问题是要考虑的,那就是事务管理是否还会生效呢?

我们做个测试,新增一个接口分别插入两条记录,其中在插入第二条数据时将值设置超过了字段长度限制,会产生Data too long for column异常。

    /**
* 验证一下事物控制
*/
// @Transactional(rollbackFor = Exception.class)
@DS(value = "slave")
@GetMapping("/dbTestTransactional")
public void dbTestTransactional() { DynamicDatasourceData datasourceData = new DynamicDatasourceData();
datasourceData.setSourceName("test");
dynamicDatasourceDataMapper.insert(datasourceData); DynamicDatasourceData datasourceData1 = new DynamicDatasourceData();
datasourceData1.setSourceName("testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest");
dynamicDatasourceDataMapper.insert(datasourceData1);
}

经过测试发现执行结果如下,即便实现动态切换数据源,本地事务依然可以生效。

  • 不加上@Transactional注解第一条记录可以插入,第二条插入失败

  • 加上@Transactional注解两条记录都不会插入成功

本文案例地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot101/通用功能/springboot-config-order

SpringBoot 这么实现动态数据源切换,就很丝滑!的更多相关文章

  1. SpringBoot+Mybatis 实现动态数据源切换方案

    背景 最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据.考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态数据源的解决方案.在此分享给大家. 实现方案 数据库配置文 ...

  2. SpringBoot学习笔记:动态数据源切换

    SpringBoot学习笔记:动态数据源切换 数据源 Java的javax.sql.DataSource接口提供了一种处理数据库连接的标准方法.通常,DataSource使用URL和一些凭据来建立数据 ...

  3. Spring主从数据库的配置和动态数据源切换原理

    原文:https://www.liaoxuefeng.com/article/00151054582348974482c20f7d8431ead5bc32b30354705000 在大型应用程序中,配 ...

  4. SpringBoot和Mycat动态数据源项目整合

    SpringBoot项目整合动态数据源(读写分离) 1.配置多个数据源,根据业务需求访问不同的数据,指定对应的策略:增加,删除,修改操作访问对应数据,查询访问对应数据,不同数据库做好的数据一致性的处理 ...

  5. 【开发笔记】- AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

    AbstractRoutingDataSource动态数据源切换 上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他的业务的需求.一般的情况下我们都是使 ...

  6. Spring动态切换多数据源事务开启后,动态数据源切换失效解决方案

    关于某操作中开启事务后,动态切换数据源机制失效的问题,暂时想到一个取巧的方法,在Spring声明式事务配置中,可对不改变数据库数据的方法采用不支持事务的配置,如下: 对单纯查询数据的操作设置为不支持事 ...

  7. AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u012881904/article/de ...

  8. Java注解--实现动态数据源切换

    当一个项目中有多个数据源(也可以是主从库)的时候,我们可以利用注解在mapper接口上标注数据源,从而来实现多个数据源在运行时的动态切换. 实现原理 在Spring 2.0.1中引入了Abstract ...

  9. Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用

    [参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...

  10. SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换

    SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换 本文转载至:http://exceptioneye.iteye.com/blog/1698064 Spri ...

随机推荐

  1. .NET应用多语言-葡萄牙语软件,如何处理本地化,特别是数字的转换和计算

    在葡萄牙语软件中,数字本地化通常涉及小数点和千位分隔符的使用.在葡萄牙语中,小数点用","表示,而不是英语中使用的".".千位分隔符通常是一个空格或一个点. 例 ...

  2. JavaAgent寄生在目标进程中引起的ClassNotFoundException

    今天有解决方案部的小伙伴反映,我公司XWind产品在分析客户应用程序的潜在性能问题时,总是显现诊断任务异常,为了定位问题的根因,我们马上要求解决方案部的小伙伴提供XWind相关的日志,从日志中找到了如 ...

  3. c语言代码练习3

    //查看数字是否存在于数组中#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { int i = 0; int ...

  4. MOOC慕课课表

    8. 教育法学,共11单元---课件全开放状态,可以1次全学完开课时间: 2020年08月17日 ~ 2020年12月16日进行至第1周,共18周学时安排: 3-5小时每周 9. 教师职业道德与教育政 ...

  5. 2020/4/27 日常补坑-tarjan第一道awa

    第一题 luoguP1407 我们已知n对夫妻的婚姻状况,称第i对夫妻的男方为Bi,女方为Gi.若某男Bi与某女Gj曾经交往过(无论是大学,高中,亦或是幼儿园阶段,i≠j),则当某方与其配偶(即Bi与 ...

  6. SQL还是NoSQL?架构师必备选型技能

    很多时候我们都会有这样的疑问. 如果这时候直接去看MySQL.Mongo.HBase.Redis等数据库的用法.特点.区别,其实有点太着急了. 这时候,最好从「数据模型」开始讨论. 1.SQL vs ...

  7. idea 连接远程 docker 并部署项目到 docker

    目录 idea 连接远程 docker 1. 安装 docker 插件 2. 登录远程服务器,修改docker配置 3. 添加云服务器防火墙规则 4. idea 配置连接 docker 部署项目到 d ...

  8. 运用chatGPT生成E-R图的prompt

    根据以上内容,让我们定义用例 让我们为用例定义一个数据模型 更详细地描述数据模型或使用 Markdown 的表格格式 这种模型可以根据具体的用例进行扩展和修改,以满足需求分析和设计过程中的实际需要. ...

  9. influxdb报错:cache-max-memory-size exceeded

    转载请注明出处: influxdb报错日志: 该错误信息表示 InfluxDB 引擎超过了缓存最大内存大小.这意味着 InfluxDB 的缓存使用量超出了配置的限制. 要解决此问题,可以采取以下步骤来 ...

  10. JVM是如何处理反射的

    反射实现1-调用本地方法 例: 1 // v0版本 2 import java.lang.reflect.Method; 3 4 public class Test { 5 public static ...