Spring boot 使用AbstractRoutingDataSource实现数据源动态切换

目录

一、AbstractRoutingDataSource

二、具体实现

1、pom.xml

2、新建UserMapper

3、在spring boot 启动类上添加扫描mapper注解

4、在配置文件 application.properties 中添加多个(我这里是两个)数据源的配置信息

5、集成动态数据源模块

5.1、新建注解 CurDataSource 指定要使用的数据源

5.2、新建常量存储于获取数据源

5.3、新建类 DynamicDataSource

5.4、新建多数据源配置类

5.5、采用aop的方式

5.6、启动类上添加数据源配置

5.7、测试数据源切换


一、AbstractRoutingDataSource

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 源码的介绍:

大概意思是:
AbstractRoutingDataSource的getConnection() 方法根据查找 lookup key 键对不同目标数据源的调用,通常是通过(但不一定)某些线程绑定的事物上下文来实现。

AbstractRoutingDataSource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。
基于AbstractRoutingDataSource的多数据源动态切换,可以实现读写分离,这么做缺点也很明显,无法动态的增加数据源。
 

实现逻辑:

定义DynamicDataSource类继承抽象类AbstractRoutingDataSource,并实现了determineCurrentLookupKey()方法。
把配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中。
调用AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。

详细解析跳转此链接查看利用AbstractRoutingDataSource实现动态数据源切换determineCurrentLookupKey方法-CSDN博客

二、具体实现

代码结构

1、pom.xml

新建springboot项目,其中pom.xml 文件依赖如下

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><!-- MySQL --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><!-- MyBatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency><!-- swagger --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.xmlunit</groupId><artifactId>xmlunit-core</artifactId></dependency></dependencies>

2、新建UserMapper

在新建两个数据库,新建两个表,可以如下,也可以自己定义

实体User

@Data
public class User {private String name;private String address;private int number;}

UserMapper

@Mapper
public interface UserMapper {@Select("select * from userdata")List<User> getAllUsers();
}
3、在spring boot 启动类上添加扫描mapper注解

注意
@MapperScan("com.example.dynamicdata.mapper")

如下图,但是mapper路径要改成自己存放mapper文件的  文件夹相对路径

其中路径为mapper的相对路径

4、在配置文件 application.properties 中添加多个(我这里是两个)数据源的配置信息
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis.mapper-locations=classpath:com.com.example.dynamicdata.mapper/*.xml
# 应用服务 WEB 访问端口
server.port=8080
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据源1
spring.datasource.druid.first.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true
spring.datasource.druid.first.username=root
spring.datasource.druid.first.password=123456
# 数据源2  需要创建对应数据库  更改该库中 sys_user 表
spring.datasource.druid.second.url=jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true
spring.datasource.druid.second.username=root
spring.datasource.druid.second.password=123456

如果还要添加数据源就按照 这种格式继续往下写。

5、集成动态数据源模块
5.1、新建注解 CurDataSource 指定要使用的数据源
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurDataSource {String name() default "";}
5.2、新建常量存储于获取数据源
public interface DataSourceNames {String FIRST = "first";String SECOND = "second";}
5.3、新建类 DynamicDataSource

DynamicDataSource扩展Spring的AbstractRoutingDataSource抽象类,重写 determineCurrentLookupKey() 方法

public class DynamicDataSource extends AbstractRoutingDataSource {/*** ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。* 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好** @param defaultTargetDataSource 默认数据源* @param targetDataSources       目标数据源*/public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return getDataSource();}public static void setDataSource(String dataSource) {CONTEXT_HOLDER.set(dataSource);}public static String getDataSource() {return CONTEXT_HOLDER.get();}public static void clearDataSource() {CONTEXT_HOLDER.remove();}}
5.4、新建多数据源配置类

配置多数据源的信息,生成多个(我这里是两个,对应application.properties中定义的数据源)数据源

注意

此处

