SpringBoot+dynamic-datasource实现多数据源(msyql、sqlserver、postgresql)手动切换

news2024/11/15 15:44:17

场景

SpringBoot+MybatisPlus+dynamic-datasources实现连接Postgresql和mysql多数据源:

SpringBoot+MybatisPlus+dynamic-datasources实现连接Postgresql和mysql多数据源-CSDN博客

上面实现通过注解和配置文件的方式去进行多数据源操作。

如果业务需求,比如查询第三方接口时提供的是sqlserver的视图连接方式时,需要在调用

接口时手动新增数据源-检验数据源是否可用-切换当前数据源-查询数据-清除当前数据源

实现以上流程,可以通过mybatisplus的dynamic-datasource来实现。

dynamic-datasource

开源文档付费

基础必读(免费) · dynamic-datasource · 看云

多数据源 | MyBatis-Plus

但是可以通过引入依赖后查看其源码以及借助网上一些教程,可以找到源码中需要用到的几个类

注:

博客:
霸道流氓气质-CSDN博客

实现

1、引入pom依赖

这里springboot的版本是2.6.13

dynamic-datasource的版本是3.2.1

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.2.1</version>
        </dependency>

引入msyql、sqlserver、postgresql所需的依赖

        <!--MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <!-- sqlserver-->
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>7.4.1.jre8</version>
        </dependency>

引入Mybatisplus所需依赖

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

引入其他依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>

2、这里使用默认的HikariCP数据库连接池,没使用Druid,流程一样

参考上面博客已经实现了连接Mysql和sqlserver多数据源

spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
         url:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
          dbcp2:
            min-idle: 5                                # 数据库连接池的最小维持连接数
            initial-size: 5                            # 初始化连接数
            max-total: 5                               # 最大连接数
            max-wait-millis: 150                       # 等待连接获取的最大超时时间

        pg:
          url: jdbc:postgresql://127.0.0.1:5432/test
          username: postgres
          password: 123456
          driver-class-name: org.postgresql.Driver
          dbcp2:
            min-idle: 5                                # 数据库连接池的最小维持连接数
            initial-size: 5                            # 初始化连接数
            max-total: 5                               # 最大连接数
            max-wait-millis: 150                       # 等待连接获取的最大超时时间

3、SpringBoot+dynamic-datasource使用DynamicRoutingDataSource获取当前所有数据源

代码实现:

@SpringBootTest
class DynamicDataSourceTest {

    @Autowired
    private DataSource dataSource;


    /**
     * 获取当前所有数据源
     */
    @Test
    void getAllDataSource() {
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        System.out.println(ds.getCurrentDataSources().keySet());
    }
}

其中DataSource是javax.sql包下的。

javax.sql.DataSource 是 jdk 提供的接口,各个连接池厂商 和 Spring 都对 DataSource 进行了设计和实现。

javax.sql.DataSource 是连接到物理数据源的工厂接口。它是 java.sql.DriverManager 功能的替代者,

是获取数据库连接的首选方法。

DataSource 数据源在必要时可以修改它的属性。例如,如果将数据源移动到其他服务器,

则可以更改 DataSource 的属性,这样访问该数据源的代码不需要做任何更改就可以获取到达到目的。

单元测试运行结果:

4、SpringBoot+DynamicRoutingDataSources实现添加与删除数据源

这里通过代码将sqlserver的数据源添加到DynamicRoutingDataSource中

这就需要用到creator包下的各种创建器

这里是默认HikariCP的连接池,所以使用该创建器,如果是其它类型则使用对应的创建器。

数据源创建需要的参数这里新建一个DTO类用来赋值

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class DataSourceDTO {
    private String dataSourceName;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
}

编写单元测试

@SpringBootTest
class DynamicDataSourceTest {

    @Autowired
    private DataSource dataSource;

    @Autowired(required = false)
    private HikariDataSourceCreator hikariDataSourceCreator;

