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

【java】@Transactional导致@DS注解切换数据源失效

  最近业务中出现了多商户多租户的逻辑,所以需要分库,项目框架使用了mybatisplus所以我们自然而然的选择了同是baomidou开发的dynamic.datasource来实现多数据源的切换。在使用初期程序运行都很好,但之后发现在调用com.baomidou.mybatisplus.extension.service.IService.saveBatch时@DS切换数据源会失效。

问题原因

  进入saveBatch方法我们可以看到方法上添加了Transactional,我们知道Transactional用来管理事务,在事务开启后进行数据库的切换时并不会生效,源代码如下,当线程持有数据库连接时会复用当前线程绑定的数据库连接,否则绑定默认的主库连接,既然最终连接到主库,说明@DS并没有生效。

尝试解决问题step1

  前往Github的dynamic-datasource代码仓库查看Issues,发现了大量的关于@DS多数据源切换无效的Issues,but官方看起来很傲慢,要么直接回复未复现,要么直接关闭。

  只有一条信息比较有用,在调用被Transactional注解的方法的方法或类上添加@DS注解,我试了有效果。

  但是我认为在Service和方法上加@DS注解并不合适,Spring框架就是因为清晰明了的分层结构深受大家喜爱,控制层专注Web,Service层专注业务逻辑,持久层专注数据库交互,所以@DS数据库切换放在Mapper我觉得是合理的,而不应该为了解决问题硬生生的放在方法和类上来破坏这种分层结构。况且mybatisplus中那么多添加了Transactional的方法在调用的地方我都需要重写并添加@DS这太2了。

尝试解决问题step2

  离开Github我马上找google度娘,毕竟我遇到的这点问题前辈们可能早就遇到了并给出了解决方案。

这里不得不吐槽一下中文技术博客的现状,很多偷文贼将别人的文章偷走,也不标转载自哪里,导致大量博客内容雷同且存在很多词不达意的内容。因为喜欢所以才会分享表达,不喜欢不热爱你说你偷别人文章干啥。

  根据搜索引擎的结果,主要分为3种解决方案。

  1. 在Service类或者方法上添加@DS注解
  2. 在调用带有Transactional注解的方法前切换数据库
  3. 自己实现TransactionManager在使用Transactional时手动指定来替换Spring默认的DataSourceTransactionManager

  方案1在step1我自己并不认可

  方案2相对方案1更加灵活,毕竟因为在方法中切换,可以根据不同的Service来获取需要切换的数据源,但这种方案个人觉得侵入性太强,需要对使用了mybatisplus批量方法的Service全部进行处理

  方案3我认为风险太高,自己实现TransactionManager事务、异步、同步等都需要考虑到还要保证单元测试尽可能的覆盖,我不认为短时间内能做的比迭代了多年的框架更好,所以也放弃

尝试解决问题step3

  我们知道Spring因为AOP特性可以轻松的实现在不对原有代码侵入的情况下对特定内容进行增强,所以我决定使用切面编程mybatisplus中带有Transactional注解的方法进行拦截,然后手动切换数据库,注册切面部分很快完成,剩下的就是调试数据库切换。

  数据库切换我使用了dynamic.datasource包内的DynamicDataSourceContextHolder.push方法,但遗憾的是数据库切换一直不成功并卡了很久,期间使用DynamicRoutingDataSource.setPrimary方法将需要使用的数据库指定为主库运行成功,但这种骚操作肯定不合适,将别的库指定为主库风险肯定很大。

  之后就是漫长的Debug,不断的F7、F8,一直没有头绪,我在方法上添加了@DS注解,并关闭了我的切面类再进行调试,突然发现了点不一样的东西,不知道有没有敏感的同学发现端倪。

  请关注chain变量,里面包含3个拦截器,更重要的是动态数据库切换的拦截器在事务拦截器前面,而我们的目的不就是在事务开启前切换数据库吗,那我现在的问题就是我的切面类在事务后执行的,所以调整我的切面类执行优先级就好了,立马把Order注解抬上来,执行程序完美运行。