@ConfigurationProperties("spring.datasource.druid.first"),second也一样

中的内容写application.properties中数据库配置的前半截,参考我这个写,不用写后边的url

package com.example.dynamicdata.configs;import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.example.dynamicdata.common.DataSourceNames;
import com.example.dynamicdata.common.DynamicDataSource;
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 btyuan* @version V1.0.0*/
@Configuration
public class DynamicDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.druid.first")public DataSource firstDataSource(){return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.druid.second")public DataSource secondDataSource(){return DruidDataSourceBuilder.create().build();}@Bean@Primarypublic DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {Map<Object, Object> targetDataSources = new HashMap<>(5);targetDataSources.put(DataSourceNames.FIRST, firstDataSource);targetDataSources.put(DataSourceNames.SECOND, secondDataSource);return new DynamicDataSource(firstDataSource, targetDataSources);}}
5.5、采用aop的方式

在需要修改数据源的地方使用注解方式去切换,然后切面修改ThreadLocal的内容

此处要注意

@Pointcut("@annotation(com.example.dynamicdata.common.CurDataSource)")

中的路径为CurDataSource文件相对路径,这是我的,你要改成自己新建CurDataSource文件的目录

package com.example.dynamicdata.utils;import com.example.dynamicdata.common.CurDataSource;
import com.example.dynamicdata.common.DataSourceNames;
import com.example.dynamicdata.common.DynamicDataSource;
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.core.Ordered;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 多数据源,切面处理类** @author btyuan* @version V1.0.0*/
@Slf4j
@Aspect
@Component
public class DataSourceAspect implements Ordered {@Pointcut("@annotation(com.example.dynamicdata.common.CurDataSource)")public void dataSourcePointCut() {}@Around("dataSourcePointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();CurDataSource ds = method.getAnnotation(CurDataSource.class);if (ds == null) {DynamicDataSource.setDataSource(DataSourceNames.FIRST);log.debug("set datasource is " + DataSourceNames.FIRST);} else {DynamicDataSource.setDataSource(ds.name());log.debug("set datasource is " + ds.name());}try {return point.proceed();} finally {DynamicDataSource.clearDataSource();log.debug("clean datasource");}}@Overridepublic int getOrder() {return 1;}
}
5.6、启动类上添加数据源配置

因为数据源是自己生成的,所以要去掉原先springboot启动时候自动装配的数据源配置。

注意

@MapperScan("com.example.dynamicdata.mapper")是mapper文件夹的相对路径
DynamicDataSourceConfig.class类名称不能写错,是上边5.4的配置类名称
package com.example.dynamicdata;import com.example.dynamicdata.configs.DynamicDataSourceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Import;@MapperScan("com.example.dynamicdata.mapper")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@Import({DynamicDataSourceConfig.class})
public class DynamicDataApplication {public static void main(String[] args) {SpringApplication.run(DynamicDataApplication.class, args);}}
5.7、测试数据源切换

service中定义两个查询,分别查两个数据库:

service

public interface SysUserService  {List<User> findUserByFirstDb();List<User> findUserBySecondDb();}

实现类:因为默认是使用第一个数据源,所以不用注解,使用数据源二需要添加注解 @CurDataSource(name = DataSourceNames.SECOND) 。

serviceImpl

注意

注解@Service不要忘了写

@Service
public class SysUserServiceImpl implements SysUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic List<User> findUserByFirstDb() {List<User> allUsers = userMapper.getAllUsers();return allUsers;}@CurDataSource(name = DataSourceNames.SECOND)@Overridepublic List<User> findUserBySecondDb() {List<User> allUsers = userMapper.getAllUsers();return allUsers;}
}

测试类Controller