    /**
     * 获取当前所有数据源
     */
    @Test
    void getAllDataSource() {
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        System.out.println(ds.getCurrentDataSources().keySet());
    }

    /**
     * 添加 与 删除 HikariCP数据源
     */
    @Test
    void addHcpDataSource() {
        DataSourceDTO sqlserver = DataSourceDTO.builder()
                .dataSourceName("sqlserver")
                .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
                .url("jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test")
                .username("sa")
                .password("123456")
                .build();
        DataSourceProperty dataSourceProperty = new DataSourceProperty();
        BeanUtils.copyProperties(sqlserver,dataSourceProperty);
        DataSource sqlserverDataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        ds.addDataSource(sqlserver.getDataSourceName(),sqlserverDataSource);
        System.out.println(ds.getCurrentDataSources().keySet());
        ds.removeDataSource(sqlserver.getDataSourceName());
        System.out.println(ds.getCurrentDataSources().keySet());
    }
}

单元测试运行结果:

5、SpringBoot中+HikariCP实现检测数据源是否可用

预实现手动切换当前数据源,首先需要确保配置的数据源的相关参数可以正常连接可用

新建检测数据源的方法

    /**
     *  检测数据源
     * @param dataSourceDTO
     * @return
     */

    private boolean checkDataBase(DataSourceDTO dataSourceDTO){
        Connection connection = null;
        try {
            DataSource dataSource = creatHcpDataSource(dataSourceDTO);
            connection = dataSource.getConnection();
            Statement statement = connection.createStatement();
            statement.execute("select * from s_user");
            return true;
        } catch (Exception exception) {
            return false;
        }finally {
            try {
                if(null!=connection){
                    connection.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

这其中就是创建数据源并进行连接和执行sql,确保无异常发生则代表连接可用,这里执行的sql以及表名

均写死,其中s_user是sqlserver中新建的表。

用到创建数据源的方法creatHcpDataSource的实现

    /**
     * 创建Hcp datasource
     * @param dataSourceDTO
     * @return
     */
    private DataSource creatHcpDataSource(DataSourceDTO dataSourceDTO){
        HikariDataSource dataSource = new HikariDataSource();
        //控制客户端(即用户程序)等待池中连接的最长毫秒数。如果在没有连接可用的情况下超过此时间,则将抛出SQLException异常。最低可以接受的连接超时为250毫秒。默认值:3000(30秒)。这是一个很重要的问题排查指标
        dataSource.setConnectionTimeout(3000l);
        //HikariCP将尝试仅基于jdbcUrl通过DriverManager解析驱动程序,但对于某些较旧的驱动程序必须指定driverClassName。除非用户收到明显的错误消息,表明未找到驱动程序,否则可忽略此属性。默认值:无
        //dataSource.setDataSourceClassName(dataSourceDTO.getDriverClassName());
        dataSource.setJdbcUrl(dataSourceDTO.getUrl());
        dataSource.setUsername(dataSourceDTO.getUsername());
        dataSource.setPassword(dataSourceDTO.getPassword());
        //表示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。该属性的默认值:自动生成
        dataSource.setPoolName(dataSourceDTO.getDataSourceName());
        return dataSource;
    }

相关参数和配置自行搜索和设置,这里重点关注

dataSource.setConnectionTimeout(3000l);

设置超时时间3秒。

控制客户端(即用户程序)等待池中连接的最长毫秒数。如果在没有连接可用的情况下超过此时间,则将抛出SQLException异常。

最低可以接受的连接超时为250毫秒。默认值:3000(30秒)。这是一个很重要的问题排查指标

dataSource.setPoolName

示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。该属性的默认值:自动生成

编写单元测试:

    /**
     * 检测数据源
     */
    @Test
    void testDataSource(){
        DataSourceDTO sqlserver = DataSourceDTO.builder()
                .dataSourceName("sqlserver")
                .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
                .url("jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test")
                .username("sa")
                .password("123456")
                .build();
        boolean checkDataBase = checkDataBase(sqlserver);
        System.out.println(checkDataBase);
        DataSourceDTO sqlserverWrong = DataSourceDTO.builder()
                .dataSourceName("sqlserver")
                .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
                .url("jdbc:sqlserver://192.168.1.2:1433;DatabaseName=test")
                .username("sa")
                .password("123456")
                .build();
        boolean checkDataBaseWrong = checkDataBase(sqlserverWrong);
        System.out.println(checkDataBaseWrong);
    }

第二个故意写了一个不存在的数据源的地址

运行单元测试:

6、SpringBoot+DynamicDataSourceContextHolder实现手动切换清除当前数据源

动态数据源切换的关键在于DynamicDataSourceContextHolder类,它提供了一种机制来存储当前使用的数据源。

它主要由两部分组成,一部分是线程本地的数据源容器,另一部分是管理动态数据源的数据源切换类。

查看其源码

主要用到的方法

push 设置当前线程数据源 如非必要不要手动调用,调用后确保最终清除

poll 清空当前线程数据源 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称

clear 强制清空本地线程  防止内存泄漏,如手动调用了push可调用此方法确保清除

编写单元测试:

    /**
     * 手动切换数据源
     */
    @Test
    void changeDataSource() {
        DataSourceDTO sqlserver = DataSourceDTO.builder()
                .dataSourceName("sqlserver")
                .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver")
                .url("jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test")
                .username("sa")
                .password("123456")
                .build();
        boolean checkDataBase = checkDataBase(sqlserver);
        if(checkDataBase){
            DataSourceProperty dataSourceProperty = new DataSourceProperty();
            BeanUtils.copyProperties(sqlserver,dataSourceProperty);
            DataSource sqlserverDataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
            DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
            Set<String> dataSourceSet = ds.getCurrentDataSources().keySet();
            if (!dataSourceSet.contains(sqlserver.getDataSourceName())) {
                ds.addDataSource(sqlserver.getDataSourceName(),sqlserverDataSource);
            }
            //push 设置当前线程数据源 如非必要不要手动调用,调用后确保最终清除
            DynamicDataSourceContextHolder.push(sqlserver.getDataSourceName());
            List<SUser> sUsers = sUserMapper.selectList(new LambdaQueryWrapper<>());
            System.out.println(sUsers);
            //DynamicDataSourceContextHolder.poll();
            //poll 清空当前线程数据源 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称
            //clear 强制清空本地线程  防止内存泄漏,如手动调用了push可调用此方法确保清除
            DynamicDataSourceContextHolder.clear();
            ds.removeDataSource(sqlserver.getDataSourceName());
        }else {
            System.out.println("数据源连接异常");
        }
    }

这里一定要对数据源可用性进行校验,确保可用才进行数据源添加和切换当前数据源操作

其中SUser是Sqlserver中新建表对应的实体类

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "s_user")
public class SUser implements Serializable {

    private static final long serialVersionUID = -5514139686858156155L;

    private Integer id;

    private String name;

    private Integer age;

    private String address;

}

调用查询的mapper接口实现

import com.badao.demo.entity.SUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

@Repository
public interface SUserMapper extends BaseMapper<SUser> {

}

单元测试运行结果

7、将以上手动切换数据源和执行业务逻辑的操作封装成工具类

import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import java.util.function.Supplier;

/**
 * 数据源切换工具类
 */
public class DynamicDSExecute {

    public static <T> T execute(String dsName, Supplier<T> executor){
        try {
            DynamicDataSourceContextHolder.push(dsName);
            return executor.get();
        }finally {
            DynamicDataSourceContextHolder.clear();
        }
    }
}

这里用到了函数式接口,可参考如下

java8中常用函数式接口Supplier<T>、Consumer<T>、Function<T,R>、Predicate<T>使用示例:

java8中常用函数式接口Supplier<T>、Consumer<T>、Function<T,R>、Predicate<T>使用示例_java8 函数式编程supplier接口案例大全-CSDN博客

然后调用工具类时

            Supplier<List<SUser>> supplier = () -> sUserMapper.selectList(new LambdaQueryWrapper<>());
            List<SUser> sUsers = DynamicDSExecute.execute(sqlserver.getDataSourceName(),supplier);

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

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

相关文章

Tensorflow2.0笔记 - 不使用layer方式,简单的MNIST训练

本笔记不使用layer相关API&#xff0c;搭建一个三层的神经网络来训练MNIST数据集。 前向传播和梯度更新都使用最基础的tensorflow API来做。 import tensorflow as tf from tensorflow import keras from tensorflow.keras import datasets import numpy as npdef load_mnist(…

arthas(阿尔萨斯)日常java代码调优使用命令

官方项目文档&#xff1a;https://gitee.com/arthas/arthas &#xff08;最权威的教学还是得官网&#xff0c;这里仅作简单记录&#xff09; 1&#xff1a;启动 java -jar arthas-boot.jar 2&#xff1a;查看cpu占用排名前三 thread -3 3&#xff1a;查看指定id thread 203 4:查…

ui 开发 剪辑等工具集成网站

这里给大家推荐一个工具集成网站,总体来说还是挺不错的 菜鸟工具 - 不止于工具

mysql中DATE_FORMAT() 函数详解

mysql中DATE_FORMAT() 函数详解 一. 说明 在 MySQL 中&#xff0c;DATE_FORMAT() 函数用于将日期/时间类型的值按照指定的格式进行格式化输出。它的一般语法如下&#xff1a; DATE_FORMAT(date, format)其中&#xff0c;date 参数是要被格式化的日期/时间值&#xff0c;form…

C++03:条件与分支语句

2024年1月14日 内容来自The Cherno&#xff1a;C系列 2024年1月17日 更新内容整理自&#xff1a; 南京大学 陈佳俊 郑涛 《程序设计教程 用C语言编程》 --------------------------------------------------------------------------------------------------------------…

Java和SpringBoot学习路线图

看了一下油管博主Amigoscode的相关视频&#xff0c;提到了Java和SpringBoot的学习路线&#xff0c;相关视频地址为&#xff1a; How To Master Java - Java for Beginners RoadmapSpring Boot Roadmap - How To Master Spring Boot 如下图所示&#xff1a; 当然关于Java和Spr…

【声光语音告警】小机房-动环系统与服务器监控二合一告警方案

目前场景及存在的问题 目前有很多小规模机房&#xff0c;服务器数量不多&#xff0c;机房面积也较小&#xff0c;例如医院、车站、博物馆、学校、工厂等环境。机房虽小&#xff0c;但仍然需要进行服务器性能监控以及机房动力环境监控&#xff0c;例如漏水、温湿度、烟感、电压…

ChatGPT企业版跟个人版有什么区别?

ChatGPT企业版&#xff08;ChatGPT Enterprise&#xff09;除了有和个人版GPT一样的功能外&#xff0c;企业版还可提供企业级的安全和隐私、以及数据分析功能。 订阅实用可以看下这篇文章&#xff1a; ChatGPT企业版的区别是&#xff0c;企业版允许客户输入公司的数据&#xf…

6.4.2转换文件

6.4.2转换文件 利用Swf2VideoConverter2可以很方便地将Flash动画(*.swf)转换为其它的视频格式。 1&#xff0e;单击“添加”按钮&#xff0c;在弹出的下拉菜单中选择“添加文件”&#xff0c;在弹出的“Open Swf Files(打开Swf文件)”窗口中选择swf文件(如&#xff1a;那些花…

拉索回归(Lasso Regression)的原理是什么?

拉索回归&#xff08;Lasso Regression&#xff09;&#xff0c;全称Least Absolute Shrinkage and Selection Operator回归&#xff0c;是一种线性回归的改进方法&#xff0c;主要用于数据分析和特征选择。其核心原理是在传统的线性回归损失函数中加入了一个L1正则化项&#x…

Javaweb超详细实现模拟支付宝扫码支付

1.普通方式创建Javaweb项目 首先创建Java项目 2.创建好的项目添加web框架支持 如图选择确定 在项目结构中配置有关信息 右键创建classes文件夹与lib文件夹 如图 此处找到刚才的项目的classes路径设置 在依赖中设置lib路径的设置 找到刚才的lib路径 选择此选项 结束项目结构中模…

web蓝桥杯真题--10、灯的颜色变化

介绍 我们经常会看到各种颜色的灯光&#xff0c;本题我们将实现一个颜色会变化的灯的效果。 准备 开始答题前&#xff0c;需要先打开本题的项目代码文件夹&#xff0c;目录结构如下&#xff1a; ├── effect.gif ├── images │ ├── greenlight.svg │ ├── l…

初识SpringBoot

SpringBoot以约定大于配置的核心思想,默认帮我们进行了很多设置,简单来说就是SpringBoot其实不是什么新的框架&#xff0c;它默认配置了很多框架的使用方式&#xff0c;就像maven整合了所有的jar包&#xff0c;spring boot整合了所有的框架 。 创建的包一定要在项目主程序入口…

MATLAB - 计算机械臂关节扭矩以平衡末端力和力矩

系列文章目录 前言 产生力矩以平衡作用在平面机器人末端执行器体上的端点力。要使用各种方法计算关节力矩&#xff0c;请使用刚体树机器人模型的几何雅各比&#xff08;geometricJacobian&#xff09;和反动力学&#xff08;inverseDynamics&#xff09;对象函数。 一、初始化…

JavaScript的代码执行顺序

&#xff08;1&#xff09;. js的执行顺序&#xff0c;先同步后异步 &#xff08;2&#xff09;. 异步中任务队列的执行顺序&#xff1a; 先微任务microtask队列&#xff0c;再宏任务macrotask队列 注意&#xff0c;按顺序从上到下时&#xff0c;没有轮到执行的任务会进入相应…

PowerScale重磅升级,加速迈进AI时代

2024开年 给大伙报告一则好消息 Dell非结构化数据存储的扛把子 PowerScale迎来重大升级 第二代PowerScale全闪存系统 即将闪亮登场 此次升级主要涉及硬件、软件及与NVIDIA的合作关系三个方面&#xff0c;升级后的PowerScale有望成为第一个通过 NVIDIA DGX SuperPOD验证的以…

Linux———sort命令总结详解(狠狠爱住)

目录 sort命令&#xff1a; 命令参数及描述&#xff1a; 示例&#xff1a; 使用-b参数&#xff0c;忽略行首空白字符&#xff0c;按照第一列进行排序&#xff1a; -d 选项是 sort 命令中一个非常有用的选项&#xff0c;它可以按照字典顺序进行排序&#xff0c;同时忽略非字…

创业前先把刘强东这两句琢磨明白!不然大概率失败!2024最适合创业的行业!2024年普通人的创业机会在哪里

第一句&#xff0c;真正解决一个问题。 这句话表达了&#xff0c;你的项目一定是要建立在解决具体的问题上&#xff0c;而不是你觉得自己有个好点子&#xff0c;或者好产品就可以了。因为即使你的产品很好&#xff0c;服务很好&#xff0c;如果不能切实的解决某个问题&#xf…

渐开线齿轮计算软件开发Python

从0开始开发计算软件&#xff0c;欢迎大家加入 源代码仓库

【C++】std::string 转换成非const类型 char* 的三种方法记录

std::string 有两个方法&#xff1a;data() 和 c_str()&#xff0c;都是返回该字符串的const char类型&#xff0c;那如何转换成非const的char呢&#xff1f; 下面展示三种方法&#xff1a; 强转&#xff1a;char* char_test (char*)test.c_str();使用string的地址&#xff…