【Java】SpringBoot中实现多数据源切换

news2024/11/26 18:29:17

前言

在日常项目开发中,某些需求会让不同的数据落实到不同的数据库,也或许是不同的页面需要不同数据库中的数据,在这种场景下,我们可以使用多数据源的配置来完成,通过在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_datasourcedb2_datasource

表:每个数据库都有一个user表,分别是db1_userdb2_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"
    }

总结

使用动态数据源可以减轻数据库很多的压力,不同的数据可以存入不同的数据库中,常常应用在一些读写分离、分库分表的模式下。

如果这篇文章对你有帮助的话,请点个赞吧。

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

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

相关文章

一篇文章解决Mysql8

基于尚硅谷的Mysql8.0视频&#xff0c;修修改改。提取了一些精炼的内容。 首先需要在数据库内引入一张表。链接地址如下。 链接&#xff1a;https://pan.baidu.com/s/1DD83on3J1a2INI7vrqPe4A 提取码&#xff1a;68jy 会进行持续更新。。 1. Mysql目录结构 Mysql的目录结构…

时序预测相关技术分享

时序预测相关技术分享 时序预测是指对时间序列数据进行预测&#xff0c;以预测未来的趋势或行为。在实际生产和应用中&#xff0c;时序预测广泛应用于金融、电力、交通等领域。时序预测可以帮助人们更好地理解和掌握未来的趋势和规律&#xff0c;从而做出更明智的决策。 时序…

Go语言测试——【单元测试 | Mock测试 | 基准测试】

作者&#xff1a;非妃是公主 专栏&#xff1a;《Golang》 博客主页&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录 序一、单元测试1. 测试文件命名2. 测试函数3.…

【Linux脚本篇】什么是shell脚本,什么是shell变量

目录 &#x1f341;什么是shell &#x1f342;什么是shell脚本 &#x1f342;shell脚本能做什么 &#x1f342;学习shell需要哪些知识 &#x1f342;shell基本规范 &#x1f342;shell脚本五种运行方式 &#x1f341;shell变量 &#x1f342;变量命名规范 &#x1f342;shell变…

配置 RT-Thread 的工程目录

1. 前言 RT-Thread 基于 Scons 的包管理非常方便让我们使用 RT-Thread 进行开发&#xff0c;但在实际工程中将应用代码写到 RT-Thread 官方提供的 bsp 目录下面会非常不便于使用&#xff0c;无法使用自己 git 工具进行代码管理。 解决方式&#xff0c;可以是 fork 出一个基于特…

Python 依赖库管理:pipreqs、pigar、pip-tools、pipdeptree

在 Python 的项目中&#xff0c;如何管理所用的全部依赖库呢&#xff1f;最主流的做法是维护一份“requirements.txt”&#xff0c;记录下依赖库的名字及其版本号。 那么&#xff0c;如何来生成这份文件呢&#xff1f;一种常规的方法&#xff1a; pip freeze > requiremen…

从零开始学习Web自动化测试:如何使用Selenium和Python提高效率?

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 引言&#xff1a; 一、了解Web自动化测试的基本概念 二、选择Web自动化测试工具 三、学习Web自动化测试的…

企业数据挖掘平台|道路运输安全大数据分析解决方案

TipDM大数据挖掘建模平台是由泰迪智能科技自主研发打造的可视化、一站式、高性能的数据挖掘与人工智能建模服务平台。目前已与民政、广电、电力、交通运输等多个行业的100客户达成及合作。 基于数据挖掘平台的道路运输安全大数据分析解决方案如下&#xff1a; 方案背景 …

U盘数据加密怎么设置?这个方法更快速、更安全!

U盘是目前使用率最高的移动储存设备&#xff0c;有时我们需要使用U盘存放非常重要的数据&#xff0c;那么就需要将其进行加密。常见的加密手段&#xff0c;如BitLocke驱动器加密&#xff0c;它的加密速度非常慢&#xff0c;当U盘中的数据稍微多一点的时候&#xff0c;我们就需要…

分不清信息化、数字化的区别?这篇文章让你全明白

前几天看到一篇讲数字化的文章&#xff0c;把信息化和数字化混为一谈&#xff0c;一些企业在实践转型的时候也分不清数字化和信息化之间的区别。 正好借这个问题尝试梳理一下数字化和信息化之间的本质区别。个人拙见&#xff0c;也欢迎大家评论区探讨。 01 业务侧重点不同 信…

