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

news2024/11/29 13:35:33

目录

一、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();
    }

    @Override
    protected 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
    @Primary
    public 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");
        }
    }

    @Override
    public 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 {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> findUserByFirstDb() {
        List<User> allUsers = userMapper.getAllUsers();
        return allUsers;
    }

    @CurDataSource(name = DataSourceNames.SECOND)
    @Override
    public 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 {

    @Autowired
    private 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.coloradmin.cn/o/1824987.html

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

相关文章

【复旦邱锡鹏教授《神经网络与深度学习公开课》笔记】梯度的反向传播算法

矩阵微积分&#xff08;Matrix Calculus&#xff09; 在开始之前&#xff0c;需要先了解矩阵微积分的一些计算规则。 首先&#xff0c;对于矩阵微积分的表示&#xff0c;通常由两种符号约定&#xff1a; 分母布局 标量关于向量的导数为列向量 向量关于标量的导数为行向量 N维…

C# Winform Datagridview查询项目实例

在项目中&#xff0c;我们经常要遇到查询和展示内容&#xff0c;常用的做法是通过文本框&#xff0c;时间控件&#xff0c;按键和datagridview查询和展示内容。下面是一个常见的综合实例&#xff0c;并支持Excel(csv)导入导出&#xff0c;表格列动态调整的功能。 实例代码链接&…

【python-AI篇】人工智能技能树思维导图

大致总结一下得出如下思维导图&#xff0c;如不完善日后迭代更新 1. python基础三方库 1.1 科学计算库 ---- numpy库 1.2 科学计算库 ---- Scipy库 1.3 数据分析处理库 ---- pandas库 1.4 可视化库 ---- matplotlib库 1.5 可视化库 ---- seaborn库 1.6 机器学习和数据挖掘库 …

springboot网上书店管理系统-计算机毕业设计源码03780

摘 要 网上书店管理系统采用B/S结构、java开发语言、以及Mysql数据库等技术。系统主要分为管理员和用户两部分&#xff0c;管理员管理主要功能包括&#xff1a;首页、站点管理&#xff08;轮播图&#xff09;用户管理&#xff08;管理员、注册用户&#xff09;内容管理&#x…

OpenCV 4.10 发布

OpenCV 4.10 JPEG 解码速度提升 77%&#xff0c;实验性支持 Wayland、Win ARM64 根据 “OpenCV 中国团队” 介绍&#xff0c;从 4.10 开始 OpenCV 对 JPEG 图像的读取和解码有了 77% 的速度提升&#xff0c;超过了 scikit-image、imageio、pillow。 4.10 版本的一些亮点&…

第N4周:中文文本分类-Pytorch实现

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 目录 一、准备工作 1.任务说明 文本分类流程图&#xff1a; 2.加载数据 ​编辑 二、…

第2章 Rust初体验7/8:错误处理时不关心具体错误类型的下划线:提高代码可读性:猜骰子冷热游戏

讲动人的故事,写懂人的代码 2.6.6 用as进行类型转换:显式而简洁的语法 贾克强:“大家在查看Rust代码时,可能会注意到这一句。在这里,如果我们不使用as i32,编译器会报错,因为它在u32中找不到abs()方法。这是因为prev和sum_of_two_dice都是u32类型,u32类型并不支持abs(…

【LLM】吴恩达『微调大模型』课程完全笔记

Finetuning Large Language Models 版权说明&#xff1a; 『Finetuning Large Language Models』是DeepLearning.AI出品的免费课程&#xff0c;版权属于DeepLearning.AI(https://www.deeplearning.ai/)。 本文是对该课程内容的翻译整理&#xff0c;只作为教育用途&#xff0c;不…

4-字符串-11-反转字符串-LeetCode344

4-字符串-11-反转字符串-LeetCode344 LeetCode: 题目序号344 更多内容欢迎关注我&#xff08;持续更新中&#xff0c;欢迎Star✨&#xff09; Github&#xff1a;CodeZeng1998/Java-Developer-Work-Note 技术公众号&#xff1a;CodeZeng1998&#xff08;纯纯技术文&#xff0…

【牛客面试必刷TOP101】Day32.BM68 矩阵的最小路径和和BM69 把数字翻译成字符串

文章目录 前言一、BM68 矩阵的最小路径和题目描述题目解析二、BM69 把数字翻译成字符串题目描述题目解析总结 前言 一、BM68 矩阵的最小路径和 题目描述 描述&#xff1a; 给定两个字符串str1和str2&#xff0c;输出两个字符串的最长公共子序列。如果最长公共子序列为空&#x…

Ubuntu系统环境配置

Ubuntu安装terminator以及美化 安装 sudo apt-get install terminator美化 修改或者创建.config/terminator/config文件&#xff0c;添加如下配置 [global_config]suppress_multiple_term_dialog Truetitle_font Sans 11title_use_system_font False [keybindings] [lay…

supOS助力中核陕西铀浓缩有限公司迈向智能化、数字化、绿色化

中核陕西铀浓缩有限公司是中国核工业集团有限公司所属大型生产骨干企业&#xff0c;建于1969年10月&#xff0c;是我国第一座采用离心法生产浓缩铀的工厂。 蓝卓基于supOS工业操作系统&#xff0c;以“三化一平台一基础”的顶层架构&#xff0c;面向陕铀公司工艺优化、设备管理…

【Photoshop】PS修改文字内容

Photoshop(PS)修改图片上文字内容&#xff0c;网上教材不少&#xff0c;本人整理实践过的方法&#xff0c;分享给各位。本人实践方法&#xff1a; 内容识别填充&#xff1a;适用于背景色复杂的图片内容修补工具&#xff1a;适用于背景色为纯色的图片 方式一&#xff1a;内容识…

el-table表头文字换行或者修改字体颜色样式

例如 <el-table:data"tableData":header-cell-style"headClass" style"width: 100%;" border ><el-table-columnprop"address"label"生产工序"align"center"></el-table-column> //重点看这里…

uniapp 开发版小程序之间跳转

uni.navigateToMiniProgram({appId: urL,path: patH,envVersion: release,//我使用develop会给我返回&#xff1a;开发版小程序已过期&#xff0c;请在开发者工具重新扫码确定success(res) {console.log(res);// 打开成功uni.showToast({title: 跳转成功})},fail(err) {console…

希亦、追觅、云鲸洗地机:究竟有何不同?选择哪款更合适

最近收到很多私信里&#xff0c;要求洗地机测评的呼声特别高&#xff0c;作为宠粉的测评博主&#xff0c;当然是马上安排起来&#xff0c;满足大家对想看洗地机的愿望。这次洗地机测评&#xff0c;我挑选了三款热门的品牌型号&#xff0c;并从多个维度对它们进行使用测评&#…

【STM32】CubeIDE下载安装使用全记录

文章目录 0 前言1 下载安装2 基本使用2.0 编译下载2.1 字体和代码高亮设置2.2 快速格式化代码2.3 快速定位函数/变量的声明和定义2.4 设置代码折叠2.5 生成hex文件 3 设置代码自动提示4 设置中文界面5 遇到的问题和解决办法 0 前言 作为ST官方主推的集成开发环境&#xff08;ID…

深入浅出 Go 语言的 GPM 模型(Go1.21)

引言 在现代软件开发中&#xff0c;有效地利用并发是提高应用性能和响应速度的关键。随着多核处理器的普及&#xff0c;编程语言和框架如何高效、简便地支持并发编程&#xff0c;成为了软件工程师们评估和选择工具时的一个重要考量。在这方面&#xff0c;Go 语言凭借其创新的并…

单调栈——AcWing.830单调栈

单调栈 定义 单调栈是一种特殊的数据结构&#xff0c;栈内元素保持某种单调性&#xff08;通常是单调递增或单调递减&#xff09;。 运用情况 求解下一个更大元素或下一个更小元素。计算每个元素左边或右边第一个比它大或小的元素。 注意事项 要明确单调栈是递增还是递减…

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

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