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

Springboot+mybatis-plus+dynamic-datasource 切换数据源失败问题总结

Springboot+mybatis-plus+dynamic-datasource+Druid 多数据源 切换数据源失败总结

文章目录

  • Springboot+mybatis-plus+dynamic-datasource+Druid 多数据源 切换数据源失败总结
  • 0.前言
  • 1. dynamic-datasource 切换数据源失败场景总结
    • 1. spring-batch整合情况下切换数据源异常
    • 解决办法:
    • 2. 使用了Spring 原生事务注解导致多数据源切换失败
      • 解决办法
      • 原理解析:
      • 解决思路
    • 3. 内部方法调用导致的
      • 解决办法
    • 4 . Shiro框架问题
      • 原理解析
      • 解决办法
    • 5. PostConstruct初始化顺序
      • 解决方法:
    • 6. Druid版本太低
    • 7. 新开线程导致(@Async或者java8的ParallelStream并行流之类方法。)
      • 解决办法
  • 3. 参考资料

0.前言

背景 dynamic-datasource 是苞米豆(baomidou) 团队中@小锅盖开源的一款很优秀的多数据源管理组件,特别方便也很强大,在spring boot中简直就是开箱即用。但是也有很多小问题,主要是在多数据源切换失效这块。我们公司也是重依赖dynamic-datasource.也修复了一些问题。特此整理总结一下。首先我们拉出来一些issue 大家看下。

  1. 支持子线程继承主线程的数据源code https://github.com/baomidou/dynamic-datasource/issues/502
  2. 加上@DS 用的是postgrel数据库,数据源切换失败 https://github.com/baomidou/dynamic-datasource/issues/385
  3. 《多层数据源嵌套切换,开启事务的情况下无效》https://github.com/baomidou/dynamic-datasource/issues/248
  4. 关于 子线程数据源切换失败的原因,也有一些同学在github Issues 里给出了自己项目的最佳实践,不过作者考虑到该组件的通用性,以及异步场景较少的情况,给出的答复是,“用异步的少,不想支持.”。此同学还在继续说教以及列举事实,说不定,@小锅盖会在下个版本中将此缺陷完美修复,大家敬请期待
    在这里插入图片描述

在这里插入图片描述

1. dynamic-datasource 切换数据源失败场景总结

1. spring-batch整合情况下切换数据源异常

github issue 地址 https://github.com/baomidou/dynamic-datasource/issues/340

解决办法:

作者给的办法, 读的时候不用事务或者单独事务,写的时候单独开事务
在这里插入图片描述

2. 使用了Spring 原生事务注解导致多数据源切换失败

解决办法

可能需要查看调用链路上涉及的类和方法,看看是否有@Transactional注解。如果在必要的情况下需要保证多个数据库的事务一致性,你需要采用分布式事务的解决方法,seate 作者已经在mybatis-plus提交了一个PR 解决了这个问题,只需要使用@DSTransactional即可。

原理解析:

在Spring项目中,Spring提供了事务管理的功能,主要通过@Transactional这个注解实现。在被@Transactional注解的方法中,Spring会维护一个ConnectionHolder,它含有当前事务所使用的数据库连接,这就保证了在这个事务中,所有的数据库操作都在一个数据库连接中完成,也就保证了事务的原子性。

但这时如果你使用了dynamic-datasource进行数据源切换,就会出现问题。因为dynamic-datasource是通过AOP的方式,在调用方法前切换数据源的。但在@Transactional注解的方法中,Spring已经从某一个数据源获取了连接,而这个连接在整个事务中是不会变的。所以,当你在这个事务中的某个地方希望切换到另一个数据源时,dynamic-datasource虽然可以切换数据源,但已经无法改变Spring事务已经获得的那个数据库连接,导致实际的数据库操作还是在原来的数据库中进行。

解决思路

  1. 让需要切换数据源的操作不在事务中进行,如无必要不加事务原则。
  2. 如果必须要在事务中进行,那么你可能需要查看调用链路上涉及的类和方法,看看是否有@Transactional注解。如果在必要的情况下需要保证多个数据库的事务一致性,你需要采用分布式事务的解决方法或者使用本地多数据源事务注解@DSTransactional这个是seata的作者写的还是很靠谱的
  3. 如果不想@DSTransactional,可以自己使用JTA(Java Transaction API)全局事务实现框架替代《Spring Boot+Atomikos进行多数据源的分布式事务管理详解和实例》,在多个数据源之间进行事务管理。这需要相应的XA驱动的支持。但它会带来一定的性能开销,适合对数据一致性要求较高的场景,并且要有一定的技术功底,如果想图省事,就使用作者提供的@DSTransactional注解。

