SpringBoot实现多数据源切换

1. 概述


仓库地址:https://gitee.com/aopmin/multi-datasource-demo

随着项目规模的扩大和业务需求的复杂化,单一数据源已经不能满足实际开发中的需求。在许多情况下,我们需要同时操作多个数据库,或者需要将不同类型的数据存储在不同的数据库中。这时,多数据源场景成为必不可少的解决方案。

市面上常见的多数据源实现方案如下:

  • 方案1:基于Spring框架提供的AbstractRoutingDataSource。

    • 优点: 简单易用,支持动态切换数据源;适用于少量数据源情况。
    • 场景:适用于需要动态切换数据源,且数据库较少的情况。
    • 文档地址:
  • 方案2:使用MP提供的Dynamic-datasource多数据源框架。

    • 文档地址:https://baomidou.com/guides/dynamic-datasource/#dynamic-datasource
  • 方案3:通过自定义注解在方法或类上指定数据源,实现根据注解切换数据源的功能。

    • 优点: 灵活性高,能够精确地控制数据源切换;在代码中直观明了。
    • 场景: 适用于需要在代码层面进行数据源切换,并对数据源切换有精细要求的情况。
  • 方案4:使用动态代理技术,在运行时动态切换数据源,实现多数据源的切换。

    • 优点: 灵活性高,支持在运行时动态切换数据源;适合对数据源切换的逻辑有特殊需求的情况。
    • 场景: 适用于需要在运行时动态决定数据源切换策略的情况。

2. 基于SpringBoot的多数据源实现方案


1、执行sql脚本:(分别创建两个数据库,里面都提供一张user表)

-- 创建数据库ds1
CREATE DATABASE `ds1`;

-- 使用ds1数据库
USE ds1;

-- 创建user表
CREATE TABLE `user` (
    `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `username` VARCHAR(50) COMMENT '用户名',
    `gender` TINYINT(1) COMMENT '性别:0男,1女'
);

-- 向user表插入数据
INSERT INTO user (username, gender) VALUES 
('张三', 1),
('李四', 0),
('王五', 1);

-- 创建数据库ds2
CREATE DATABASE `ds2`;

-- 使用ds2数据库
USE ds2;

-- 创建user表
CREATE TABLE `user` (
    `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `username` VARCHAR(50) COMMENT '用户名',
    `gender` TINYINT(1) COMMENT '性别:0男,1女'
);

-- 向user表插入数据
INSERT INTO user (username, gender) VALUES 
('赵六', 1),
('陈七', 0),
('宝国', 1);

2、创建一个maven工程,向pom.xml中添加依赖:

<!--锁定SpringBoot版本-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.10</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <!--jdbc起步依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
    <!--test起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.20</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
      <!--jdbc起步依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
</dependencies>

3、编写实体类:

package cn.aopmin.entity;

import lombok.*;

/**
 * 实体类
 *
 * @author 白豆五
 * @since 2024/7/4
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    private Integer id;
    private String username;
    private Integer gender;
}

4、创建application.yml文件,配置数据源:

spring:

  #动态数据源配置
  datasource:
    ds1:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/ds1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
      username: root
      password: 123456
    ds2:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/ds2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
      username: root
      password: 123456

logging:
  level:
    cn.aopmin: debug

5、编写数据源配置类:

package cn.aopmin.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源配置类
 * 配置多数据源和动态数据源
 *
 * @author 白豆五
 * @since 2024/7/4
 */
@Configuration
public class DataSourceConfig {

    //定义数据源1
    @Bean("ds1")
    @ConfigurationProperties(prefix = "spring.datasource.ds1")
    public DataSource ds1() {
        return DataSourceBuilder.create().build();
    }

    //定义数据源2
    @Bean("ds2")
    @ConfigurationProperties(prefix = "spring.datasource.ds2")
    public DataSource ds2() {
        return DataSourceBuilder.create().build();
    }

    //定义动态数据源
    @Bean(name = "dataSource")
    public DataSource dynamicDataSource(@Qualifier("ds1") DataSource ds1,
                                        @Qualifier("ds2") DataSource ds2) {
        //1.定义数据源map
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("ds1", ds1);
        targetDataSources.put("ds2", ds2);
        //2.实例化自定义的DynamicDataSource对象, 并设置数据源map
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        //3.设置默认数据源,未匹配上则使用默认数据源
        dynamicDataSource.setDefaultTargetDataSource(ds1);
        return dynamicDataSource;
    }