切面类最终代码

  如果你也遇到了调用mybatisplus中批量方法无法切换多数据源的话,可直接拷贝安全食用,不会对现有的人和代码侵入和更改。如果你只是处理Transactional和@DS的冲突,你可以对切面类的作用范围小小修改即可解决你的问题。

package com.spman.common.aspect;import com.alibaba.fastjson2.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.lang.reflect.Field;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Order(0)
@Component
public class MyBatisPlusServiceTransactionalAspect {/*** 存储当前切面主动切换的数据库, 在方法执行完成后主动出栈*/private static final ThreadLocal<String> DS_KEY = new ThreadLocal<>();@Pointcut("execution(* com.baomidou.mybatisplus.extension.service.IService+.*(..))")public void myBatisPlusMethodPointcut() {}@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")public void transactionalPointcut() {}@Before("myBatisPlusMethodPointcut() && transactionalPointcut()")public void beforeHandler(JoinPoint joinPoint) {String argsJson = JSON.toJSONString(joinPoint.getArgs());ServiceImpl<?, ?> target = (ServiceImpl<?, ?>)joinPoint.getTarget();String methodName = target.getClass().getTypeName() + "." + joinPoint.getSignature().getName();log.info("MyBatisPlusServiceAspect拦截到{}开始执行, 参数列表->{}", methodName, argsJson);Class<? extends BaseMapper<?>> mapperClass = getMapperClass(target);DS dsAnnotation = getDSAnnotation(mapperClass);if (dsAnnotation == null) {log.info("{}未绑定DS注解, 跳过数据源切换", mapperClass.getName());} else {DS_KEY.set(dsAnnotation.value());DynamicDataSourceContextHolder.push(dsAnnotation.value());log.info("{}已绑定DS注解, 已主动切换数据源为{}", mapperClass.getName(), dsAnnotation.value());}}@After("myBatisPlusMethodPointcut() && transactionalPointcut()")public void afterHandler(JoinPoint joinPoint) {String dsKey = DS_KEY.get();ServiceImpl<?, ?> target = (ServiceImpl<?, ?>)joinPoint.getTarget();String methodName = target.getClass().getTypeName() + "." + joinPoint.getSignature().getName();if (dsKey != null && !dsKey.isEmpty()) {DynamicDataSourceContextHolder.poll();log.info("DS_KEY线程变量为{}, 已执行数据源变量出栈操作", dsKey);} else {log.info("DS_KEY线程变量不存在, 跳过数据源变量出栈操作");}log.info("MyBatisPlusServiceAspect拦截到{}结束执行", methodName);}/*** 从ServiceImpl中获取service绑定的mapper** @param target ServiceImpl实例*/@SneakyThrowsprivate Class<? extends BaseMapper<?>> getMapperClass(ServiceImpl<?, ?> target) {Field mapperClassField = target.getClass().getSuperclass().getDeclaredField("mapperClass");mapperClassField.setAccessible(true);return (Class<? extends BaseMapper<?>>) mapperClassField.get(target);}/*** 根据BaseMapper接口获取标记的DS注解** @param clazz 继承自BaseMapper的mapper接口*/public static DS getDSAnnotation(Class<? extends BaseMapper<?>> clazz) {if (clazz == null) return null;DS target = clazz.getAnnotation(DS.class);// 找不到DS注解时从继承的接口上继续查找if (target == null) {for (Class<?> parentInterface: clazz.getInterfaces()) {target = getDSAnnotation((Class<? extends BaseMapper<?>>)parentInterface);if (target != null) return target;}}return target;}
}

如果真的需要解决问题还是需要自己耐心的跟进,拒绝为了解决问题而解决问题。

参考资料

[1] mybatisplus官网: https://baomidou.com/

[2] dynamic-datasource代码仓库: https://github.com/baomidou/dynamic-datasource

[3] Spring之AOP的详细讲解: https://blog.csdn.net/m0_74097410/article/details/137476783

相关文章:

【java】@Transactional导致@DS注解切换数据源失效

