当前位置: 首页 > 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…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...