spring boot 整合多数据源
多数据源产生的场景
一般情况下,不会有多数据源这样的场景出现,但老项目或者特殊需求的项目,可能会有这样的场景
- 同一个应用需要访问两个数据库
- 不用数据库中间件的读写分离
注入数据源选择的时机
声明两个数据源实例,在getConnection的时候根据业务的不同,注入不同数据源的连接
环境准备
准备sql脚本,建立两个库,这里mysql为例
create database stu;
create database tech;
use stu;
create table student
(id varchar(50) not null comment '主键',name varchar(50) null comment '姓名',stu_no varchar(50) null comment '学号',constraint student_pk primary key (id)
);insert into student values ('1','张同学','111');
insert into student values ('2','李同学','222');
use tech;
create table teacher
(id varchar(50) not null comment '主键',name varchar(50) null comment '姓名',teach_no varchar(50) null comment '教师号',constraint teacher_pk primary key (id)
);insert into teacher values ('1','王老师','111');
insert into teacher values ('2','高老师','222');
实现DataSource方式实现多数据源
配置多数据源
server:port: 9000
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedatasource1:url: jdbc:mysql://shilei.tech:3306/stu?useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: root123456driver-class-name: com.mysql.cj.jdbc.Driverdatasource2:url: jdbc:mysql://shilei.tech:3306/tech?useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: root123456driver-class-name: com.mysql.cj.jdbc.Driverdruid:initial-size: 5min-idle: 1max-active: 20mybatis-plus:mapper-locations: classpath:/mapper/*.xmltype-aliases-package: com.datasource.dynamicdatasource.model
添加数据源配置
package com.datasource.dynamicdatasource.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;/*** @author sl*/
@Configuration
public class DataSourceConfig {@Bean("dataSource1")@ConfigurationProperties(prefix = "spring.datasource.datasource1")public DataSource dataSource1(){DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}@Bean("dataSource2")@ConfigurationProperties(prefix = "spring.datasource.datasource2")public DataSource dataSource2(){DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}
}
实现DataSource多数据源
package com.datasource.dynamicdatasource.config;import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;/*** @author sl* @Primary主要注入的bean*/
@Configuration
@Primary
public class DynamicDataSource implements DataSource {public static ThreadLocal<String> nameFlag = new ThreadLocal<>();@Resourceprivate DataSource dataSource1;@Resourceprivate DataSource dataSource2;@Overridepublic Connection getConnection() throws SQLException {if("student".equals(nameFlag.get())){return dataSource1.getConnection();}return dataSource2.getConnection();}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class<?> iface) throws SQLException {return false;}@Overridepublic PrintWriter getLogWriter() throws SQLException {return null;}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {}@Overridepublic int getLoginTimeout() throws SQLException {return 0;}@Overridepublic Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}
}
测试多数据源
package com.datasource.dynamicdatasource.controller;import com.datasource.dynamicdatasource.config.DynamicDataSource;
import com.datasource.dynamicdatasource.model.Student;
import com.datasource.dynamicdatasource.model.Teacher;
import com.datasource.dynamicdatasource.service.StudentService;
import com.datasource.dynamicdatasource.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @author sl*/
@RestController
public class TestDataSourceController {@Autowiredprivate StudentService studentService;@Autowiredprivate TeacherService teacherService;@GetMapping("/stu")public String getStu(){DynamicDataSource.nameFlag.set("student");List<Student> allStudent = studentService.findAllStudent();return allStudent.toString();}@GetMapping("/tech")public String getTech(){DynamicDataSource.nameFlag.set("teacher");List<Teacher> allTeacher = teacherService.findAllTeacher();return allTeacher.toString();}
}
效果如下所示
此实现方式的弊端
实现DataSource接口我们本质上只使用了一个方法,就是getConnection()这个无参的方法,其他方法,当内部调用时可能会导致错误,我们不可能实现所有的方法,所以我们继承AbstractRoutingDataSource抽象类
继承AbstractRoutingDataSource实现多数据源
AbstractRoutingDataSource的结构
可以看到AbstractRoutingDataSource继承自DataSource,提供了一些实现方法
AbstractRoutingDataSource的重要属性
targetDataSources 所有数据源 (需指定)
defaultTargetDataSource 默认数据源(需指定)
resolvedDataSources= targetDataSources 负责最终切换的数据源map 等于 tagetDataSources
继承AbstractRoutingDataSource实现多数据源
package com.datasource.dynamicdatasource.config;import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author sl* @Primary主要注入的bean*/
@Configuration
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {public static ThreadLocal<String> nameFlag = new ThreadLocal<>();@Resourceprivate DataSource dataSource1;@Resourceprivate DataSource dataSource2;@Overrideprotected Object determineCurrentLookupKey() {// 返回当前数据源的标识return nameFlag.get();}@Overridepublic void afterPropertiesSet() {// 为targetDataSources 初始化所有数据源Map<Object,Object> targetDataSources=new HashMap<>();targetDataSources.put("student",dataSource1);targetDataSources.put("teacher",dataSource2);super.setTargetDataSources(targetDataSources);// 设置默认数据源super.setDefaultTargetDataSource(dataSource1);// 循环给resolvedDataSources,也就是最终数据源mapsuper.afterPropertiesSet();}
}
determineCurrentLookupKey的作用
看一段源码,就是通过determineCurrentLookupKey获取数据源的key,然后去resolvedDataSources中取数据源,resolvedDataSources数据源其实就是targetDataSources
protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");}return dataSource;}
测试多数据源
package com.datasource.dynamicdatasource.controller;import com.datasource.dynamicdatasource.config.DynamicDataSource;
import com.datasource.dynamicdatasource.model.Student;
import com.datasource.dynamicdatasource.model.Teacher;
import com.datasource.dynamicdatasource.service.StudentService;
import com.datasource.dynamicdatasource.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @author sl*/
@RestController
public class TestDataSourceController {@Autowiredprivate StudentService studentService;@Autowiredprivate TeacherService teacherService;@GetMapping("/stu")public String getStu(){// 默认数据源就是studentList<Student> allStudent = studentService.findAllStudent();return allStudent.toString();}@GetMapping("/tech")public String getTech(){DynamicDataSource.nameFlag.set("teacher");List<Teacher> allTeacher = teacherService.findAllTeacher();return allTeacher.toString();}
}
AOP自定义注解方式+AbstractRoutingDataSource实现多数据源
数据源的切换还是使用AbstractRoutingDataSource,只不过切换方式采用aop拦截自定义注解切换数据源,这种方式也是mybatis-plus多数据源插件所采用的方式
自定义注解
package com.datasource.dynamicdatasource.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author sl*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDataSource {String value() default "student";
}
配置切面
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
package com.datasource.dynamicdatasource.aspect;import com.datasource.dynamicdatasource.annotation.MyDataSource;
import com.datasource.dynamicdatasource.config.DynamicDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** @author sl*@Aspect 标识是一个切面*/
@Aspect
@Component
public class DatasourceAspect {/*** 切点规则*/@Pointcut("@annotation(com.datasource.dynamicdatasource.annotation.MyDataSource)")public void pointcut() {}@Before("pointcut()")public void dataSourceAspect(JoinPoint joinPoint){// 获取方法Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();// 判断方法中是否添加了注解if(method.isAnnotationPresent(MyDataSource.class)){// 获取方法上的注解MyDataSource annotation = method.getAnnotation(MyDataSource.class);String value = annotation.value();// 设置数据源DynamicDataSource.nameFlag.set(value);}}
}
测试自定义注解切换数据源
@GetMapping("/tech")@MyDataSource("teacher")public String getTech(){List<Teacher> allTeacher = teacherService.findAllTeacher();return allTeacher.toString();}
dynamic-datasource多数据源组件实现多数据源
官方文档及搭建指南地址:多数据源 | MyBatis-Plus
引入依赖
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.6.1</version>
</dependency>
配置数据源
server:port: 9000
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedynamic:#设置默认的数据源或者数据源组,默认值即为masterprimary: master#严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源strict: falsedatasource:master:url: jdbc:mysql://shilei.tech:3306/stu?useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: root123456driver-class-name: com.mysql.cj.jdbc.Driverteacher:url: jdbc:mysql://shilei.tech:3306/tech?useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: root123456driver-class-name: com.mysql.cj.jdbc.Driverdruid:initial-size: 5min-idle: 1max-active: 20mybatis-plus:mapper-locations: classpath:/mapper/*.xmltype-aliases-package: com.datasource.dynamicdatasource.model
测试数据源切换
数据源切换使用@DS注解,不使用此注解,使用默认数据源,方法上使用>类上使用
package com.datasource.dynamicdatasource.controller;import com.baomidou.dynamic.datasource.annotation.DS;
import com.datasource.dynamicdatasource.model.Student;
import com.datasource.dynamicdatasource.model.Teacher;
import com.datasource.dynamicdatasource.service.StudentService;
import com.datasource.dynamicdatasource.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @author sl*/
@RestController
public class TestDataSourceController {@Autowiredprivate StudentService studentService;@Autowiredprivate TeacherService teacherService;@GetMapping("/stu")public String getStu(){List<Student> allStudent = studentService.findAllStudent();return allStudent.toString();}@GetMapping("/tech")@DS("teacher")public String getTech(){List<Teacher> allTeacher = teacherService.findAllTeacher();return allTeacher.toString();}
}
项目启动日志中可以看到两个数据源的加载信息
访问tech以及stu都能正常访问,代表动态数据源添加成功
需要注意的问题
使用多数据源要注意事务的控制,提交和回滚策略,可以观看spring多数据源事务解决方案
相关文章:

spring boot 整合多数据源
多数据源产生的场景 一般情况下,不会有多数据源这样的场景出现,但老项目或者特殊需求的项目,可能会有这样的场景 同一个应用需要访问两个数据库不用数据库中间件的读写分离 注入数据源选择的时机 声明两个数据源实例,在getConnect…...

数据集成:数据挖掘的准备工作之一
⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据…...

xml配置文件密码特殊字符处理
错误姿势: 正确姿势:采取转义符的方式 常用转义符:...

遥感数据与作物模型同化
基于过程的作物生长模拟模型DSSAT是现代农业系统研究的有力工具,可以定量描述作物生长发育和产量形成过程及其与气候因子、土壤环境、品种类型和技术措施之间的关系,为不同条件下作物生长发育及产量预测、栽培管理、环境评价以及未来气候变化评估等提供了…...

UI库DHTMLX Suite v8.2发布全新表单组件,让Web表单实现高度可定制!
DHTMLX Suite v8.2日前已正式发布,此版本的核心是DHTMLX Form,这个小部件接收了4个备受期待的新控件,如Fieldset、Avatar、Toggle和ToggleGroup。官方技术团队还为Grid和TreeGrid小部件中的页眉/页脚工具提示提供了一系列新的配置选项等。 在…...

河北省图书馆典藏《乡村振兴振兴战略下传统村落文化旅游设计》许少辉八一新著
河北省图书馆典藏《乡村振兴振兴战略下传统村落文化旅游设计》许少辉八一新著...
什么是卷积002
文章目录 前言1.卷积网络和传统网络区别2.卷积神经网络整体架构1.输入层2. 卷积层3.池化层4.全连接层 5.神经网络6.经典网络1.Alexnet2. Vgg3.Resnet 残差网络-特征提取 7.感受野 前言 大纲目录 首先链接图像颜色通道 1.卷积网络和传统网络区别 右边的就是CNN,卷…...

黑马JVM总结(八)
(1)StringTable面试题 1.8 1.6时 (2)StringTable的位置 jvm1.6时StringTable是常量池的一部分,它随着常量池存储在永久代当中,在1.7、1.8中从永久代变成了堆中,为什么做这个更改呢?…...

开源网安入选广东省网络空间安全标准化技术委员会新技术及应用安全技术工作组成员单位
近日,第二届广东省网络空间安全标准化技术委员会(GD/TC 124)(以下简称省网安标委)正式成立。为进一步发挥省网安标委在支撑网络强国建设、推进网络安全产业高质量发展过程中,示范引领核心技术攻关、创新产品…...

Nginx配置指南:如何定位、解读与优化Linux上的Nginx设置
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🐅🐾猫头虎建议程序员必备技术栈一览表📖: 🛠️ 全栈技术 Full Stack: 📚…...

辉瑞与吉利德科学:制药巨头的新冠病毒之战
来源:猛兽财经 作者:猛兽财经 总结: (1)猛兽财经认为,华尔街低估了辉瑞(PFE)和吉利德科学(GILD)的前景,因为它们在开发新冠病毒疫苗和药物方面都…...

x86架构基础汇编知识
通用寄存器 EAX 32位 函数返回值 AX 低16位 AH 高八位 AL 低八位 EBX 32位 ECX 32位 循环次数,this指针 EDX 32位 EBP 32位 栈底寄存器 ESP 32位 栈顶寄存器 ESI 源索引寄存器 EDI 目标索引寄存器 EIP 无法直接通过汇编操作 例子 mov al,0xff …...
ThreadLocal的原理
ThreadLocal是Java中的一个类,它提供了线程本地变量的功能。每个线程都可以独立地访问自己的ThreadLocal变量,并且不会受到其他线程的干扰。 public class ThreadLocal<T> { ThreadLocal的原理是通过使用一个ThreadLocalMap来存储每个线程的变量副…...

Chrome 108版(64-bit 108.0.5359.125)网盘下载
还在用Selenium的朋友们注意了,目前Chrome的最新版是116,而官方的Chromedriver只支持到115版。 可惜Google不提供旧版Chrome的下载方式,需要旧版的很难回去了。如果真的想要旧版的Chrome,只能民间自救。 我在2022年12月备份了C盘…...

Mars3d插件参考开发教程并在相关页面引入
问题场景: 1.在使用Mars3d热力图功能时,提示mars3d.layer.HeatLayer is not a constructor 问题原因: 1.mars3d的热力图插件mars3d-heatmap没有安装引用。 解决方案: 1.参考开发教程,找到相关的插件库:Mars3D 三维…...

Windows 性能突然打鸡血,靠 Bug 修复了多年顽疾
要说 的 Bug 集中地,当属资源管理器。 速度缓慢、卡顿、崩溃,不同设备、不同版本的用户都有不同的感受。 严格来说,这其实是 Windows 的传统艺能,要完美修复可不容易。 而作为小老弟的文件资源管理器,时不时来个无响…...

亚马逊封买家账号的原因有哪些
亚马逊可能封锁买家账号的原因有多种,主要是出于保护市场和维护平台秩序的考虑。以下是一些可能导致亚马逊封锁买家账号的常见原因: 1、涉及违规行为:如果买家违反了亚马逊的使用政策,如发表虚假评价、滥用退货政策、欺诈或盗窃等…...

1.0零基础尝试DCM通讯(c-store)
前言 本项目是对医院放疗及相关设备的互通互联。对dcm文件及数据协议是本项目的基础。 今天在项目组成员支持下,对dcm通讯进行了初步的尝试,有人之路,这个过程可以说是非常愉快,于是乎准备将这个愉快的过程记录,方便自己查阅和后来人。 c-store 本次的安装和测试使用的…...
vue之封装tab类组件
vue之封装tab类组件 vue之封装tab类组件CSS样式方面JS方面 vue之封装tab类组件 需求:点击【上一步】、【下一步】按钮,切换tab,且有淡入浅出的动画。 CSS样式方面 <div class"parent"><div class"childDiv" id…...

固定资产管理中净值怎么算
在资产管理的领域中,我们经常听到“净值”这个词。然而,对于许多人来说,净值的概念仍然模糊不清。本文将试图揭示固定资产管理的净值计算方法,并提供一些创新的观点。 我们需要明确什么是净值。在财务术语中,净值是…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...