Spring事务主要分为编程式和声明式两大类,前者通过PlatformTransactionManager或TransactionTemplate在代码中手动控制事务,后者通过@Transactional注解结合AOP实现事务管理,具有低侵入性和高可维护性,是现代Spring应用的首选方式。

Spring事务的实现方式,说白了,主要就两大类:一种是编程式事务,另一种是声明式事务。在我个人的经验里,声明式事务因为其便捷性和低侵入性,几乎成了现代Spring应用的首选,但了解编程式事务的底层逻辑也绝不是坏事。
解决方案
Spring框架在事务管理上提供了强大的支持,核心在于抽象了底层事务API(如JDBC、JTA、JPA等),提供了一致的编程模型。
1. 编程式事务管理这种方式需要你在代码中显式地调用事务API来管理事务的开始、提交和回滚。它提供了最细粒度的控制,但缺点是代码侵入性强,容易产生大量重复代码。
使用
PlatformTransactionManager
: 这是Spring事务抽象的核心接口。你需要注入一个
PlatformTransactionManager
实例,然后手动创建
TransactionStatus
对象,并在try-catch-finally块中进行事务的提交或回滚。
@Servicepublic 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
。
@Servicepublic 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配置: 早期项目或一些特定场景下可能会用到。通过
定义事务通知,然后用
将其织入到目标方法。这种方式配置相对繁琐,可读性也不如注解直观。
基于
@Transactional
注解: 这是目前最主流、最推荐的方式。你只需要在类或方法上添加
@Transactional
注解,Spring就会自动为其创建事务代理。
当你在方法上加上
@Transactional
注解时,Spring会创建一个代理对象,在方法执行前开启事务,方法执行成功后提交事务,如果抛出运行时异常(
RuntimeException
或
Error
)则回滚事务。你也可以通过注解的属性来控制事务的行为:
propagation
:事务的传播行为(如
REQUIRED
,
REQUIRES_NEW
等)。
isolation
:事务的隔离级别(如
READ_COMMITTED
,
REPEATABLE_READ
等)。
timeout
:事务的超时时间。
readOnly
:是否为只读事务,可以优化性能。
rollbackFor
:指定哪些异常类型需要回滚。
noRollbackFor
:指定哪些异常类型不需要回滚。
@Servicepublic 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
更严格。
NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个新的事务。
火山方舟
火山引擎一站式大模型服务平台,已接入满血版DeepSeek
99 查看详情
理解: 嵌套事务,它有点像
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上织入的事务逻辑就无法生效了。
排查:
@Servicepublic 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配置中没有
。
排查: 检查你的Spring配置类或XML文件。
事务方法内部调用了非事务方法,且非事务方法抛出了异常: 如果一个事务方法内部调用了一个没有
@Transactional
注解的方法,而这个非事务方法抛出了异常,并且这个异常没有被事务方法捕获,那么事务会回滚。但如果异常被事务方法捕获了,且没有再次抛出运行时异常或标记回滚,事务就不会回滚。
排查: 仔细审查异常的传播路径和捕获逻辑。
编程式事务与声明式事务,我该如何选择?
这其实是一个经典的取舍问题,但现代应用开发中,答案往往偏向一边。
在我看来,绝大多数情况下,你应该选择声明式事务,特别是基于
@Transactional
注解的方式。 理由很简单:
代码整洁度: 声明式事务将事务管理逻辑与业务逻辑彻底分离。你的业务代码只关心业务本身,不需要掺杂任何事务相关的API调用。这让代码看起来更干净、更易读、更专注于核心业务。开发效率: 只需要一个注解,Spring就会为你处理所有事务的开启、提交、回滚,大大减少了重复代码的编写。这简直是生产力工具。维护性: 当事务策略需要调整时(比如改变传播行为、隔离级别),你只需要修改注解的属性,而不需要深入到业务代码中去修改。这降低了维护成本和出错的可能性。
那么,什么时候会考虑编程式事务呢? 说实话,这种情况非常少见,但也不是没有。
极度细粒度的控制: 比如你需要在同一个方法内部,对不同的操作块应用不同的事务策略,甚至在某些特定条件下手动控制提交或回滚,而这些条件又非常复杂,无法通过声明式事务的属性来表达。但即使是这种场景,我也会先思考能否通过拆分方法或调整架构来用声明式实现。非Spring环境的事务集成: 比如你正在一个混合项目中,需要与一些非Spring管理的事务系统进行交互,或者你需要完全脱离Spring的事务抽象,直接操作底层的JDBC
Connection
事务。遗留系统改造: 某些老旧系统可能已经有了大量的编程式事务代码,在不进行大规模重构的情况下,继续沿用编程式可能是成本最低的选择。
总的来说,如果你正在开发一个新的Spring应用,或者对现有应用进行现代化改造,请毫不犹豫地拥抱
@Transactional
。它不仅能让你的代码更优雅,也能让你从繁琐的事务管理细节中解脱出来,把精力放在真正有价值的业务逻辑上。编程式事务更像是一个“备用方案”,在极少数特殊场景下才需要考虑。
以上就是spring 事务实现方式有哪些?的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/594573.html
微信扫一扫
支付宝扫一扫