Spring事务的实现方式,说白了,主要就两大类:一种是编程式事务,另一种是声明式事务。在我个人的经验里,声明式事务因为其便捷性和低侵入性,几乎成了现代Spring应用的首选,但了解编程式事务的底层逻辑也绝不是坏事。
解决方案
Spring框架在事务管理上提供了强大的支持,核心在于抽象了底层事务API(如JDBC、JTA、JPA等),提供了一致的编程模型。
1. 编程式事务管理 这种方式需要你在代码中显式地调用事务API来管理事务的开始、提交和回滚。它提供了最细粒度的控制,但缺点是代码侵入性强,容易产生大量重复代码。
-
使用
PlatformTransactionManager
: 这是Spring事务抽象的核心接口。你需要注入一个PlatformTransactionManager
实例,然后手动创建TransactionStatus
对象,并在try-catch-finally块中进行事务的提交或回滚。@Service public class MyService { private final PlatformTransactionManager transactionManager; public MyService(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public void doSomethingTransactional() { DefaultTransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); try { // 业务逻辑操作 // 例如:dao.insertDataA(); dao.updateDataB(); transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw new RuntimeException("Transaction failed", e); } } }
-
使用
TransactionTemplate
: 这是Spring提供的一个模板类,它封装了事务的创建、提交、回滚等boilerplate代码,让你的业务逻辑更聚焦。它在内部还是使用了PlatformTransactionManager
。@Service public class MyService { private final TransactionTemplate transactionTemplate; public MyService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } public void doSomethingTransactionalWithTemplate() { transactionTemplate.execute(status -> { try { // 业务逻辑操作 // 例如:dao.insertDataC(); dao.deleteDataD(); return "Success"; } catch (Exception e) { status.setRollbackOnly(); // 标记事务为回滚 throw new RuntimeException("Transaction failed", e); } }); } }
2. 声明式事务管理 这是Spring推荐的方式,通过AOP(面向切面编程)实现。你不需要在业务代码中显式编写事务管理逻辑,只需通过配置(XML或注解)来声明哪些方法需要事务支持。Spring会在运行时通过代理为这些方法织入事务管理功能。
基于XML配置: 早期项目或一些特定场景下可能会用到。通过
<tx:advice>
定义事务通知,然后用<aop:config>
将其织入到目标方法。这种方式配置相对繁琐,可读性也不如注解直观。-
基于
@Transactional
注解: 这是目前最主流、最推荐的方式。你只需要在类或方法上添加@Transactional
注解,Spring就会自动为其创建事务代理。当你在方法上加上
@Transactional
注解时,Spring会创建一个代理对象,在方法执行前开启事务,方法执行成功后提交事务,如果抛出运行时异常(RuntimeException
或Error
)则回滚事务。你也可以通过注解的属性来控制事务的行为:propagation
:事务的传播行为(如REQUIRED
,REQUIRES_NEW
等)。isolation
:事务的隔离级别(如READ_COMMITTED
,REPEATABLE_READ
等)。timeout
:事务的超时时间。readOnly
:是否为只读事务,可以优化性能。rollbackFor
:指定哪些异常类型需要回滚。noRollbackFor
:指定哪些异常类型不需要回滚。
@Service public class UserService { // 假设有用户数据访问对象 // private final UserRepository userRepository; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void createUserAndAssignRole(User user, Role role) { // userRepository.save(user); // roleRepository.assign(user.getId(), role.getId()); // 如果这里有任何异常,整个方法的操作都会回滚 } @Transactional(readOnly = true) public User getUserById(Long id) { // return userRepository.findById(id); return null; } }
在我看来,
@Transactional
注解的简洁性简直是开发者的福音,它让业务代码保持纯粹,极大地提升了开发效率和代码可维护性。
Spring事务的传播行为有哪些,如何理解?
事务传播行为,这玩意儿听起来有点玄乎,但其实就是当一个方法调用另一个方法时,这两个方法的事务如何相互作用的规则。理解这些规则,对于避免一些难以察觉的事务问题至关重要。Spring定义了七种传播行为,但日常开发中常用的也就那么几种。
-
REQUIRED
(默认也是最常用):如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务。- 理解: 想象你正在进行一项任务(事务A),这时你需要调用一个子任务(方法B)。如果子任务B也要求在事务中运行,那么它会“搭上”你当前任务A的事务这趟车。如果任务A没有事务,子任务B会自己开一辆新车。这是最安全、最常用的选择,确保方法总是在事务环境中运行。
-
SUPPORTS
:如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务方式执行。- 理解: 子任务B对事务持“支持”态度。有事务就跟着走,没事务也不强求,自己干自己的活。适合那些可有可无事务支持的操作,比如查询。
-
MANDATORY
:如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。- 理解: 子任务B是“强制”要求有事务的。如果没有事务,它就直接罢工(抛异常)。这适用于那些必须在事务环境下才能正确执行的关键操作。
-
REQUIRES_NEW
:总是创建一个新的事务,如果当前存在事务,就将当前事务挂起。- 理解: 子任务B是个“独立”的个体,它不关心你有没有事务,它每次都会开一辆新车,并且把你原来的车(事务)暂时停在一边。子任务B自己的事务提交或回滚,不会影响到它外部的事务。这在需要确保某个操作独立提交或回滚时非常有用,比如日志记录。
-
NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就将当前事务挂起。- 理解: 子任务B“不支持”事务。如果它发现你带着事务来了,它会让你把事务先放一边,自己以非事务方式执行。适合那些不需要事务,甚至会干扰事务的操作。
-
NEVER
:以非事务方式执行操作,如果当前存在事务,就抛出异常。-
理解: 子任务B“绝不”允许有事务。如果你带着事务来了,它就直接报错。这比
NOT_SUPPORTED
更严格。
-
理解: 子任务B“绝不”允许有事务。如果你带着事务来了,它就直接报错。这比
-
NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个新的事务。PIA
全面的AI聚合平台,一站式访问所有顶级AI模型
226 查看详情
-
理解: 嵌套事务,它有点像
REQUIRED
,但又不同。它会在父事务中创建一个“保存点”(savepoint)。如果嵌套事务回滚,它只会回滚到这个保存点,而不会影响到父事务的提交或回滚。但如果父事务回滚,它会连同嵌套事务一起回滚。这需要底层数据库支持保存点。我个人觉得,这个用得相对较少,而且理解起来也容易混淆,实际项目中我倾向于用REQUIRES_NEW
来明确隔离。
-
理解: 嵌套事务,它有点像
为什么我的Spring事务不生效?常见陷阱与排查。
说实话,
@Transactional注解虽然好用,但有时它就是不生效,这真是让人抓狂。遇到这种情况,别急着骂Spring,多半是有些细节没注意到。这里我总结几个常见的“坑”和排查思路:
-
方法不是
public
的: 这是最常见的。Spring的AOP代理(无论是JDK动态代理还是CGLIB代理)默认只对public
方法生效。如果你把@Transactional
放在private
、protected
或默认(包可见)方法上,它是不会生效的。-
排查: 检查你的事务方法是不是
public
的。
-
排查: 检查你的事务方法是不是
-
同一个类中方法A调用方法B(自调用问题): 这是一个非常经典的陷阱。当你在同一个Service类中,一个没有
@Transactional
注解的方法A调用了另一个有@Transactional
注解的方法B时,事务可能不会生效。原因: Spring的事务是通过代理实现的。当你通过
this
关键字调用同一个类中的方法时,实际上是绕过了Spring生成的代理对象,直接调用了目标对象的方法。这样一来,代理在方法B上织入的事务逻辑就无法生效了。-
排查:
@Service public class MyService { @Transactional // 这个事务不会生效 public void methodB() { System.out.println("Method B executed."); // ... 业务逻辑 ... } public void methodA() { System.out.println("Method A executed."); this.methodB(); // 直接通过this调用,绕过了代理 } }
-
解决方案:
- 将
methodB
移到一个独立的Service类中。 - 通过Spring上下文获取当前Service的代理对象来调用(不推荐,代码丑陋)。
- 使用
AopContext.currentProxy()
获取当前代理对象,然后调用((MyService) AopContext.currentProxy()).methodB();
(需要开启exposeProxy = true
)。 - 我个人更倾向于第一种,职责分离也更清晰。
- 将
-
异常类型不对,或者异常被捕获了: 默认情况下,Spring事务只对运行时异常(
RuntimeException
及其子类)和Error
进行回滚。如果你抛出的是受检异常(Checked Exception,如IOException
、SQLException
等),而又没有明确配置@Transactional(rollbackFor = MyCheckedException.class)
,事务是不会回滚的。-
排查: 检查你的业务逻辑中抛出的异常类型。如果捕获了异常,确保在捕获后重新抛出运行时异常,或者手动调用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
来标记回滚。
-
排查: 检查你的业务逻辑中抛出的异常类型。如果捕获了异常,确保在捕获后重新抛出运行时异常,或者手动调用
-
数据库不支持事务: 某些数据库存储引擎(比如MySQL的MyISAM)是不支持事务的。虽然Spring的事务配置没问题,但底层数据库不支持,自然也就无法实现事务。
- 排查: 检查你的数据库表使用的存储引擎(例如MySQL的InnoDB是支持事务的)。
-
没有启用Spring的事务管理: 忘记在Spring配置中启用事务管理,比如没有
@EnableTransactionManagement
注解(在Spring Boot中通常会自动配置),或者XML配置中没有<tx:annotation-driven/>
。- 排查: 检查你的Spring配置类或XML文件。
-
事务方法内部调用了非事务方法,且非事务方法抛出了异常: 如果一个事务方法内部调用了一个没有
@Transactional
注解的方法,而这个非事务方法抛出了异常,并且这个异常没有被事务方法捕获,那么事务会回滚。但如果异常被事务方法捕获了,且没有再次抛出运行时异常或标记回滚,事务就不会回滚。- 排查: 仔细审查异常的传播路径和捕获逻辑。
编程式事务与声明式事务,我该如何选择?
这其实是一个经典的取舍问题,但现代应用开发中,答案往往偏向一边。
在我看来,绝大多数情况下,你应该选择声明式事务,特别是基于
@Transactional注解的方式。 理由很简单:
- 代码整洁度: 声明式事务将事务管理逻辑与业务逻辑彻底分离。你的业务代码只关心业务本身,不需要掺杂任何事务相关的API调用。这让代码看起来更干净、更易读、更专注于核心业务。
- 开发效率: 只需要一个注解,Spring就会为你处理所有事务的开启、提交、回滚,大大减少了重复代码的编写。这简直是生产力工具。
- 维护性: 当事务策略需要调整时(比如改变传播行为、隔离级别),你只需要修改注解的属性,而不需要深入到业务代码中去修改。这降低了维护成本和出错的可能性。
那么,什么时候会考虑编程式事务呢? 说实话,这种情况非常少见,但也不是没有。
- 极度细粒度的控制: 比如你需要在同一个方法内部,对不同的操作块应用不同的事务策略,甚至在某些特定条件下手动控制提交或回滚,而这些条件又非常复杂,无法通过声明式事务的属性来表达。但即使是这种场景,我也会先思考能否通过拆分方法或调整架构来用声明式实现。
-
非Spring环境的事务集成: 比如你正在一个混合项目中,需要与一些非Spring管理的事务系统进行交互,或者你需要完全脱离Spring的事务抽象,直接操作底层的JDBC
Connection
事务。 - 遗留系统改造: 某些老旧系统可能已经有了大量的编程式事务代码,在不进行大规模重构的情况下,继续沿用编程式可能是成本最低的选择。
总的来说,如果你正在开发一个新的Spring应用,或者对现有应用进行现代化改造,请毫不犹豫地拥抱
@Transactional。它不仅能让你的代码更优雅,也能让你从繁琐的事务管理细节中解脱出来,把精力放在真正有价值的业务逻辑上。编程式事务更像是一个“备用方案”,在极少数特殊场景下才需要考虑。
以上就是spring 事务实现方式有哪些?的详细内容,更多请关注知识资源分享宝库其它相关文章!
相关标签: mysql 工具 ai 应用开发 数据访问 api调用 动态代理 spring框架 为什么 red mysql spring spring boot 架构 封装 子类 try catch xml Error 接口 class public private protected finally 对象 this 数据库 重构 应用开发 大家都在看: mysql怎样分区? 面试官:MySQL 是如何实现 ACID 的? 单工、半双工、全双工 Java中的JDBC是如何连接和操作数据库的?(请解释Java数据库连接(JDBC)的工作原理及其用法。) PHP中的Smarty模板引擎是如何工作的?(请解释Smarty模板引擎在PHP中的应用和原理。)
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。