Spring @Transactional 注解完全指南
一、什么是 @Transactional
在开发Spring Boot项目时,我们常常会遇到这样的场景:一个方法里执行了多步数据库操作,中间某一步出错了,导致数据出现"半成功半失败"的情况------部分数据被提交,部分被回滚。这种数据不一致问题会带来严重的业务风险。
@Transactional 就是为了解决这个问题而生的。它的核心作用是:要么所有操作都成功提交,要么全部回滚作废,保证数据库的完整性和一致性。
@Transactional 是Spring框架提供的一个注解,用于声明事务边界。它允许开发者以声明式的方式管理事务,而无需手动编写开始、提交或回滚事务的代码。
二、实现原理
@Transactional 注解基于 AOP(面向切面编程) 实现。当Spring容器初始化时,会扫描标记了此注解的方法或类,并在这些方法执行前后自动织入事务管理逻辑。
具体来说,Spring会在运行时为带有 @Transactional 注解的类生成一个代理对象(JDK动态代理或CGLIB代理)。代理对象会在目标方法执行前后添加事务管理逻辑:
- 方法执行前:开启一个新的事务
- 方法执行后:如果方法执行成功,提交事务
- 方法抛出异常:根据配置回滚事务
Spring事务管理的核心组件包括:事务管理器(PlatformTransactionManager)、事务定义(TransactionDefinition)、事务拦截器(TransactionInterceptor)等。
三、注解位置
@Transactional 可以应用于以下位置:
1. 方法级别(最常见)
java
@Service
public class UserService {
@Transactional
public void createUser(User user) {
userRepository.save(user);
}
}
2. 类级别
java
@Transactional(readOnly = true)
@Service
public class ReportService {
// 所有方法默认为只读事务
public List<Report> generateReport() { ... }
@Transactional // 覆盖类级别配置
public void updateReportStatus(int id, String status) { ... }
}
类级别的注解会应用于该类的所有public方法,方法级别的注解会覆盖类级别的配置。
3. 接口或接口方法 (不推荐) Spring团队建议使用 @Transactional 注解来注解具体类的方法,而不是依赖接口中注解的方法,因为Java注解不能从接口继承。
四、参数详解
1. value / transactionManager
功能:指定事务管理器的名称。当存在多个事务管理器时,用于指定使用哪一个。
默认值:空字符串(使用默认事务管理器)。
示例:
java
@Transactional(value = "myTransactionManager")
public void myMethod() { ... }
使用场景:多数据源场景下,需要明确指定使用哪个数据源的事务管理器。
2. propagation------事务传播行为
功能:定义事务的传播行为,即当一个事务方法被另一个事务方法调用时,应该如何处理事务。
默认值 :Propagation.REQUIRED。
| 传播行为 | 说明 |
|---|---|
| REQUIRED(默认) | 如果当前存在事务,则加入该事务;否则创建一个新事务 |
| REQUIRES_NEW | 总是创建一个新事务。如果当前存在事务,则挂起当前事务 |
| SUPPORTS | 如果当前存在事务,则加入;否则以非事务方式执行 |
| NOT_SUPPORTED | 以非事务方式执行。如果当前存在事务,则挂起该事务 |
| MANDATORY | 必须在一个现有事务中执行。如果没有事务,则抛出异常 |
| NEVER | 以非事务方式执行。如果当前存在事务,则抛出异常 |
| NESTED | 如果当前存在事务,则创建一个嵌套事务;否则等价于REQUIRED |
使用场景:
REQUIRED:绝大多数业务场景,多个操作需要在同一个事务中REQUIRES_NEW:需要独立事务的场景,如操作日志记录------即使主业务失败,日志也要保存NESTED:需要部分回滚的场景,内层事务失败不影响外层已执行的操作
3. isolation------事务隔离级别
功能:定义事务的隔离级别,控制并发事务之间的数据可见性。
默认值 :Isolation.DEFAULT(使用底层数据库的默认隔离级别)。
| 隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|
| READ_UNCOMMITTED | 允许读取未提交的数据 | ✔ | ✔ | ✔ |
| READ_COMMITTED | 只允许读取已提交的数据 | ✘ | ✔ | ✔ |
| REPEATABLE_READ | 多次读取同一数据结果一致 | ✘ | ✘ | ✔ |
| SERIALIZABLE | 最高的隔离级别,串行执行 | ✘ | ✘ | ✘ |
使用场景:
READ_COMMITTED:大多数应用的首选,平衡性能与一致性SERIALIZABLE:对数据一致性要求极高的场景(如金融系统),但性能开销大
4. timeout
功能:设置事务的超时时间,单位为秒。超过此时间事务将被强制回滚。
默认值:-1(无超时限制)。
示例:
java
@Transactional(timeout = 30) // 30秒超时
public void myMethod() { ... }
使用场景:防止事务长时间占用数据库资源,适用于预计执行时间较长的操作。
5. readOnly
功能:指定事务是否为只读事务。
默认值:false。
示例:
java
@Transactional(readOnly = true)
public List<User> findUsers() { ... }
使用场景:
- 纯查询操作设置为
true,可以帮助数据库引擎优化事务处理 - 增删改操作必须为
false(默认值)
6. rollbackFor / rollbackForClassName
功能:指定哪些异常会导致事务回滚。
默认 :Spring默认只对未捕获的 RuntimeException 和 Error 触发回滚。
示例:
java
@Transactional(rollbackFor = {SQLException.class, IOException.class})
public void myMethod() throws SQLException, IOException { ... }
使用场景:当需要让检查异常(Checked Exception)也触发回滚时。
7. noRollbackFor / noRollbackForClassName
功能:指定哪些异常不会导致事务回滚。
示例:
java
@Transactional(noRollbackFor = {DataIntegrityViolationException.class})
public void myMethod() { ... }
使用场景:某些特定异常发生时希望提交已执行的操作,而不是全部回滚。
五、实际案例
案例一:用户注册(基础用法)
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private AccountRepository accountRepository;
@Transactional(rollbackFor = Exception.class)
public void registerUser(User user, Account account) throws Exception {
// 保存用户信息
userRepository.save(user);
// 创建账户
accountRepository.save(account);
// 如果这里抛出异常,两个操作都会回滚
}
}
案例二:订单创建(事务传播)
java
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void createOrder(Order order) {
// 扣减库存
inventoryService.deductStock(order.getProductId(), order.getQuantity());
// 保存订单
orderRepository.save(order);
// 记录操作日志(独立事务)
logService.saveLog("创建订单:" + order.getId());
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String message) {
logRepository.save(new Log(message));
}
}
即使订单创建失败回滚,日志记录也会独立提交,保证操作可追溯。
案例三:批量操作(嵌套事务)
java
@Service
public class BatchService {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void batchProcess(List<Data> dataList) {
for (Data data : dataList) {
processSingle(data); // 每个单独处理,失败不影响其他
}
}
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void processSingle(Data data) {
// 单条数据处理,失败只回滚当前条
dataRepository.save(data);
}
}
六、事务失效场景与踩坑指南
场景1:方法不是 public
Spring要求被代理的方法必须是 public 的,否则事务不会生效。
java
@Service
public class UserService {
@Transactional // ❌ 无效!private方法不会生效
private void updateUser(User user) {
userRepository.save(user);
}
@Transactional // ✅ 有效
public void updateUserPublic(User user) {
userRepository.save(user);
}
}
在源码层面,AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute 方法中,如果目标方法不是 public,则 TransactionAttribute 返回 null,即不支持事务。
场景2:自身调用(同类方法调用)
这是最常见的踩坑点。当一个类内部的方法调用另一个带有 @Transactional 注解的方法时,事务不会生效。
java
@Service
public class OrderService {
public void placeOrder(Order order) {
// ❌ 直接调用,不走代理,事务失效!
this.deductInventory(order);
}
@Transactional
public void deductInventory(Order order) {
inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
}
}
原因 :Spring事务基于代理实现,自调用使用的是 this(当前对象),而不是Spring生成的代理对象,事务拦截器不会被触发。
解决方案:
方案一:拆分到不同类(推荐)
java
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
public void placeOrder(Order order) {
inventoryService.deductInventory(order); // ✅ 通过代理调用
}
}
@Service
public class InventoryService {
@Transactional
public void deductInventory(Order order) {
inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
}
}
方案二:使用 AopContext.currentProxy()
java
@EnableAspectJAutoProxy(exposeProxy = true) // 启动类上添加
@SpringBootApplication
public class Application { ... }
@Service
public class OrderService {
public void placeOrder(Order order) {
((OrderService) AopContext.currentProxy()).deductInventory(order);
}
@Transactional
public void deductInventory(Order order) { ... }
}
方案三:注入自身
java
@Service
public class OrderService {
@Autowired
private OrderService self;
public void placeOrder(Order order) {
self.deductInventory(order); // ✅ 通过代理调用
}
@Transactional
public void deductInventory(Order order) { ... }
}
场景3:异常被 try-catch 捕获且未重新抛出
如果异常被 catch 住且没有重新抛出,Spring感知不到异常,会认为方法执行成功并提交事务。
java
@Transactional
public void deleteDept(Long id) {
try {
deptMapper.delete(id);
int x = 1 / 0; // 触发异常
} catch (Exception e) {
System.out.println("发生异常,但事务未回滚"); // ❌ 异常被吃掉
}
}
正确做法:让异常抛出去,或手动抛出新的 RuntimeException。
java
@Transactional(rollbackFor = Exception.class)
public void deleteDept(Long id) {
try {
deptMapper.delete(id);
int x = 1 / 0;
} catch (Exception e) {
throw new RuntimeException("手动抛出异常,确保事务回滚", e); // ✅
}
}
场景4:异常类型不匹配
Spring默认只对 RuntimeException 和 Error 回滚。如果抛出的是检查异常(Checked Exception),事务不会回滚。
java
@Transactional // ❌ IOException不会触发回滚
public void deleteDept(Long id) throws IOException {
deptMapper.delete(id);
throw new IOException("不会回滚!");
}
解决方案 :指定 rollbackFor。
java
@Transactional(rollbackFor = Exception.class) // ✅ 所有异常都回滚
public void deleteDept(Long id) throws IOException {
deptMapper.delete(id);
throw new IOException("现在会回滚了!");
}
场景5:类未被Spring管理
如果类没有交给Spring容器管理(如缺少 @Service、@Component 等注解),@Transactional 不会生效。
java
// ❌ 没有@Service注解,不会被Spring管理
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) { ... }
}
场景6:数据库引擎不支持事务
以MySQL为例,MyISAM引擎不支持事务 ,需要使用 InnoDB引擎。
sql
-- 检查表引擎
SHOW TABLE STATUS LIKE 'your_table';
-- 修改为InnoDB
ALTER TABLE your_table ENGINE=InnoDB;
场景7:未配置事务管理器
在Spring Boot中,引入 spring-boot-starter-data-jdbc 或 spring-boot-starter-data-jpa 后会自动配置事务管理器。但在传统Spring项目中需要手动配置:
java
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
场景8:设置了不支持事务的传播行为
java
@Transactional(propagation = Propagation.NOT_SUPPORTED) // ❌ 不支持事务
public void updateOrder(Order order) { ... }
场景9:方法被 final 修饰
被 final 修饰的方法无法被代理,事务不会生效。
java
@Transactional
public final void updateOrder(Order order) { ... } // ❌ 事务失效
场景10:多线程调用
不同线程中的事务是独立的,@Transactional 无法跨线程传播事务。
七、最佳实践
-
始终指定
rollbackFor = Exception.class:确保所有异常都能触发回滚,避免检查异常导致事务不生效的坑。 -
事务范围最小化:尽量将事务控制在最小范围内,减少锁定资源的时间,提高系统并发能力。
-
避免自调用:将事务方法拆分到不同的Service类中,避免同类调用导致事务失效。
-
合理设置传播行为:理解不同传播行为的含义,选择最适合业务场景的模式。
-
只读事务加
readOnly = true:纯查询操作加上只读标志,有助于数据库优化。 -
谨慎使用
try-catch:在事务方法中捕获异常后务必重新抛出,否则事务不会回滚。 -
确保方法为
public:Spring事务代理只对public方法生效。 -
检查数据库引擎:使用MySQL时确保表引擎为InnoDB。
八、总结
@Transactional 是Spring框架中最强大也最容易被误用的注解之一。理解其背后的AOP代理机制是正确使用的关键。牢记以下几个核心要点:
- 事务基于代理实现,只有通过代理对象调用才会生效
- 默认只对 RuntimeException 回滚,建议显式指定
rollbackFor - 方法必须是 public 的
- 自调用会绕过代理,导致事务失效
- try-catch 捕获异常后必须重新抛出
掌握这些要点,就能在日常开发中优雅地使用 @Transactional,避免那些"事务明明加了却不生效"的困扰。