当前位置: 首页 > news >正文

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.DriverjdbcUrl: jdbc:mysql://localhost:3306/ds1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456ds2:driverClassName: com.mysql.cj.jdbc.DriverjdbcUrl: jdbc:mysql://localhost:3306/ds2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456logging: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.定义数据源mapMap<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("ds1", ds1);targetDataSources.put("ds2", ds2);//2.实例化自定义的DynamicDataSource对象, 并设置数据源mapDynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);//3.设置默认数据源,未匹配上则使用默认数据源dynamicDataSource.setDefaultTargetDataSource(ds1);return dynamicDataSource;}// 通过JdbcTemplate	@Beanpublic 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*/@Overrideprotected 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 {@Autowiredprivate 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 {@Resourceprivate UserService userService;@Testpublic void testInsertDs1() {User user = new User();user.setUsername("jack");user.setGender(0);user.setGender(1);userService.insertDs1(user);}@Testpublic 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:#数据源1master:url: jdbc:mysql:///ds1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver#数据源2slave1:url: jdbc:mysql:///ds2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-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 {@Autowiredprivate 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 {@Resourceprivate UserService userService;@Testpublic void testInsertDs1() {User user = new User();user.setUsername("jack");user.setGender(0);user.setGender(1);userService.insertDs1(user);}@Testpublic void testInsertDs2() {User user = new User();user.setUsername("rose");user.setGender(1);userService.insertDs2(user);}
}

相关文章:

SpringBoot实现多数据源切换

1. 概述 仓库地址&#xff1a;https://gitee.com/aopmin/multi-datasource-demo 随着项目规模的扩大和业务需求的复杂化&#xff0c;单一数据源已经不能满足实际开发中的需求。在许多情况下&#xff0c;我们需要同时操作多个数据库&#xff0c;或者需要将不同类型的数据存储在不…...

VUE + 小程序 关于前端循环上传附件页面卡死的问题

最开始我使用for循环&#xff0c;后端能正常保存&#xff0c;但是前端页面卡死了&#xff0c;开始代码是这么写的 wx.showLoading({title: 文件上传中...,mask: true // 是否显示透明蒙层&#xff0c;防止触摸穿透&#xff0c;默认&#xff1a;false});const {fileList} that.…...

【基础算法总结】分治—归并

分治—归并 1.排序数组2.交易逆序对的总数3.计算右侧小于当前元素的个数4.翻转对 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.排序数组 …...

基于Java+SpringMvc+Vue技术的实验室管理系统设计与实现(6000字以上论文参考)

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…...

19_谷歌GoogLeNet(InceptionV1)深度学习图像分类算法

1.1 简介 GoogLeNet&#xff08;有时也称为GoogleNet或Inception Net&#xff09;是一种深度学习架构&#xff0c;由Google的研究团队在2014年提出&#xff0c;主要设计者为Christian Szegedy等人。这个模型是在当年的ImageNet大规模视觉识别挑战赛&#xff08;ILSVRC&#xf…...

clickhouse高可用可拓展部署

clickhouse高可用&可拓展部署 1.部署架构 1.1高可用架构 1.2硬件资源 部署服务 节点名称 节点ip 核数 内存 磁盘 zookeeper zk-01 / 4c 8G 100G zk-02 / 4c 8G 100G zk-03 / 4c 8G 100G clikehouse ck-01 / 32c 128G 2T ck-02 / 32c 128G 2T ck-03 / 32c 128G 2T ck-04 /…...

QT中QDomDocument读写XML文件

一、XML文件 <?xml version"1.0" encoding"UTF-8"?> <Begin><Type name"zhangsan"><sex>boy</sex><school>Chengdu</school><age>18</age><special>handsome</special>&l…...

sql盲注

文章目录 布尔盲注时间盲注 布尔盲注 介绍&#xff1a;在网页只给你两种回显的时候是用&#xff0c;类似于布尔类型的数据&#xff0c;1表示正确&#xff0c;0表示错误。 特点&#xff1a;思路简单&#xff0c;步骤繁琐且麻烦。 核心函数&#xff1a; length()函数substr()函…...

星网安全产品线成立 引领卫星互联网解决方案创新

2024年6月12日&#xff0c;盛邦安全&#xff08;688651&#xff09;成立星网安全产品线&#xff0c;这是公司宣布全面进入以场景化安全、网络空间地图和卫星互联网安全三大核心能力驱动的战略2.0时代业务落地的重要举措。 卫星互联网技术的快速发展&#xff0c;正将其塑造为全球…...

