news 2026/6/10 12:21:39

杂记-@Transactional使用的一点记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
杂记-@Transactional使用的一点记录

一、问题记录

在设计一个简单的重命名修改方法的时候考虑使用乐观插入+异常捕获来优化代码,其中用到了@Transaction这个注解。然而这里发现了一个致命缺陷,spring事务的问题。

  • 永远不要在@Transactional方法内部依赖try-catch来处理由数据库约束引起的RuntimeException,并期望事务能继续正常提交
原代码如下:

@Transaction private void processFileNameDuplicate(AccountFileDO accountFileDO) { String originalFileName = accountFileDO.getFileName(); Long accountId = accountFileDO.getAccountId(); Long parentId = accountFileDO.getParentId(); // 首先尝试插入原始文件名(乐观插入) try { accountFileMapper.insert(accountFileDO); return; // 插入成功,直接返回 } catch (DataIntegrityViolationException e) { // 文件名冲突,需要重命名 } // 文件名冲突处理 String baseName, extension = ""; int dotIndex = originalFileName.lastIndexOf("."); if (dotIndex > 0 && dotIndex < originalFileName.length() - 1) { baseName = originalFileName.substring(0, dotIndex); extension = originalFileName.substring(dotIndex); } else { baseName = originalFileName; } int counter = 1; String newFileName; do { newFileName = baseName + "(" + counter + ")" + extension; counter++; accountFileDO.setFileName(newFileName); try { accountFileMapper.insert(accountFileDO); return; // 成功插入新文件名 } catch (DataIntegrityViolationException e) { // 继续循环尝试下一个文件名 } } while (true); }

分析这个乐观锁加异常捕获的设计(通过数据库的插入成功与否来判断新的命名是否可用),当第一次插入判断失败,进入给文件名加后缀继续插入判断的循环,直到插入判断成功,spring仍然保留着最初的异常DataIntegrityViolationException,最终在默认的@Transaction隔离级别下会执行回滚,数据库的那一套事务会收到spring给的ROLLBACK指令。这样就会出现一个BUG,Java 对象accountFileDO的状态不会回滚,尽管被修正了,在数据库中依然查询不到。

二、设计思路和修正思路

策略方法缺陷
内存全量加载使用预查询的方法将文件名全部取到SET集合中,避免事务问题,最终加上联合唯一索引确保最终一致性;内存瓶颈:当单个目录文件数量巨大时(如超过10万),会消耗大量内存。
目标化数据库查询先乐观地检查原始名是否存在。如果存在,再用LIKEREGEXP查询只捞出相关的重名文件(如笔记(%).txt),然后在内存中计算1.数据库压力LIKEREGEXP查询可能无法完美利用索引,在千万级文件的目录下仍可能较慢。2.并发问题:依然存在“检查-操作”的竞态条件,需要数据库唯一索引来兜底。
乐观插入与异常捕获不做任何SELECT检查,直接尝试INSERT。如果成功,万事大吉。如果失败(捕获唯一键异常),则在catch块中处理重命名逻辑,再次尝试INSERT1.失败时最慢:异常的抛出和捕获、事务的回滚,开销很大。如果冲突频繁,性能会急剧下降。2.代码复杂性高:需要处理 Spring@Transactional的回滚陷阱,通常需要隔离事务,实现起来非常复杂且易错。3.设计模式争议:滥用异常来控制正常的业务流程。
架构级优化1. 分布式锁:在执行任何数据库操作前,先通过 Redis (SETNX) 等中间件获取一个代表该文件名的分布式锁,确保同一时间只有一个线程能操作这个名字。2. ID与名称解耦:文件在数据库中的唯一标识是UUID,而用户看到的文件名只是一个可变的“别名”。唯一性约束放在(parent_id, alias_name)上。1.引入新依赖:需要引入并维护 Redis 等外部系统,增加了系统复杂度。2.设计更复杂:需要从系统设计的更高层面去规划,不再是单个函数的优化。

针对于方案3,修正思路是启用一个沙箱机制,将数据库插入判断操作放到由@Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = DataIntegrityViolationException.class)注解的方法内部。