    // 通过JdbcTemplate	
    @Bean
    public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource ds) {
        return new JdbcTemplate(ds);
    }
}

6、创建DynamicDataSource动态数据类:

package cn.aopmin.config;

import cn.aopmin.common.DataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * AbstractRoutingDataSource(抽象的数据源路由器) 的基本原理是, 它维护了一个数据源的集合,每个数据源都有唯一的一个标识符
 * 当应用程序需要访问数据库的时候,AbstractRoutingDataSource会根据某种匹配规则(例如请求参数、用户身份等)来选择一个合适的数据源,
 * 并将请求转发给这个数据源。
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 获取数据源名称
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

7、定义一个ThreadLocal工具类:

package cn.aopmin.common;

/**
 * 使用ThreadLocal保存数据源名称
 *
 * @author 白豆五
 * @since 2024/7/4
 */
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 将数据源名称绑定到当前线程上
    public static void setDataSource(String dataSourceName) {
        contextHolder.set(dataSourceName);
    }

    // 获取当前线程上的数据源名称
    public static String getDataSource() {
        return contextHolder.get();
    }

    // 清除数据源名称
    public static void clearDataSource() {
        contextHolder.remove();
    }
}

8、创建启动类

package cn.aopmin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author 白豆五
 * @since 2024/7/3
 */
@SpringBootApplication
public class Demo01Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo01Application.class, args);
    }
}

9、创建UserService:

package cn.aopmin.service;

import cn.aopmin.common.DataSourceContextHolder;
import cn.aopmin.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * @author 白豆五
 * @since 2024/7/4
 */
@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insertDs1(User user) {
        try {
            // todo:自定义注解+SpringAop实现数据源的切换
            DataSourceContextHolder.setDataSource("ds1");
            String sql = "insert into user(username,gender) values(?,?)";
            jdbcTemplate.update(sql,user.getUsername(), user.getGender());
        } finally {
            DataSourceContextHolder.clearDataSource();
        }
    }

    public void insertDs2(User user) {
        try {
            DataSourceContextHolder.setDataSource("ds2");
            String sql = "insert into user(username,gender) values(?,?)";
            jdbcTemplate.update(sql,user.getUsername(), user.getGender());
        } finally {
            DataSourceContextHolder.clearDataSource();
        }
    }
}

10、编写测试:

package cn.aopmin.service;

import cn.aopmin.Demo01Application;
import cn.aopmin.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest(classes = {Demo01Application.class})
public class UserServiceTest {

    @Resource
    private UserService userService;


    @Test
    public void testInsertDs1() {
        User user = new User();
        user.setUsername("jack");
        user.setGender(0);
        user.setGender(1);
        userService.insertDs1(user);
    }

    @Test
    public void testInsertDs2() {
        User user = new User();
        user.setUsername("rose");
        user.setGender(1);
        userService.insertDs2(user);
    }
}

最终效果:

在这里插入图片描述


3. 基于Dynamic-datasource实现方案


mp文档:https://baomidou.com/guides/dynamic-datasource/#_top

1、创建SpringBoot工程,引入Dynamic-datasource依赖:

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

2、配置数据源:

spring:
  #多数据源配置
  datasource:
    dynamic:
      primary: master #设置默认数据源
      strict: false #是否严格检查动态数据源提供的数据库名
      datasource:
        #数据源1
        master:
          url: jdbc:mysql:///ds1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        #数据源2
        slave1:
          url: jdbc:mysql:///ds2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

3、实体类:

package cn.aopmin.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 实体类
 *
 * @author 白豆五
 * @since 2024/7/4
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private Integer gender;
}

4、业务类:

package cn.aopmin.service;

import cn.aopmin.entity.User;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * 通过@DS注解切换数据源
 * @author 白豆五
 * @since 2024/7/4
 */
@Service
// @DS("master") //不加@DS注解,会使用默认数据源
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insertDs1(User user) {
        String sql = "insert into user(username,gender) values(?,?)";
        jdbcTemplate.update(sql,user.getUsername(), user.getGender());
    }

    @DS("slave1")
    public void insertDs2(User user) {
        String sql = "insert into user(username,gender) values(?,?)";
        jdbcTemplate.update(sql,user.getUsername(), user.getGender());
    }
}