Adam自适应动量优化算法

Adam&#xff08;Adaptive Moment Estimation&#xff09;是一种结合了动量法和自适应学习率思想的优化算法&#xff0c;特别适用于训练神经网络和深度学习模型。以下是对Adam调整学习率的详细介绍及具体例子。 一、Adam调整学习率介绍 自适应学习率&#xff1a; Adam算法的核…...

Mac OS系统中Beyond Compare 4破解方式

文章出处 https://blog.csdn.net/qq_42418042/article/details/137544113 前言 记录实操过程&#xff0c;以防以后找不到了~ 实际原理是启动时删除文件&#xff0c;实现无限试用 实操过程 下载安装包 官网链接 https://www.scootersoftware.com/download.php 解压、移动到应…...

6000元最好的家用投影仪:当贝X5S Pro六千元配置最高画质最强

数码家电品牌发展迅速&#xff0c;投影同样也是一种更新迭代较快的产品类型&#xff0c;有时候去年还比较火的产品&#xff0c;今年就会被别的产品取代&#xff0c;就比如之前灯泡投影一直被认为是好产品的代表&#xff0c;但是现在国产激光投影的销量反而更高。一般来说6000元…...

#### golang中【堆】的使用及底层 ####

声明&#xff0c;本文部分内容摘自&#xff1a; Go: 深入理解堆实现及应用-腾讯云开发者社区-腾讯云 数组实现堆 | WXue 堆&#xff08;Heap&#xff09;是实现优先队列的数据结构&#xff0c;Go提供了接口和方法来操作堆。 应用 package mainimport ("container/heap&q…...

OpenAI Gym Atari on Windows

题意&#xff1a;在Windows系统上使用OpenAI Gym的Atari环境 问题背景&#xff1a; Im having issues installing OpenAI Gym Atari environment on Windows 10. I have successfully installed and used OpenAI Gym already on the same system. It keeps tripping up when t…...

Java进阶----接口interface

接口 接口概述 接口是一种规范&#xff0c;使用接口就代表着要在程序中制定规范. 制定规范可以给不同类型的事物定义功能&#xff0c;例如&#xff1a; 利用接口&#xff0c;给飞机、小鸟制定飞行规范&#xff0c;让其都具备飞行的功能&#xff1b;利用接口&#xff0c;给鼠…...

【网络协议】ISIS

ISIS IS-IS&#xff08;Intermediate System to Intermediate System&#xff0c;中间系统到中间系统&#xff09;协议是一种用于在自治系统&#xff08;AS&#xff09;内部进行路由选择的链路状态路由协议。它最初是为OSI&#xff08;开放系统互连&#xff09;网络设计的&…...

一.4 处理器读并解释储存在内存中的指令

此刻&#xff0c;hello.c源程序已经被编译系统翻译成了可执行目标文件hello&#xff0c;并被存放在硬盘上。要想在Unix系统上运行该可执行文件&#xff0c;我们将它的文件名输入到称为shell的应用程序中&#xff1a; linux>./hello hello, world linux> shell是一个命令…...

【Android面试八股文】Android性能优化面试题:怎样检测函数执行是否卡顿?

文章目录 卡顿一、可重现的卡顿二、不可重现的卡顿第一种方案: 基于 Looper 的监控方法第二种方案:基于 Choreographer 的监控方法第三种方案:字节码插桩方式第四种方案: 使用 JVMTI 监听函数进入与退出总结相关大厂的方案ArgusAPMBlockCanaryQQ空间卡慢组件Matrix微信广研参…...

C语言7 控制语句

目录 1. 条件语句 if 语句 if-else 语句 if-else if-else 语句 switch 语句 2. 循环语句 for 循环 while 循环 do-while 循环 3. 跳转语句 break 语句 continue 语句 return 语句 goto 语句 1. 条件语句 if 语句 if语句根据给定条件的真或假来决定是否执行某段…...

go mod 依赖管理补充2

依赖包的版本问题&#xff0c;别的开发语言有没有类似的问题&#xff1f;是怎么解决的&#xff1f; 举例&#xff1a;java java的依赖包的版本问题&#xff0c;通过Maven模块来操作&#xff0c;可以指定依赖包版本号&#xff0c;如下&#xff1a; go.mod 文件 go.mod文件是G…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...