package com.example.dynamicdata.controller;import com.example.dynamicdata.entity.User;
import com.example.dynamicdata.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@Slf4j
@RestController
@RequestMapping("/dynamic")
public class UserController {@Autowiredprivate SysUserService sysUserService;@RequestMapping("/data")private List<User> getUserData(){List<User> userList = new ArrayList<>();List<User> userByFirstDb = sysUserService.findUserByFirstDb();log.info("第一个数据库的User数据:" + userByFirstDb);List<User> userBySecondDb = sysUserService.findUserBySecondDb();log.info("第二个数据库的User数据:" + userBySecondDb);userList.addAll(userByFirstDb);userList.addAll(userBySecondDb);return userList;}}

访问测试

本地访问

http://localhost:8080/dynamic/data

如下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/27413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

186.二叉树:二叉搜索树中的插入操作(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

pytest + yaml 框架 -61.jenkins+allure+钉钉通知添加测试结果

前言 上一篇pytest + yaml 框架 -60.git+jenkins+allure+钉钉通知反馈 已经实现测试结果用钉钉通知。 本篇继续在钉钉通知里添加测试的汇总结果,此功能在pytest-yaml-yoyo v1.5.2版本上实现。 Environment Injector 插件 在运行完用例后会生成一个summary.json 文件,汇总…

护眼台灯哪个品牌更好?五款市面主流的护眼台灯款式分享

近年来&#xff0c;护眼台灯的研发和创新不断推进&#xff0c;一些台灯配备了智能化功能&#xff0c;如定时开关机、自动调节光线等&#xff0c;使孩子们能够更好地控制用眼时间和光线环境。护眼台灯哪个品牌更好&#xff1f;一些高端的护眼台灯还采用了纳米光滤镜技术&#xf…

誉天5月红帽战报:恭喜14名学员通过RHCE认证,通过率87.5%!

红帽认证是全球公认的Linux权威认证之一&#xff0c;对于Linux从业者来说具有很高的价值和认可度。旨在评估考生在Linux系统管理和应用方面的专业知识和技能。红帽考试是Linux从业者提升自身技能水平和职业竞争力的重要途径之一。 5月份&#xff0c;誉天14名学员通过了RHCE认证…

Python基础教程(十九):网络编程

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

Midjourney提示词终极指南(完整版)

在这篇博客中&#xff0c;我们深入研究了使用提示的艺术&#xff0c;以利用Midjourney的AI功能的力量。我们将探索各种技术&#xff0c;以创建个性化和迷人的图像&#xff0c;将你的创意想法转变为令人惊叹的视觉杰作。 1. 了解提示词 提示是简短的文字描述或关键词&#xff…

基于Spring+Vue的前后端分离的计算器

麻雀虽小&#xff0c;五脏俱全 需求分析 表达式求值 支持加减乘除四则运算、支持高精度 获取日志 Api文档定义 前后端分离&#xff0c;人不分离 通过Apifox定义接口细节&#xff0c;协调前后端开发工作。 软件架构设计 Spring-MVC 把软件系统分为三个基本部分&#xff1a;模…

跨国大文件传输需要哪些方面?怎么实现数据快速传输?

跨国大文件传输涉及到许多方面&#xff0c;包括网络速度、安全性、可靠性和法律合规性等。 以下是跨国大文件传输时需要考虑的一些重要方面&#xff1a; 高速稳定的网络连接&#xff1a;确保有足够的带宽和稳定的网络连接以支持大文件的快速传输。这可能需要考虑到跨国网络的延…

数字孪生火电厂:传统能源的数字化转型

通过图扑自主研发的产品 HT for Web &#xff0c;采用可视化与数字孪生技术&#xff0c;打造多样化设计风格和业务视角下的火电厂数字孪生方案。为智慧电厂综合“一张图”管理提供了上层展示技术支撑&#xff0c;助力企业增强对火电厂的信息化和数字化管理水平。

用android如何实现计算机计算功能

一.新建一个项目 步骤&#xff1a; 1.新建项目 2.选择 二.用户界面构建 找到项目的res的下面layout里面的activity.xml文件进行约束布局界面构建。 activity.xml代码如下&#xff1a; <?xml version"1.0" encoding"utf-8"?> <androidx.c…

电脑屏幕录制怎么录制?这7个录制屏幕的技巧值得一试!

电脑屏幕录制怎么录制&#xff1f;屏幕录制是什么? 简单地说&#xff0c;电脑屏幕录制就是在你的设备屏幕上录制视频。它可以捕捉屏幕上正在发生的事情&#xff0c;并让你与其他人分享。记录电脑、手机或笔记本电脑屏幕的原因有很多&#xff1a; 1. 一个简单的屏幕录制可以用…

STM32项目分享:智能大棚/智慧农业系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 2.PCB板打样焊接图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.co…

超全Midjourney自学教程,怒码1万3千字!这是我见过最良心的教程啦!

前段时间&#xff0c;后台有网友私信我&#xff0c;说想跟我一起学AI~当时一边开心一边惶恐&#xff0c;满足于被人看到自己的努力、又担心自己是不是教不好别人&#xff0c;毕竟我自己也是业余时间边学边发的那种~ 不过&#xff0c;我还是会继续搬运或整理一些我认为值得记录…

Spark学习——不同模式下执行脚本

举个简单的例子&#xff1a;使用spark官方用例"取pi值" 一、local模式 进入spark目录执行后台命令&#xff1a; bin/spark-submit \ --class org.apache.spark.examples.SparkPi \ --master local[*] \ ./examples/jars/spark-examples_2.12-3.2.1.jar \ 10运行结…

七、IP路由原理和路由引入

目录 一、IP路由原理 二、路由引入 2.1、双点双向路由引入 2.2、路由回灌 三、路由策略与路由控制 路由匹配工具&#xff08;规则&#xff09; ACL IP前缀列表 路由控制工具&#xff08;控制&#xff09; 策略工具1 策略工具2 搭配组合 组…

理解Es的DSL语法(二):聚合

前一篇已经系统介绍过查询语法&#xff0c;详细可直接看上一篇文章&#xff08;理解DSL语法&#xff08;一&#xff09;&#xff09;&#xff0c;本篇主要介绍DSL中的另一部分&#xff1a;聚合 理解Es中的聚合 虽然Elasticsearch 是一个基于 Lucene 的搜索引擎&#xff0c;但…

LaTeX 学习 第2节 数学结构

----用教授的方式学习 目录 2.1 上标与下标 2.2 上下画线与花括号 2.3 分式 2.4 根式 2.5 矩阵 ​​​​​​​LaTex安装包&#xff1a;https://download.csdn.net/download/weixin_38135241/89416392 LaTex- windows安装包&#xff1a;https://download.csdn.net/down…

SpringBoot3 整合 Mybatis 完整版

本文记录一下完整的 SpringBoot3 整合 Mybatis 的步骤。 只要按照本步骤来操作&#xff0c;整合完成后就可以正常使用。1. 添加数据库驱动依赖 以 MySQL 为例。 当不指定 依赖版本的时候&#xff0c;会 由 springboot 自动管理。 <dependency><groupId>com.mysql&l…

yolo案例项目学习记录

box-ocr: 监控摄像头视频流实时计数传送带的货物&#xff0c;并提取货物上面文字或二维码 1.本地环境&#xff1a; 1.1torch、torchvison、torchaudio版本对应关系 PyTorch中torch、torchvision、torchaudio、torchtext版本对应关系_torch2.0.1对应的torchvision-CSDN博客 1…

Python 全栈系列252 一些小计划

说明 最近整体进展还比较顺利&#xff0c;不过也因为这样&#xff0c;好几个线头怎么继续平衡和推进需要稍微捋一下。 内容 按重要|紧急方法来看&#xff0c;线头1是重要且紧急的&#xff0c;QTV200也算重要且紧急&#xff0c;其他都算是重要不紧急。 线头1: 数据清洗 虽然…