3. 内部方法调用导致的

这个所有基于AOP实现的功能都存在这个问题。所以在当前方法内调用。数据源切换是不会被触发的。此处不做详细解读,可聊一下动态代理条件和原理即可明白、

在Spring中,数据源切换的实现原理是基于AOP代理的。故而,如果你在方法内部调用另一个需要切换数据源的方法,

解决办法

将需要切换数据源的方法提取到另一个service中,然后在外部单独调用。

4 . Shiro框架问题

这个是官方文档中提出的,我们项目没有遇到。
在使用Shiro框架中,如果你使用@Autowired注入的类,可能会发现事务注解和缓存注解失效问题。

原理解析

在Spring框架中,对Bean实例化、初始化的过程会涉及到几个关键的拦截点,包括BeanFactoryPostProcessor、BeanPostProcessor等。在这个过程中,这些拦截点的加载顺序有其严格的优先级:

  1. BeanFactoryPostProcessor:最早加载的,它会在BeanDefinition加载完成后及Instantiation(实例化)前进行拦截,这个时候所有的Bean定义信息已经加载到Spring中,但所有的Bean都还未被实例化。

  2. BeanPostProcessor:相对BeanFactoryPostProcessor来说,它的加载时机稍晚,它会在Bean的生命周期的初始化阶段和销毁阶段进行拦截。

  3. Bean:普通的Bean的加载顺序在上述两者之后。

产生问题的关键就是其中的加载顺序。由于Shiro的ShiroFilterFactoryBean是BeanPostProcessor,所以它会比普通的Bean(如UserController,UserService等)要早加载。由于AOP就是通过BeanPostProcessor来实现的,若ShiroFilterFactoryBean已经在AOP处理之前被加载,那么在ShiroFilterFactoryBean中注入的Bean就无法被AOP进行增强(如@Transactional注解的事务增强),所以引发了依赖注入的Bean失效问题。

例如,在以下的依赖链中:

ShiroFilterFactoryBean -> SecurityManager -> UserRealm -> IUserService

IUserService下游依赖的其他 service 例如 MenuService、RoleService 等,都会由于上述原因,导致无法正常工作,用户在这些Service中使用诸如@Transactional等注解,将不会起到预期的效果。

解决办法

1. 手动获取Bean方法,可以通过ApplicationContext.getBean()手动获取;
2. 使用@Lazy注解,让Bean的初始化推迟,待AOP处理完成后再实例化Bean。

@Component
public class UserRealm extends AuthorizingRealm {@Lazy@Autowiredprivate IUserService userService;//... 省略其他无关的内容
}

5. PostConstruct初始化顺序

此处直接照搬官方给的文档
这是Spring框架的特性,涉及到Spring的生命周期和Bean初始化过程。

Spring容器创建Bean对象的过程中,会按照以下顺序进行:

1.实例化:使用反射机制,根据配置文件创建对应的Bean对象。

2.属性赋值:根据配置文件中,使用set方法进行属性赋值。

3.Bean后置处理器Before:在Bean对象初始化前进行一些处理。

4.初始化:对Bean进行一些自定义的初始化。

  • 对象实现了InitializingBean接口,会执行afterPropertiesSet方法。

  • 对象有配置@PostConstruct注解的方法,会执行该方法。

  • 在配置文件中通过init-method指定的初始化方法。
    5. Bean后置处理器After:在Bean对象初始化后进行一些处理。

通过以上的顺序可以看出,当执行用户设定的初始化(包括@PostConstruct,afterPropertiesSet,init-method)时,Bean后置处理器(包括AOP)还未执行,因此在这个阶段是无法获取到AOP增强后的代理对象的。

初始化包括:PostConstruct注解,InitializingBean接口,自定义init-method。在这个阶段,任何AOP都无效。

