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. 概述 仓库地址:https://gitee.com/aopmin/multi-datasource-demo 随着项目规模的扩大和业务需求的复杂化,单一数据源已经不能满足实际开发中的需求。在许多情况下,我们需要同时操作多个数据库,或者需要将不同类型的数据存储在不…...
VUE + 小程序 关于前端循环上传附件页面卡死的问题
最开始我使用for循环,后端能正常保存,但是前端页面卡死了,开始代码是这么写的 wx.showLoading({title: 文件上传中...,mask: true // 是否显示透明蒙层,防止触摸穿透,默认:false});const {fileList} that.…...

【基础算法总结】分治—归并
分治—归并 1.排序数组2.交易逆序对的总数3.计算右侧小于当前元素的个数4.翻转对 点赞👍👍收藏🌟🌟关注💖💖 你的支持是对我最大的鼓励,我们一起努力吧!😃😃 1.排序数组 …...

基于Java+SpringMvc+Vue技术的实验室管理系统设计与实现(6000字以上论文参考)
博主介绍:硕士研究生,专注于信息化技术领域开发与管理,会使用java、标准c/c等开发语言,以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年,拥有近12年的管理工作经验,拥有较丰富的技术架…...

19_谷歌GoogLeNet(InceptionV1)深度学习图像分类算法
1.1 简介 GoogLeNet(有时也称为GoogleNet或Inception Net)是一种深度学习架构,由Google的研究团队在2014年提出,主要设计者为Christian Szegedy等人。这个模型是在当年的ImageNet大规模视觉识别挑战赛(ILSVRC…...

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

星网安全产品线成立 引领卫星互联网解决方案创新
2024年6月12日,盛邦安全(688651)成立星网安全产品线,这是公司宣布全面进入以场景化安全、网络空间地图和卫星互联网安全三大核心能力驱动的战略2.0时代业务落地的重要举措。 卫星互联网技术的快速发展,正将其塑造为全球…...
Adam自适应动量优化算法
Adam(Adaptive Moment Estimation)是一种结合了动量法和自适应学习率思想的优化算法,特别适用于训练神经网络和深度学习模型。以下是对Adam调整学习率的详细介绍及具体例子。 一、Adam调整学习率介绍 自适应学习率: Adam算法的核…...
Mac OS系统中Beyond Compare 4破解方式
文章出处 https://blog.csdn.net/qq_42418042/article/details/137544113 前言 记录实操过程,以防以后找不到了~ 实际原理是启动时删除文件,实现无限试用 实操过程 下载安装包 官网链接 https://www.scootersoftware.com/download.php 解压、移动到应…...

6000元最好的家用投影仪:当贝X5S Pro六千元配置最高画质最强
数码家电品牌发展迅速,投影同样也是一种更新迭代较快的产品类型,有时候去年还比较火的产品,今年就会被别的产品取代,就比如之前灯泡投影一直被认为是好产品的代表,但是现在国产激光投影的销量反而更高。一般来说6000元…...

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

OpenAI Gym Atari on Windows
题意:在Windows系统上使用OpenAI Gym的Atari环境 问题背景: 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
接口 接口概述 接口是一种规范,使用接口就代表着要在程序中制定规范. 制定规范可以给不同类型的事物定义功能,例如: 利用接口,给飞机、小鸟制定飞行规范,让其都具备飞行的功能;利用接口,给鼠…...
【网络协议】ISIS
ISIS IS-IS(Intermediate System to Intermediate System,中间系统到中间系统)协议是一种用于在自治系统(AS)内部进行路由选择的链路状态路由协议。它最初是为OSI(开放系统互连)网络设计的&…...

一.4 处理器读并解释储存在内存中的指令
此刻,hello.c源程序已经被编译系统翻译成了可执行目标文件hello,并被存放在硬盘上。要想在Unix系统上运行该可执行文件,我们将它的文件名输入到称为shell的应用程序中: 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
依赖包的版本问题,别的开发语言有没有类似的问题?是怎么解决的? 举例:java java的依赖包的版本问题,通过Maven模块来操作,可以指定依赖包版本号,如下: go.mod 文件 go.mod文件是G…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...

高抗扰度汽车光耦合器的特性
晶台光电推出的125℃光耦合器系列产品(包括KL357NU、KL3H7U和KL817U),专为高温环境下的汽车应用设计,具备以下核心优势和技术特点: 一、技术特性分析 高温稳定性 采用先进的LED技术和优化的IC设计,确保在…...