最近业务中出现了多商户多租户的逻辑&#xff0c;所以需要分库&#xff0c;项目框架使用了mybatisplus所以我们自然而然的选择了同是baomidou开发的dynamic.datasource来实现多数据源的切换。在使用初期程序运行都很好&#xff0c;但之后发现在调用com.baomidou.mybatisplus.ex…...

003 SpringBoot集成Kafka操作

4.SpringBoot集成Kafka 文章目录 4.SpringBoot集成Kafka1.入门示例2.yml完整配置3.关键配置注释说明1. 生产者优化参数2. 消费者可靠性配置3. 监听器高级特性4. 安全认证配置 4.配置验证方法5.不同场景配置模板场景1&#xff1a;高吞吐日志收集场景2&#xff1a;金融级事务消息…...

Android SystemUI开发(一)

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUI.java frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java 关键文件 SystemUI 关键服务 简介 Dependency.class&#xff1a;处理系统依赖关系&#xff0c;提供资源或服…...

C#贪心算法

贪心算法&#xff1a;生活与代码中的 “最优选择大师” 在生活里&#xff0c;我们常常面临各种选择&#xff0c;都希望能做出最有利的决策。比如在超市大促销时&#xff0c;面对琳琅满目的商品&#xff0c;你总想用有限的预算买到价值最高的东西。贪心算法&#xff0c;就像是一…...

Vue程序下载

Vue是一个基于JavaScript&#xff08;JS&#xff09;实现的框架&#xff0c;想要使用它&#xff0c;就得先拿到Vue的js文件 Vue官网 Vue2&#xff1a;Vue.js Vue3&#xff1a;Vue.js - 渐进式 JavaScript 框架 | Vue.js 下载并安装vue.js 第一步&#xff1a;打开Vue2官网&a…...

【UCB CS 61B SP24】Lecture 17 - Data Structures 3: B-Trees学习笔记

本文以 2-3-4 树详细讲解了 B 树的概念&#xff0c;逐步分析其操作&#xff0c;并用 Java 实现了标准的 B 树。 1. 2-3 & 2-3-4 Trees 上一节课中讲到的二叉搜索树当数据是随机顺序插入的时候能够使得树变得比较茂密&#xff0c;如下图右侧所示&#xff0c;时间复杂度也就…...

机器学习决策树

一、香农公式 熵&#xff1a; 信息增益&#xff1a; 信息增益信息熵-条件熵 前者是初始信息熵大小&#xff0c;后者是因为条件加入后带来的确定性增加 信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度 信息增益越大说明影响越大 二、代码 ""&…...

Spring Boot + MyBatis 实现 RESTful API 的完整流程

后端开发&#xff1a;Spring Boot 快速开发实战 引言 在现代后端开发中&#xff0c;Spring Boot 因其轻量级、快速开发的特性而备受开发者青睐。本文将带你从零开始&#xff0c;使用 Spring Boot MyBatis 实现一个完整的 RESTful API&#xff0c;并深入探讨如何优雅地处理异…...

通过 ANSYS Discovery 进行 CFD 分析,增强工程设计

概括 工程师使用计算流体动力学 (CFD) 分析来研究和优化各种应用中的流体流动和传热分析。ANSYS Discovery 是一个用户友好的软件平台&#xff0c;使工程师能够轻松设置和解决 CFD 模型&#xff0c;并能够通知设计修改 在这篇博文中&#xff0c;我们将重点介绍在 Ansys Disc…...

家用可燃气体探测器——家庭燃气安全的坚实防线

随着社会的发展和变迁&#xff0c;天然气为我们的生活带来了诸多便利&#xff0c;无论是烹饪美食&#xff0c;还是温暖取暖&#xff0c;都离不开它的支持。然而&#xff0c;燃气安全隐患如影随形&#xff0c;一旦发生泄漏&#xff0c;可能引发爆炸、火灾等严重事故&#xff0c;…...

ListControl双击实现可编辑

为Edit Control控件添加丢失输入焦点事件,可见设为false 为List Control控件添加双击事件 控件和成员变量之间交换数据 CListCtrl ListPrint1; //列表输出 CEdit...

ave-form.vue 组件中 如何将产品名称发送给后端 ?