@Component
public class MyConfiguration {@Resourceprivate UserMapper userMapper;@DS("slave")@PostConstructpublic void init(){// 无法选择正确的数据源userMapper.selectById(1);}
}

解决方法:

监听容器启动完成事件, 在容器完成后做初始化。

@Component
public class MyConfiguration {@DS("slave")@EventListenerpublic void onApplicationEvent(ContextRefreshedEvent event) {// 成功选择正确的数据源userMapper.selectById(1);}
}

相关spring源码 : `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean

6. Druid版本太低

如果你的Druid版本过低,可能会在高并发下出现数据源切换失效的问题。

解决办法:更新你的Druid版本到1.1.22及以上版本。

7. 新开线程导致(@Async或者java8的ParallelStream并行流之类方法。)

在使用@Async或者java8的ParallelStream等需要新开线程的场景下,数据源切换同样会出现失效问题。
在处理并发任务时,Spring的@Async注解或者Java 8的ParallelStream是我们常用的工具。它们可以帮助我们在新的线程中处理任务,提高程序的运行效率。然而,由于这些新的线程是独立于原来的线程的,原来线程中的数据源切换并不能传递到新的线程中,这就可能导致在新的线程中对数据库的操作还是使用的原来线程的数据源,不能正确地进行数据源切换。

解决办法

在新开的方法上添加对应的DS注解。可以通过在新的方法上添加对应的DS注解来解决这个问题。DS注解可以指定这个方法使用的数据源,这样即使这个方法在新的线程中被执行,它还是会使用我们通过DS注解指定的数据源。

例如:

@DS("second")
@Async
public void asyncMethod() {// 这个方法将在新的线程中执行,并使用"second"数据源
}

同样,对于java8的ParallelStream并行流执行的方法,我们也可以在方法上添加DS注解来指定数据源:

@DS("second")
public void parallelMethod() {List<String> data = ...;data.parallelStream().forEach(item -> {asyncMethod() // 这个方法将在新的线程中执行,并使用"second"数据源});
}

3. 参考资料

  1. dynamic-datasource GitHub 仓库 ↗:dynamic-datasource 的官方 GitHub 仓库,包含源代码、文档和示例等资源。

相关文章:

Springboot+mybatis-plus+dynamic-datasource 切换数据源失败问题总结

Springbootmybatis-plusdynamic-datasourceDruid 多数据源 切换数据源失败总结 文章目录 Springbootmybatis-plusdynamic-datasourceDruid 多数据源 切换数据源失败总结0.前言1. dynamic-datasource 切换数据源失败场景总结1. spring-batch整合情况下切换数据源异常解决办法&am…...

QuantLib学习笔记——InterestRate的应用

⭐️ 单利还是复利 巴菲特老爷子有句名言&#xff1a;“人生就像滚雪球&#xff0c;重要的是发现很湿的雪和很长的坡。” 很湿的雪&#xff0c;指的就是复利。很长的坡&#xff0c;指的就是时间。很湿的雪和很长的坡组合起来&#xff0c;就能滚成巨大的雪球。 哈哈&#xff0…...

记录--解决前端内存泄漏:问题概览与实用解决方案

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 内存泄漏是前端开发中的一个常见问题&#xff0c;可能导致项目变得缓慢、不稳定甚至崩溃。在本文中&#xff0c;我们将深入探讨在JavaScript、Vue和React项目中可能导致内存泄漏的情况&#xff0c;并提…...

IP初学习

1.IP报文 首部长度指的是报头长度&#xff0c;用于分离报头和有效载荷 2.网段划分 IP地址 目标网络 目标主机 3.例子 4.特殊的IP地址 5.真正的网络环境 6.调制解调器 “猫”&#xff0c;学名叫宽带无线猫 7.NAT 源IP在内网环境不断被替换 8.私有IP不能出现在公网上 因…...

live5555 testProgs目录

文章目录 测试testProgs视频流直播流注意&#xff1a; 测试 testProgs 当涉及到许多示例程序时&#xff0c;解释每一个都可能会变得非常冗长。然而&#xff0c;我可以为你提供一些关键示例程序的简要解释&#xff0c;以帮助你了解每个示例的用途和功能&#xff1a; testOnDem…...

yolov5模型s,l,m,x的区别

yolov5s 是什么&#xff1f;yolov5系列最小的模型&#xff0c;s是small。 适合什么情况下使用&#xff1f;适合在计算资源有限的设备上使用。如移动设备或边缘设备。 速度和准确率&#xff1a;速度最快&#xff0c;准确率最低。 输入分辨率&#xff1a;通常为640x640 # Param…...

Springboot 实践(13)spring boot 整合RabbitMq

前文讲解了RabbitMQ的下载和安装&#xff0c;此文讲解springboot整合RabbitMq实现消息的发送和消费。 1、创建web project项目&#xff0c;名称为“SpringbootAction-RabbitMQ” 2、修改pom.xml文件&#xff0c;添加amqp使用jar包 <!-- RabbitMQ --> <dependency&g…...

YoloV8改进策略:轻量级Slim Neck打造极致的YoloV8

文章目录 摘要Yolov8官方结果源码改进方法测试结果总结摘要 论文链接:https://arxiv.org/ftp/arxiv/papers/2206/2206.02424.pdf 作者研究了增强 CNN 学习能力的通用方法,例如 DensNet、VoVNet 和 CSPNet,然后根据这些方法的理论设计了 Slim-Neck 结构。 使用轻量级卷积…...

使用java代码给Excel加水印,代码全,进阶版

以下代码&#xff0c;亲测可以跑通 1、上一篇博客用了Apache POI库3.8的版本的形式对Excel加了水印&#xff0c;但是最近主线版本用了4.1.2的形式&#xff0c;由于为了保持版本的兼容性&#xff0c;下面有开发了Apache POI的4.1.2的版本号的方案。 pom文件为&#xff1a; <d…...

day37:网编day4,多点通信和并发服务器

一、广播接收方&#xff1a; #include <myhead.h>#define ERR_MSG(msg) do{\ fprintf(stderr,"__%d__\n",__LINE__);\ perror(msg);\ }while(0)#define BRD_IP "192.168.114.255" #define BRD_PORT 8888int main(int argc, const char *argv[]) {//…...

STM32 硬件IIC 控制OLED I2C卡死问题

1. STM32L151C8T6 硬件IIC 控制OLED 屏&#xff0c;OLED 驱动IC CH1116G, 查阅OLED 数据手册 2. STM32 硬件IIC 初始化&#xff0c;用的标准库&#xff0c;固件库 // stm32l151c8t6 as master, oled control ic (CH1116G) as slave, and communicate by master iic2 void STM3…...

Redis图文指南

1、什么是 Redis&#xff1f; Redis&#xff08;REmote DIctionary Service&#xff09;是一个开源的键值对数据库服务器。 Redis 更准确的描述是一个数据结构服务器。Redis 的这种特殊性质让它在开发人员中很受欢迎。 Redis不是通过迭代或者排序方式处理数据&#xff0c;而是…...

C++17 std::string_view介绍与使用

std::string_view介绍 std::string_view是C17增加的新内容。它是一个轻量级的、只读的字符串视图&#xff0c;可以用来表示一个字符串或字符串的一部分。std::string_view可以提高代码的可读性、可维护性和性能。 std::string_view与std::string的主要区别在于&#xff0c;st…...

写得了代码,焊得了板!嵌入式开发工程师必修之代码管理方案(下)

目录 极狐GitLab嵌入式开发场景解决方案 3.1 高可用部署与灾备 3.2 组织管理 3.3 分支策略 3.4 分支保护 3.5 推送规则 3.6 代码评审 3.7 数据保护 3.8 其他相关 本文来自 武让 极狐GitLab 高级解决方案架构师 &#x1f4a1; 前两篇文章&#xff0c;作者介绍了嵌入式开…...

Matlab论文插图绘制模板第110期—水平双向柱状图

在之前的文章中&#xff0c;分享了很多Matlab柱状图的绘制模板&#xff1a; 进一步&#xff0c;再来看一种特殊的柱状图&#xff1a;水平双向柱状图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行下…...

【广州华锐互动】VR全景工厂虚拟导览,虚拟现实技术提升企业数字化信息管理水平

随着工业4.0的到来&#xff0c;VR工厂全景制作成为了越来越多工业企业的选择。传统的工厂管理方式往往存在诸多问题&#xff0c;如信息不对称、安全隐患等。为了解决这些问题&#xff0c;VR工厂全景制作应运而生&#xff0c;它通过结合虚拟现实现实技术和数据采集技术&#xff…...

idea 创建mybatis xml文件时找不到

1、File >Settings 如图 &#xff1a; 2、添加模板&#xff1a;如下图 3、添加xml模板 如下图&#xff1a; 模板内容&#xff1a; <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//E…...

Python钢筋混凝土结构计算.pdf-混凝土构件计算

计算原理&#xff1a; 代码实现&#xff1a; #钢筋混凝土参数 def c_hrb(): global fcuk,HRB,Ec,fc,ft,ftk,Es,fy,fyp,fyk global a1,epsilon_cu fcukEcfcftftk0.0 HRBEsfyfypfyk0.0 #矩形应力图系数a1&#xff0c;C50以下为1.0 a11.0 #正截面混凝土极限压应变epsilon_cu&#…...

mysql5.7-基于docker-compose搭建主从同步

一、环境信息 系统版本&#xff1a;CentOS Linux release 7.9.2009 (Core) cat /etc/centos-release Docker版本&#xff1a;Docker version 20.10.6, build 370c289 docker --version Docker-compose版本&#xff1a;Docker Compose version v2.10.2 docker-compose --versio…...

【USRP】调制解调系列5:16QAM、32QAM、64QAM、256QAM、1024QAM、基于labview的实现

QAM 正交振幅键控是一种将两种调幅信号&#xff08;2ASK和2PSK&#xff09;汇合到一个信道的方法&#xff0c;因此会双倍扩展有效带宽&#xff0c;正交调幅被用于脉冲调幅。正交调幅信号有两个相同频率的载波&#xff0c;但是相位相差90度&#xff08;四分之一周期&#xff0c…...

Odoo|5分钟创建自定义的业务系统唯一序列号

在业务操作中&#xff0c;经常会遇到需要生成全局唯一序列号数据的情况&#xff0c;比如订单号、报价单号等。为了确保数据的唯一性和准确性&#xff0c;通常我们会使用Redis或其他分布式锁机制来实现。然而&#xff0c;很多人可能不知道&#xff0c;odoo框架本身提供了一个原生…...

mysql索引为什么提高查询速度(底层原理)

一、索引原理图 二、索引数据存储到硬盘而不是内存&#xff1f; 硬盘内存 成本低成本高 容量大容量小 读写速度一般读取速度快 断电后数据永久存储断电后数据清空 三、硬盘数据为什么要读取到内存&#xff1f;为啥不直接…...

算法通关村——位运算在查找重复元素中的妙用

用4KB内存寻找重复元素 给定一个数组&#xff0c;包含从1到N的整数&#xff0c;N最大为32000&#xff0c;数组可能还有重复值&#xff0c;且N的取值不定&#xff0c;若只有4KB的内存可用&#xff0c;该如何打印数组中所有重复元素。 如果不要求使用4KB&#xff0c;最简单就是…...

使用环境中的视觉地标和扩展卡尔曼滤波器定位移动机器人研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

【python基础知识】5.for循环和while循环

文章目录 前言for...in...循环语句for循环&#xff1a;空房间for循环&#xff1a;一群排队办业务的人range()函数for循环&#xff1a;办事流程 while循环while循环&#xff1a;放行条件while循环&#xff1a;办事流程 两种循环对比 前言 上一关&#xff0c;我们学习了两种新的…...

STM32CUBEMX_创建时间片轮询架构的软件框架

STM32CUBEMX_创建时间片轮询架构的软件框架 说明&#xff1a; 1、这种架构避免在更新STM32CUBEMX配置后把用户代码清除掉 2、利用这种时间片的架构可以使得代码架构清晰易于维护 创建步骤&#xff1a; 1、使用STM32CUBEMX创建基础工程 2、新建用户代码目录 3、构建基础的代码框…...

vue 插槽Slots

vue插槽官网 <button class"fancy-btn"><slot></slot> <!-- 插槽出口 --> </button><slot> 元素是一个插槽出口 (slot outlet)&#xff0c;标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。 // 定义一个Child.vue…...

论文阅读《Nougat:Neural Optical Understanding for Academic Documents》

摘要 科学知识主要存储在书籍和科学期刊中&#xff0c;通常以PDF的形式。然而PDF格式会导致语义信息的损失&#xff0c;特别是对于数学表达式。我们提出了Nougat&#xff0c;这是一种视觉transformer模型&#xff0c;它执行OCR任务&#xff0c;用于将科学文档处理成标记语言&a…...

较难的换根dp:P6213 「SWTR-04」Collecting Coins

传送门 前题提要:感觉这道换根dp可以说是集中了换根dp的所有较高难度的操作和思想,以及较高的一些实现细节,可以说能够完全写出这道题才叫真正理解了换根dp,非常值得一做. 首先读完题意,不难发现这道题有很多限制.点的访问次数限制,必须访问某一个点,想要获得最大的贡献,没有…...

Springboot - 15.二级分布式缓存集成-Caffeine

&#x1f440;中文文档 Caffeine &#x1f440;使用Caffeine &#xff08;本地缓存&#xff09; 当与Spring Boot结合使用时&#xff0c;Caffeine提供了一个直观且功能强大的二级缓存解决方案。Spring Boot的缓存抽象使得整合Caffeine变得相当简单。以下是如何在Spring Boot…...