Spring的事务管理是日常开发中保障数据一致性的核心能力,很多开发者在使用@Transactional注解时,会遇到事务回滚不符合预期的情况,明明方法抛出了异常,之前执行的数据库操作却没有回滚,留下脏数据。下面我们来分析常见的失效场景和对应的解决方式。

常见事务回滚失效场景及原因
1. 异常类型不符合默认回滚规则
Spring默认的事务回滚规则是:只有当方法抛出运行时异常(RuntimeException)或者Error时,事务才会自动回滚,如果是受检异常(比如IOException、SQLException等),默认是不会触发回滚的。
比如下面的代码,抛出的是受检异常,事务不会回滚:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Service
public class UserService {
// 模拟数据库操作的方法
public void insertUser() {
// 执行插入用户操作
}
@Transactional
public void testRollback() throws IOException {
insertUser();
// 抛出受检异常,默认不会触发事务回滚
throw new IOException("文件读取失败");
}
}
2. 方法访问权限不是public
Spring的事务是基于AOP代理实现的,@Transactional注解只对public修饰的方法生效,如果方法是private、protected或者默认访问权限,注解会被直接忽略,事务自然不会生效。
错误示例如下:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
// 非public方法,@Transactional注解不生效
@Transactional
private void updateOrder() {
// 执行订单更新操作
throw new RuntimeException("更新失败");
}
}
3. 同类方法自调用导致代理失效
如果一个类中的方法A没有加事务,方法B加了@Transactional,在方法A中直接调用方法B,此时方法B的事务是不会生效的,因为自调用时调用的是目标对象本身的方法,没有经过Spring的事务代理对象。
错误示例如下:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProductService {
public void methodA() {
// 自调用methodB,methodB的事务不生效
methodB();
}
@Transactional
public void methodB() {
// 执行商品库存扣减操作
throw new RuntimeException("扣减失败");
}
}
4. 事务传播行为配置错误
如果事务的传播行为配置为Propagation.NOT_SUPPORTED或者Propagation.NEVER,那么方法执行时不会开启事务,自然也不会有回滚的效果。比如配置了Propagation.NOT_SUPPORTED,表示以非事务方式执行,如果当前存在事务则挂起当前事务。
5. 异常被方法内部捕获未抛出
如果方法内部捕获了异常,没有继续向外抛出,Spring的事务管理器感知不到异常,就不会触发回滚操作。
错误示例如下:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PayService {
@Transactional
public void pay() {
try {
// 执行支付相关数据库操作
throw new RuntimeException("支付失败");
} catch (Exception e) {
// 异常被捕获,没有抛出,事务不会回滚
e.printStackTrace();
}
}
}
对应的解决方案
1. 指定rollbackFor属性
如果方法可能抛出受检异常,需要在@Transactional注解中指定rollbackFor属性,明确哪些异常需要触发回滚,比如指定rollbackFor = Exception.class,表示所有Exception的子类异常都触发回滚。
修正后的代码如下:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Service
public class UserService {
public void insertUser() {
// 执行插入用户操作
}
// 指定所有Exception异常都触发回滚
@Transactional(rollbackFor = Exception.class)
public void testRollback() throws IOException {
insertUser();
throw new IOException("文件读取失败");
}
}
2. 确保方法为public访问权限
需要添加事务的方法必须声明为public,如果确实需要非public方法支持事务,可以考虑使用AspectJ代替Spring的默认代理方式,不过一般建议直接调整方法访问权限。
3. 避免同类自调用
可以将需要事务的方法拆分到另一个Service中,通过注入的方式调用,或者从Spring容器中获取当前类的代理对象来调用方法。比如通过AopContext.currentProxy()获取代理对象,不过需要开启exposeProxy配置。
修正后的自调用示例:
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProductService {
public void methodA() {
// 获取当前类的代理对象,调用methodB
((ProductService) AopContext.currentProxy()).methodB();
}
@Transactional
public void methodB() {
// 执行商品库存扣减操作
throw new RuntimeException("扣减失败");
}
}
4. 检查事务传播行为配置
根据业务需求配置正确的传播行为,一般默认的传播行为Propagation.REQUIRED就可以满足大部分场景,即如果当前存在事务就加入,没有就新建事务。
5. 异常不要内部吞掉
方法内部如果需要捕获异常做额外处理,处理完成后需要继续把异常抛出,让事务管理器能够感知到异常,触发回滚操作。
修正后的代码如下:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PayService {
@Transactional
public void pay() {
try {
// 执行支付相关数据库操作
throw new RuntimeException("支付失败");
} catch (Exception e) {
// 处理异常后继续抛出
e.printStackTrace();
throw e;
}
}
}
总结
Spring事务回滚失效的核心原因大多和事务的生效条件、异常感知、代理机制相关,开发时只要注意方法访问权限、异常抛出规则、自调用问题、rollbackFor配置这几个点,就能避免大部分回滚失效的场景。如果遇到问题,可以先检查这些常见点,再结合日志定位具体原因。
Spring事务事务回滚rollbackFor事务传播行为@Transactional修改时间:2026-07-24 13:06:42