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

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 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场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error

在前端开发中&#xff0c;JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作&#xff08;如 Promise、async/await 等&#xff09;&#xff0c;开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝&#xff08;r…...

高抗扰度汽车光耦合器的特性

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