如何将产品名称发送给后端。 在这段代码中&#xff0c;产品名称&#xff08;productName&#xff09;的处理和发送主要发生在 save() 方法中。让我逐步分析&#xff1a; 产品ID的选择&#xff1a; <w-form-selectv-model"form.productId"label"涉及产品&q…...

DeepSeek行业应用实践报告-智灵动力【112页PPT全】

DeepSeek&#xff08;深度搜索&#xff09;近期引发广泛关注并成为众多企业/开发者争相接入的现象&#xff0c;主要源于其在技术突破、市场需求适配性及生态建设等方面的综合优势。以下是关键原因分析&#xff1a; 一、技术核心优势 开源与低成本 DeepSeek基于开源架构&#xf…...

【Markdown 语法简洁讲解】

Markdown 语法简洁语法讲解 什么是 Markdown1. 标题2. 列表3.文本样式4. 链接与图片5. 代码6. 表格7. 分割线8. 流程图9. 数学公式10. 快捷键11. 字体、字号与颜色 什么是 Markdown Markdown 是一种轻量级标记语言&#xff0c;通过简单的符号实现排版格式化&#xff0c;专注于…...

250301-OpenWebUI配置DeepSeek-火山方舟+硅基流动+联网搜索+推理显示

A. 最终效果 B. 火山方舟配置&#xff08;一定要点击添加&#xff09; C. 硅基流动配置&#xff08;最好要点击添加&#xff0c;否则会自动弹出所有模型&#xff09; D. 联网搜索配置 E. 推理过程显示 默认是没有下面的推理过程的显示的 设置步骤&#xff1a; 在Functions函…...

【3天快速入门WPF】12-MVVM

目录 1. 什么是MVVM2. 实现简单MVVM2.1. Part 12.2. Part 21. 什么是MVVM MVVM 是 Model-View-ViewModel 的缩写,是一种用于构建用户界面的设计模式,是一种简化用户界面的事件驱动编程方式。 MVVM 的目标是实现用户界面和业务逻辑之间的彻底分离,以便更好地管理和维护应用…...

查找Excel包含关键字的行(の几种简单快速方法)

需求&#xff1a;数据在后缀为xlsx的Excel的sheet1中且量比较大&#xff0c;比如几十万行几百列&#xff1b;想查找一个关键字所在的行,比如"全网首发"&#xff1b; 情况①知道关键字在哪一列 情况②不确定在哪一列&#xff0c;很多列相似又不同&#xff0c;本文演…...

性能测试分析和调优

步骤 性能调优的步骤 性能调优的步骤&#xff1a; 1.确定问题&#xff1a;根据性能测试的结果来分析确定bug。–测试人员职责 2.分析原因&#xff1a;分析问题产生的原因。----开发人员职责 3.给出解决方案&#xff1a;可以是修改软件配置、增加硬件资源配置、修改代码等----…...

(视频教程)Compass代谢分析详细流程及python版-R语言版下游分析和可视化

不想做太多的前情解说了&#xff0c;有点累了&#xff0c;做了很久的内容&#xff0c;包括整个分析&#xff0c;从软件安装和报错解决到后期下游python版-R语言版下游分析和可视化&#xff01;单细胞代谢分析我们写过很多了&#xff0c;唯独少了最“高级”的compass&#xff0c…...

【SQL】MySQL中的字符串处理函数:concat 函数拼接字符串,COALESCE函数处理NULL字符串

MySQL中的字符串处理函数&#xff1a;concat 函数 一、concat &#xff08;&#xff09;函数 1.1、基本语法1.2、示例1.3、特殊用途 二、COALESCE&#xff08;&#xff09;函数 2.1、基本语法2.2、示例2.3、用途 三、进阶练习 3.1 条件和 SQL 语句3.2、解释 一、concat &…...

MM32 MCU烧录故障排查指南:从硬件到软件的全面解析

1. 硬件问题排查&#xff1a;从电源到接口的全面检查 遇到MM32 MCU烧录失败时&#xff0c;硬件问题往往是首要排查方向。我遇到过不少新手朋友一上来就怀疑芯片质量问题&#xff0c;结果折腾半天发现是电源没接好。硬件问题排查建议按照"供电→接口→调试器"的顺序进…...