代码如下:

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.dao.DataIntegrityViolationException; @Component public class AccountFileSaver { @Autowired private AccountFileMapper accountFileMapper; /** * 在一个全新的、独立的事务中尝试插入数据。 * 这是实现策略三的关键。 * * @param accountFileDO 要插入的文件对象 * @throws DataIntegrityViolationException 如果发生唯一键冲突,则向上抛出 */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void tryInsert(AccountFileDO accountFileDO) { // 这个insert操作在自己的“事务沙箱”里运行。 // 如果成功,这个小事务就自己提交了。 // 如果因为唯一键冲突失败,它会抛出异常,并且这个小事务自己回滚。 // 关键在于,它的失败不会“污染”调用它的外部主事务。 accountFileMapper.insert(accountFileDO); } }

最终调用注入该类,调用tryInsert()方法代替原始的插入。实现外置沙箱处理掉异常,不影响外部事务。

三、使用@Transaction的一些总结

  1. Spring AOP 代理机制
  • @Transactional注解是通过 Spring AOP(面向切面编程)的代理机制来实现的。
  • 当你调用一个 Bean 的方法时(比如serviceA.methodA()),你实际调用的是 Spring 为serviceA创建的代理对象。这个代理对象在调用真实方法前后,会为你加上开启事务、提交/回滚事务的逻辑。
  • 但是,如果在一个类内部,一个方法调用同一个类的另一个方法(this.methodB()),这个调用是直接在对象内部发生的,不会经过代理。因此,methodB上的@Transactional注解就会失效
  • 所以,我们必须tryInsert方法放到另一个独立的类 (AccountFileSaver) 中,然后通过注入来调用 (accountFileSaver.tryInsert())。这样才能确保每次调用都经过代理,从而让Propagation.REQUIRES_NEW生效。
  1. 最终还是采用了策略一,简简单单
  • “文件名重复”的场景下,用多层事务嵌套设计不合理,违背了的整个流程的原子性,外层的@Transactional的事务获取到其他流程抛出的异常后发起回滚,而嵌套的独立事务已经完成提交
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 10:51:39

如何复现论文中的大模型方法并解决实际问题

关键词&#xff1a;人工智能大模型 人工智能培训 大模型培训 具身智能培训 智能体 VLA 将论文中的大模型方法应用于实际问题&#xff0c;是一个从“理论”到“实践”的转化过程。以下是系统化的步骤和建议&#xff0c;帮助你高效地实现这一目标&#xff1a; 一、理解论文方法的…

作者头像 李华
网站建设 2026/6/10 10:53:28

基于SpringBoot+Vue的学生捐赠物品管理系统设计与实现毕设

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在设计并实现一个基于SpringBoot和Vue的学生捐赠物品管理系统。该系统旨在解决当前学生捐赠物品管理过程中存在的诸多问题&#xff0c;如信息不透明、捐…

作者头像 李华
网站建设 2026/6/10 0:53:59

java计算机毕业设计校园办公管理系统 高校无纸化协同办公平台的设计与实现 基于Java的智慧校园行政事务管理系统

计算机毕业设计校园办公管理系统n97i39&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。当“让数据多跑路、让师生少跑腿”成为高校治理的新常态&#xff0c;传统纸质签批、Excel …

作者头像 李华
网站建设 2026/6/10 10:54:22

基于SpringBoot+Vue的药物评价交流平台毕设

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在构建一个基于SpringBootVue的药物评价交流平台&#xff0c;以实现药物信息的共享、评价与交流。具体研究目的如下&#xff1a; 首先&#xff0c;本研究…

作者头像 李华
网站建设 2026/6/10 10:48:41

当工具变量遇上深度学习:DeepIV如何看透因果?

引子&#xff1a;从“涨价”不一定“减量”说起 生活中有许多看似反常的经济现象。比如&#xff0c;每逢节假日&#xff0c;机票价格飙升&#xff0c;但出游的人数却不减反增&#xff0c;各大航司的机票销售依旧火爆。如果我们天真地把“价格”和“销量”这两个数据点直接拿给…

作者头像 李华
网站建设 2026/6/10 10:49:53

Windows10 永久关闭预览窗格

在 Windows 10 中&#xff0c;预览窗格&#xff08;Preview Pane&#xff09;可以方便地快速查看文件内容&#xff08;如文本、图片、视频等&#xff09;&#xff0c;但如果你希望永久关闭它&#xff0c;即使在重启后也不会自动开启&#xff0c;可以按照以下方法操作&#xff1…

作者头像 李华