上传了ipa但iTunes Connect没有构建版本问题

上传了ipa但iTunes Connect没有构建版本问题 转载&#xff1a;上传了ipa但iTunes Connect没有构建版本问题 AU上传ipa出现下图红框提示说明成功上传&#xff0c;如果App Store后台没有出现构建版本&#xff0c;请登录 apple账号对应的邮箱查看反馈&#xff0c;特别留意垃圾邮…

三角函数在js中的应用与二维空间绕另一个点旋转计算应用

开发中遇到一个问题&#xff0c;二维空间里正方形&#xff0c;按p点旋转90度后的点A2点的坐标是多少&#xff0c;这个设计到三角函数和矩阵的运算下面有公式。 由此翻了一遍三角函数相关的知识&#xff1a; A点绕p点旋转90度得A2点的坐标是多少&#xff0c;用三角函数解答&…

Java开发 - 不知道算不算详细的JUC详解

前言 大概有快两周没有发文了&#xff0c;这段时间不断的充实自己&#xff0c;算算时间&#xff0c;也到了该收获的时候&#xff0c;今天带来一篇JUC详解&#xff0c;但说实话&#xff0c;我也不敢自信它详不详细。JUC说白了就是多线程&#xff0c;学Java不久的人知道多线程&a…

openSUSE----openSUSE常用的软件包管理命令

【原文链接】openSUSE----openSUSE常用的软件包管理命令 zypper 是openSUSE操作系统软件包管理命令 repos源管理常用命令 zypper repos (或zypper lr&#xff09; 列出所有定义的安装源zypper addrepo &#xff08;或zypper ar) 添加一个新的安装源zypper removerepo (或zyp…

Java JDK下载安装环境变量配置

目录 一、下载安装 1.简介 2.JDK下载JDK 官网海外历史地址&#xff1a; 3.安装 二、环境变量配置 1.新建JAVA_HOME变量 2.PATH变量 3.CLASSPATH 变量 4.测试是否安装成功 一、下载安装 1.简介 JDK 是SUN公司提供的一套Java 语言的软件开发工具包&#xff0c;简称JDK(JavaDevelo…

hvv培训的流量分析题

题目如下 1 找扫描器的特征 常见的扫描器 使用过滤语句http contains "acunetix" 2 要找到黑客的登录后台 我们可以考虑搜搜看常见的后台路径admin ip.src 192.168.94.59 && http contains "admin" 追踪下tcp流,302说明大概就是对的 3 h…

python基础实战5-python基本结构

1 if语句 if语句是用来进行判断的&#xff0c;其使用格式如下 if 要判断的条件&#xff1a; 条件成立时&#xff0c;要做的事情 案例一&#xff1a; age 30 print("------if判断开始------") if age > 18:print("我成年了") print("------if判断…

从零基础到网络安全专家:全网最全的网络安全学习路线

前言 网络安全知识体系非常广泛&#xff0c;涉及的领域也非常复杂&#xff0c;有时候即使有想法和热情&#xff0c;也不知道从何入手。 为了帮助那些想要进入网络安全行业的小伙伴们更快、更系统地学习网络安全知识&#xff0c;我制定了这份学习路线。本路线覆盖了网络安全的…

【C++】程序员必备知识:认识类与对象

【C】程序员必备知识&#xff1a;认识类与对象 ①.面向过程和面向对象②.类的引入③.类的定义Ⅰ.定义方式Ⅱ.命名规则建议&#xff1a; ④.类的访问限定符及封装Ⅰ.访问限定符Ⅱ.封装 ⑤.类的作用域⑥.类的实例化⑦.类的对象大小计算Ⅰ.如何计算&#xff1f;Ⅱ.类对象存储方式Ⅲ…

【物联网】初步认识了解MQTT

目录 一、MQTT是什么 二、MQTT的版本 两者之间的关系&#xff1a; ​编辑三、MQTT工作的基本原理 3.1、概念 MQTT客户端&#xff1a; MQTT服务端&#xff1a; MQTT主题&#xff1a; 以下面这个图为例进行解释&#xff1a; 3.2、MQTT订阅/发布主题的特点 相互可独立性…