OpenClaw+百川2-13B-4bits量化模型:个人知识管理自动化方案

OpenClaw百川2-13B-4bits量化模型&#xff1a;个人知识管理自动化方案 1. 为什么需要自动化知识管理 作为一个长期与技术文档打交道的开发者&#xff0c;我的知识库在过去三年膨胀到了2000篇杂乱无章的Markdown文件。每次查找资料时&#xff0c;要么记不清文件名&#xff0c;…...

ALIGN vs CLIP:哪个更适合你的多模态项目?详细对比与选型指南

ALIGN vs CLIP&#xff1a;多模态模型选型实战指南 当你在构建一个需要同时理解图像和文本的AI系统时&#xff0c;ALIGN和CLIP这两个名字一定会频繁出现。作为2023年最炙手可热的多模态模型&#xff0c;它们都能将视觉和语言映射到同一个语义空间&#xff0c;但设计哲学和适用场…...

OpenClaw多模型切换指南:Qwen3-4B与Llama3混合调用策略

OpenClaw多模型切换指南&#xff1a;Qwen3-4B与Llama3混合调用策略 1. 为什么需要多模型切换&#xff1f; 去年夏天&#xff0c;当我第一次尝试用OpenClaw自动化处理技术文档时&#xff0c;发现单一模型很难满足所有需求。代码生成任务需要模型有严谨的逻辑性&#xff0c;而文…...

不用精确模型也能控?手把手教你用Matlab实现MFAC控制算法(附完整代码)

零基础实现MFAC控制&#xff1a;Matlab实战指南与参数调优全解析 在控制工程实践中&#xff0c;我们常常遇到这样的困境&#xff1a;面对一个复杂的非线性系统&#xff08;比如实验室里的倒立摆或者工厂中的液位控制装置&#xff09;&#xff0c;传统的PID控制效果不佳&#xf…...

K8s网络策略深度实验:用NetworkPolicy实现微服务隔离(含Calico实战)

K8s网络策略深度实验&#xff1a;用NetworkPolicy实现微服务隔离&#xff08;含Calico实战&#xff09; 在云原生架构中&#xff0c;微服务间的网络隔离是安全工程师必须掌握的核心技能。当多个租户或业务线共享同一个Kubernetes集群时&#xff0c;不加控制的Pod间通信可能引发…...

SevenSegmentSerial:HT16K33七段数码管多协议驱动库

1. SevenSegmentSerial 库概述SevenSegmentSerial 是一个专为 SparkFun 七段数码管串行显示模块&#xff08;型号&#xff1a;COM-11629、COM-12781、Qwiic-enabled COM-15432 等&#xff09;设计的轻量级嵌入式驱动库。该模块内部集成 HT16K33 驱动芯片&#xff0c;支持 4 位共…...

ATmega328P ADC底层控制库:精度、功耗与实时性深度优化

1. 项目概述AnalogControlPanel&#xff08;ACP&#xff09;是一个专为ATmega328P系列Arduino平台&#xff08;Uno、Nano、Pro Mini&#xff09;设计的底层ADC控制库。它并非替代analogRead()的简易封装&#xff0c;而是一套面向嵌入式工程师的、对AVR片上模数转换器&#xff0…...

AdminBSB表单组件实战:从基础到高级的完整解决方案

AdminBSB表单组件实战&#xff1a;从基础到高级的完整解决方案 【免费下载链接】AdminBSBMaterialDesign AdminBSB - Free admin panel that is based on Bootstrap 3.x with Material Design 项目地址: https://gitcode.com/gh_mirrors/ad/AdminBSBMaterialDesign Admi…...

Topit:让Mac窗口像便利贴一样随手可贴,你的多任务效率神器

Topit&#xff1a;让Mac窗口像便利贴一样随手可贴&#xff0c;你的多任务效率神器 【免费下载链接】Topit Pin any window to the top of your screen / 在Mac上将你的任何窗口强制置顶 项目地址: https://gitcode.com/gh_mirrors/to/Topit 还在为Mac上频繁切换窗口而打…...