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

news2024/11/28 18:36:53

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
    @Autowired
    private 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 {
    @Resource
    private UserMapper userMapper;
    @DS("slave")
    @PostConstruct
    public void init(){
        // 无法选择正确的数据源
        userMapper.selectById(1);
    }
}

解决方法:

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

@Component
public class MyConfiguration {

    @DS("slave")
    @EventListener
    public 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 仓库,包含源代码、文档和示例等资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/952051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab图像处理-图像缩放

基本概念 图像缩放是指将给定的图像在x轴方向按比例缩放a倍&#xff0c;在y轴方向按比例缩放b倍&#xff0c;从而获得一幅新的图像。 如果ab&#xff0c;即在x轴方向和y轴方向缩放的比率相同&#xff0c;则称这样的比例缩放为图像的全比例缩放。 如果a≠b&#xff0c;图像比…

Spring: HiddenHttpMethodFilter的用法及原理

作用&#xff1a;将html表单提交的post请求转换为put请求或delete请求发给接口。 html不支持put和delete. 一、web.xml中配置过滤器 <filter><filter-name>HiddenHttpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenH…

认识SQL sever

目录 一、数据库的概念 1.1数据库的基本概念 1.2对数据库的了解 二、数据库的分类 2.1关系型数据库&#xff08;RDBMS&#xff09;&#xff1a; 2.2非关系型数据库&#xff08;NoSQL&#xff09;&#xff1a; 2.3混合数据库&#xff1a; 2.4数据仓库&#xff1a; 2.5嵌…

二叉树中的堆

堆的概念和结构 大堆&#xff1a; 树中的任何一个父亲都大于等于孩子 小堆&#xff1a; 树中的任何一个父亲都小于等于孩子 堆在逻辑上是二叉树来存储的&#xff0c;就是在我们的想象中他是按二叉树来存储的&#xff0c;但是在实际上&#xff0c;它是以数组的形式来存储的&…

软考A计划-网络工程师-复习背熟-路由器与交换配置和网络安全

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

移动端如何适配不同的屏幕尺寸

在移动端开发中&#xff0c;适配不同的屏幕尺寸是一个重要的考虑因素。以下是一些常用的方法来实现移动端的屏幕适配&#xff1a; 使用响应式布局&#xff1a;使用CSS媒体查询和弹性布局来根据屏幕尺寸调整页面布局和元素大小。通过设置百分比、em或rem单位来实现元素的相对大小…

ICCV 2023 | 港中文MMLab: 多帧光流估计模型VideoFlow,首次实现亚像素级别误差

本文提出了一个多帧光流估计模型 VideoFlow&#xff0c;旨在充分挖掘视频中的时序信息和运动规律&#xff0c;避免当前主流方法只以两帧图片作为输入而面临的信息瓶颈&#xff0c;显著提升了光流估计的性能。 在公开的 Sintel Bechmark 上&#xff0c;VideoFlow 在 Clean 和 Fi…

过期订单关闭

由于Redis具有过期监听的功能&#xff0c;于是就有人拿它来实现过期订单关闭&#xff0c;但是这个方案并不完美。今天来聊聊11种实现订单定时关闭的方案&#xff0c;总有一种适合你&#xff01; 在电商、支付等系统中&#xff0c;一般都是先创建订单&#xff08;支付单&#x…

七牛云OSS存储

前言: 七牛云的存储项目的附件,需要开发一套七牛云的工具类,可以使用该工具类进行七牛云服务器进行文件的上传与下载操作; 七牛云的文档学习: 相关的依赖项的配置: <dependency><groupId>com.amazonaws</groupId><artifactId>aws-java-sdk-s3…

如何改善客户服务?

提供优质的客户服务是经营成功企业的一个重要方面。满意的客户更有可能成为回头客&#xff0c;并向他人推荐你的产品或服务。在当今竞争激烈的市场中&#xff0c;良好的客户服务可能是一家繁荣的企业与一家艰难生存的企业的区别。今天&#xff0c;我们就改善客户服务6个行之有效…

面试官眼中的理想候选人:如何成为他们的首选

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

测试理论与方法----测试流程第五个步骤:测试总结报告

10、通用测试技术 1、测试总结报告概述 定义&#xff1a;把测试过程和结果整理成文档&#xff0c;对发现的问题和缺陷进行分析&#xff0c;为纠正软件的质量提供依据&#xff0c;同时为后续的验收和交付打下基础。测试报告是测试阶段最后的产出&#xff0c;一份详细的测试报告…

【已解决】为什么RAR压缩包里的文件无法删除?

小伙伴们是否遇到过这样的问题&#xff1f;打开RAR压缩包&#xff0c;想要删除里面的文件却发现无法删除&#xff0c;提示“不支持当前操作”。 其实&#xff0c;这是文件在压缩的时候设置了“锁定压缩文件”&#xff0c;设置后&#xff0c;除了无法删除压缩包里的文件&#xf…

控制ATOS/HYDAC/VICKERS比例插装阀线圈放大器

驱动各种国产或进口品牌开环控制比例插装式阀的线圈&#xff0c;如SUN/ATOS/HYDAC/VICKERS/REXROTH/HYDRAFORCE等品牌比例插装式方向阀、流量阀、压力阀、叠加阀等。 比例放大器参数&#xff1a; 电源24VDC标准&#xff1b; 输入指令0-10V、4-20 mA、0-5V可选&#xff1b; …

线上排查定位OOM问题的记录

整体思路&#xff1a;把机器上的快照文件下载到本地电脑&#xff0c;用eclipse analyzer打开它&#xff0c;找到是哪一行导致oom。 注&#xff1a;日志里看不到报错信息是因为 还没有来得及记录到日志文件&#xff0c;就down了。 把该文件下载到本地电脑&#xff0c;可以借助x…

八、代理模式

一、什么是代理模式 代理模式的定义&#xff1a;由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时&#xff0c;访问对象不适合或者不能直接引用目标对象&#xff0c;代理对象作为访问对象和目标对象之间的中介。 代理模式的主要角色如下: 抽象主题&#xff08…

OutOfMemoryError内存溢出和StackOverFlowError栈溢出及解决方法

前一篇&#xff1a;JVM 内存结构 StackFlowError&#xff08;栈溢出&#xff09; 线程的堆栈存储 线程局部原始数据类型、变量、对象的引用、返回值。如果线程堆栈大小超出分配的内存限制&#xff0c;就会出现栈溢出错误。 这里用递归调演示错误 解决方法&#xff1a; 1、修…

初始Linux进程间通信(单机)

目录 1、前言 2、进程间通信的分类 管道 System V IPC POSIX IPC 3、管道 3.1 匿名管道 pipe 实例代码 管道读写规则&#xff1a; 管道特点 3.2 命名管道 创建一个命名管道 两个进程间使用命名管道进行通信示例代码 4、system V共享内存 4.1 共享内存数据结构 …

【档案专题】八、电子档案鉴定与销毁

导读&#xff1a;主要针对电子档案鉴定与销毁相关内容介绍。对从事电子档案管理信息化的职业而言&#xff0c;不断夯实电子档案管理相关理论基础是十分重要。只有通过不断梳理相关知识体系和在实际工作当中应用实践&#xff0c;才能走出一条专业化加职业化的道路&#xff0c;从…

Java虚拟机内部组成

1、栈区 public class Math {public int compute(){//一个方法对应一块栈帧内存区域int a l;int b 2;int c (a b)*10;return c; } public static void main(String[] args){Math math new, Math() ;math.compute() ;System.out.println("test");}} 栈是先进后出…