Spring事务主要分为编程式和声明式两大类,前者通过PlatformTransactionManager或TransactionTemplate在代码中手动控制事务,后者通过@Transactional注解结合AOP实现事务管理,具有低侵入性和高可维护性,是现代Spring应用的首选方式。
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配置: 早期项目或一些特定场景下可能会用到。通过
定义事务通知,然后用将其织入到目标方法。这种方式配置相对繁琐,可读性也不如注解直观。
基于@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
(默认也是最常用):如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务。
SUPPORTS
:如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务方式执行。
MANDATORY
:如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。
REQUIRES_NEW
:总是创建一个新的事务,如果当前存在事务,就将当前事务挂起。
NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就将当前事务挂起。
NEVER
:以非事务方式执行操作,如果当前存在事务,就抛出异常。
NOT_SUPPORTED更严格。
NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个新的事务。
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类中。
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的事务配置没问题,但底层数据库不支持,自然也就无法实现事务。
没有启用Spring的事务管理: 忘记在Spring配置中启用事务管理,比如没有
@EnableTransactionManagement注解(在Spring Boot中通常会自动配置),或者XML配置中没有
。
事务方法内部调用了非事务方法,且非事务方法抛出了异常: 如果一个事务方法内部调用了一个没有
@Transactional注解的方法,而这个非事务方法抛出了异常,并且这个异常没有被事务方法捕获,那么事务会回滚。但如果异常被事务方法捕获了,且没有再次抛出运行时异常或标记回滚,事务就不会回滚。
编程式事务与声明式事务,我该如何选择?
这其实是一个经典的取舍问题,但现代应用开发中,答案往往偏向一边。
在我看来,绝大多数情况下,你应该选择声明式事务,特别是基于@Transactional
注解的方式。 理由很简单:
那么,什么时候会考虑编程式事务呢? 说实话,这种情况非常少见,但也不是没有。
Connection事务。
总的来说,如果你正在开发一个新的Spring应用,或者对现有应用进行现代化改造,请毫不犹豫地拥抱
@Transactional。它不仅能让你的代码更优雅,也能让你从繁琐的事务管理细节中解脱出来,把精力放在真正有价值的业务逻辑上。编程式事务更像是一个“备用方案”,在极少数特殊场景下才需要考虑。