4、测试类:

package cn.aopmin.service;


import cn.aopmin.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
public class UserServiceTest2 {

    @Resource
    private UserService userService;


    @Test
    public void testInsertDs1() {
        User user = new User();
        user.setUsername("jack");
        user.setGender(0);
        user.setGender(1);
        userService.insertDs1(user);
    }

    @Test
    public void testInsertDs2() {
        User user = new User();
        user.setUsername("rose");
        user.setGender(1);
        userService.insertDs2(user);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

陶建辉当选 GDOS 全球数据库及开源峰会荣誉顾问

近日&#xff0c;第二十三届 GOPS 全球运维大会暨 XOps 技术创新峰会在北京正式召开。本次会议重点议题方向包括开源数据库落地思考、金融数据库自主可控、云原生时代下数据库、数据库智能运维、数据库安全与隐私、开源数据库与治理。大会深入探讨这些方向&#xff0c;促进了数…

Matplotlib 学习

知识点 1.plot()&#xff1a;用于绘制线图和 散点图scatter() 函数&#xff1a;plot() 函数可以接受许多可选参数&#xff0c;用于控制图形的外观&#xff0c;例如&#xff1a;颜色: colorblue 控制线条的颜色。线型: linestyle-- 控制线条的样式&#xff0c;例如虚线。标记…

前端vue后端java使用easyexcel框架下载表格xls数据工具类

一 使用alibaba开源的 easyexcel框架&#xff0c;后台只需一个工具类即可实现下载 后端下载实现 依赖 pom.xml <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependen…

昇思25天学习打卡营第12天|FCN图像语义分割

文章目录 昇思MindSpore应用实践基于MindSpore的FCN图像语义分割1、FCN 图像分割简介2、构建 FCN 模型3、数据预处理4、模型训练自定义评价指标 Metrics 5、模型推理结果 Reference 昇思MindSpore应用实践 本系列文章主要用于记录昇思25天学习打卡营的学习心得。 基于MindSpo…

机械键盘有哪些分类

机械键盘是一种比传统的薄膜键盘更耐用、更快捷、更具有手感的键盘。它的键帽和按键是独立的&#xff0c;能够提供更好的反应速度和操作感。机械键盘在现代化生活中得到了广泛的应用。根据其特性和使用场景&#xff0c;机械键盘可以分为以下几类&#xff1a; 1.轴体分类 机械…

永磁同步电机控制算法--最大转矩电流比控制(虚拟信号注入法)

目前&#xff0c;国内外相关学者对 MTPA 控制方法进行了一系列的理论研究与仿真分析。通过研究取得的成果综合来看&#xff0c;该控制方法主要有&#xff1a;直接公式计算法、曲线拟合法、查表法、搜索法、高频信号注入法以及参数辨识法等。 之前的文章中已经介绍了直接公式计…

柯桥小语种学校成人生活口语学习|西班牙语中H为什么不发音…

01 H en el alfabeto espaol 西语字母表中的h 字母H是唯一一个在标准西班牙语中不再代表任何音素的字母。尽管在它单独出现时被叫做HACHE&#xff0c;但在大多数单词拼写中&#xff0c;它只是一个没有声音对应关系的字母&#xff0c;因此RAE称其为“无声的H”&#xff08;hac…

昇思25天学习打卡营第4天|MindSpore数据集和数据变换

# 打卡 目录 # 打卡 Dateset&#xff1a;Pipeline 的起始 具体步骤 数据处理 Pipeline 代码例子 内置数据集的情况 自定义数据集的情况 可迭代的数据集 生成器 Transforms&#xff1a;数据预处理 代码例子 通用变换Compose 文本变换 Text Lambda变换 Dateset&…

ExtruOnt——为工业 4.0 系统描述制造机械类型的本体

概述 论文地址 &#xff1a;https://arxiv.org/abs/2401.11848 原文地址&#xff1a;https://ai-scholar.tech/articles/ontology/ExtruOnt 在工业 4.0 应用场景中&#xff0c;以机器可解释代码提供的、语义丰富的制造机械描述可以得到有效利用。然而&#xff0c;目前显然还缺…

【开源项目】LocalSend 局域网文件传输工具

【开源项目】LocalSend 局域网文件传输工具 一个免费、开源、跨平台的局域网传输工具 LocalSend 简介 LocalSend 是一个免费的开源跨平台的应用程序&#xff0c;允许用户在不需要互联网连接的情况下&#xff0c;通过本地网络安全地与附近设备共享文件和消息。 项目地址&…

​RAG与LLM原理及实践(8)--- Chroma 应用场景及限制

前言 通过前面几节的介绍&#xff0c;你应该对Chroma的运作原理有相当透彻的理解。Chroma的设计正如之前描述的&#xff1a; Chroma提供的工具&#xff1a; 存储文档数据和它们的元数据&#xff1a;store embeddings and their metadata 嵌入&#xff1a;embed documents an…

.mkp勒索病毒:深度解析与防范

引言&#xff1a; 在数字化时代&#xff0c;网络安全问题日益严峻&#xff0c;其中勒索病毒作为一种极具破坏性的恶意软件&#xff0c;严重威胁着个人用户和企业机构的数据安全。在众多勒索病毒家族中&#xff0c;.mkp勒索病毒以其强大的加密能力和广泛的传播方式&#xff0c;成…

Amesim中删除计算结果保存计算文件

前言 Amesim在工程应用中计算的结果文件有时会很大&#xff0c;为了节省电脑存储空间&#xff0c;项目结束后可以将计算结果删除进行保存以存档。 操作步骤 具体操作步骤如下&#xff1a; Step1&#xff1a;在①File下打开&#xff08;Open&#xff09;需要删除计算结果的项…

PyQt5开发笔记:2. 2D与3D散点图、水平布局和边框修饰

一、装pyqtgraph和PyOpenGL库 pip install pyqtgraph pip install PyOpenGL 注意&#xff1a;一定不要pip install OpenGL&#xff0c;否则会找不到 二、3D散点图效果 import pyqtgraph as pg import pyqtgraph.opengl as gl import numpy as np# 创建应用程序 app pg.mkQ…

《机器学习》读书笔记:总结“第4章 决策树”中的概念

&#x1f4a0;决策树 基于树结构进行决策。 一棵决策树包括&#xff1a; 一个 根节点&#xff08;起点&#xff09;若干 叶节点&#xff08;没有下游节点的节点&#xff09;若干 内部节点(分支节点) 即&#xff1a; #mermaid-svg-Mxe3d0kNg29PM2n8 {font-family:"treb…

leetcode每日一题-3101 交替子数组计数

暴力遍历&#xff1a;看起来像是回溯,实际上就是递归 class Solution { private:long long _res 0; public:long long countAlternatingSubarrays(vector<int>& nums) {backtrack(nums, 0);return _res;}void backtrack(vector<int>& nums, long long st…

黑马|最新AI+若依 |初识项目

本章主要内容是&#xff1a; 1.快速搭建了若依前后端项目在本地 2.实现了单表的增删改查快速生成 文章目录 介绍1.若依介绍2.若依的不同版本3.项目运行环境 初始化前后端项目1.下载若依项目2.初始化后端a.把表导入到数据库中b.更改application.yml文件 3.初始化前端a.安装依赖…

【游戏引擎之路】登神长阶(六)——雅达利2600汇编学习,任天堂居然还真不是抄袭起家

5月20日-6月4日&#xff1a;攻克2D物理引擎。 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 6月23日-7月1日&#xff1a;攻克《Windows游戏编程大师技巧》。 7…

基于海思Hi3403V100方案开发双目1600万拼接相机测试截图

海思Hi3403V100平台SOC内置四核A55&#xff0c;提供高效且丰富和灵活的CPU资源&#xff0c;以满足客户计算和控制需求&#xff0c;并且集成单核MCU&#xff0c;已满足一些低延时要求较高场景。 多目相机PE108CB板是针对该芯片设计的一款多目凭借相机PCBA&#xff0c;硬件接口支…

node.js_HTTP协议

Hypertext Transfer Protocol 超文本传输协议 1.HTTP报文 请求行 请求头 请求体 它的内容形式很灵活&#xff0c;可以设置任意内容 2.HTTP响应报文 响应状态码 响应状态的描述 遇到陌生的状态码可以参考一下这个网址&#xff1a; https://developer.mozilla.org/zh-C…