文章目录
- 前言
- [1. 方法非 public](#1. 方法非 public)
- [2. 内部 this 调用](#2. 内部 this 调用)
- [3. 异常被 catch 且未重新抛出](#3. 异常被 catch 且未重新抛出)
- [4. rollbackFor 未指定受检异常](#4. rollbackFor 未指定受检异常)
- [5. 数据库引擎不支持事务](#5. 数据库引擎不支持事务)
- [6. 多线程调用](#6. 多线程调用)
- [7. 传播属性配置不当](#7. 传播属性配置不当)
- [8. 未被 Spring 管理的类](#8. 未被 Spring 管理的类)
前言
Spring 事务基于 AOP 动态代理实现,核心流程是:当调用被 @Transactional 标注的方法时,实际调用的是代理对象的方法。代理对象会在方法执行前开启事务,在方法正常结束后提交事务,在方法抛出特定异常(默认 RuntimeException 或 Error)时回滚事务。
假设有一个 Service 原始类
java
@Service
public class UserServiceImpl implements UserService {
@Transactional
@Override
public void createUser(User user) {
userMapper.insert(user); // ← 核心业务:插入数据库
}
}
Spring 在启动时,会为 UserServiceImpl 生成一个代理类,大概内容如下(简化版):
java
// 这是 Spring 动态生成的代理类(伪代码,实际更复杂)
public class UserServiceProxy implements UserService {
// 持有原始对象
private UserService target;
@Override
public void createUser(User user) {
// -------- 秘书的"前置工作"(AOP 前置通知)--------
TransactionStatus status = transactionManager.begin(); // 1. 开启事务
try {
// -------- 老板干正事(调用原始对象的方法)--------
target.createUser(user); // 2. 实际业务:插入数据库
// -------- 秘书的"善后工作"(AOP 后置通知)--------
transactionManager.commit(status); // 3. 提交事务
} catch (Exception e) {
transactionManager.rollback(status); // 4. 异常回滚
throw e;
}
}
}
当我们调用 userService.createUser(user) 时,实际执行的是代理对象的 createUser 方法,而不是原始对象的方法。 原始对象的方法被包裹在代理之中,整个流程受到了事务管理的保护。
1. 方法非 public
Spring AOP 只能拦截 public 方法,如果目标方法是 protected、private 或默认(包)可见,代理无法拦截,事务自然不生效。
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
// 错误:非 public 方法
void updateUser(User user) {
userMapper.updateById(user);
throw new RuntimeException("测试回滚");
}
}
此时抛出的异常不会导致事务回滚,数据库数据仍然被更新。
- 解决方案将方法修饰符改为 public 即可
2. 内部 this 调用
在同一类中,一个无事务的方法通过 this 调用另一个带 @Transactional 的方法,调用的是原始对象而不是代理对象,事务注解被忽略。
java
@Service
public class OrderService {
public void placeOrder(Order order) {
// this 调用,绕过了代理
this.createOrder(order);
}
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
throw new RuntimeException("测试回滚");
}
}
- 解决方案1:从 Spring 容器中注入自身的代理对象,用代理调用事务方法。
java
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入自己的代理
public void placeOrder(Order order) {
self.createOrder(order);
}
@Transactional
public void createOrder(Order order) { ... }
}
- 解决方案2:通过 AopContext.currentProxy() 获取代理对象。
先在启动类或配置类上启用暴露代理:
java
@EnableAspectJAutoProxy(exposeProxy = true)
java
public void placeOrder(Order order) {
((OrderService) AopContext.currentProxy()).createOrder(order);
}
3. 异常被 catch 且未重新抛出
@Transactional 默认只在抛出 RuntimeException 或 Error 时才回滚。如果异常被 catch 块捕获并且没有再次抛出,Spring 根本不知道发生了异常,事务会正常提交。
java
@Transactional
public void transfer() {
try {
accountMapper.debit();
int i = 1 / 0; // 抛出 ArithmeticException
accountMapper.credit();
} catch (Exception e) {
log.error("transfer error", e);
// 异常被吞,事务未感知,仍然提交
}
}
- 解决方案:在 catch 块中重新抛出运行时异常。或者手动标记事务回滚。
java
} catch (Exception e) {
log.error("transfer error", e);
throw new RuntimeException(e);
}
java
} catch (Exception e) {
log.error("transfer error", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
4. rollbackFor 未指定受检异常
如果业务抛出的异常是受检异常(Exception 而非 RuntimeException),默认情况下 Spring 不会回滚。
java
@Transactional
public void createFile() throws IOException {
fileService.write();
throw new IOException("文件写入失败"); // 受检异常,默认不回滚
}
- 解决方案:明确指定 rollbackFor = Exception.class,让任何异常都触发回滚。
java
@Transactional(rollbackFor = Exception.class)
public void createFile() throws IOException { ... }
5. 数据库引擎不支持事务
MySQL 的 MyISAM 引擎不支持事务,若表使用该引擎,@Transactional 无论如何配置都不会生效。
- 解决方案:将表的存储引擎改为 InnoDB(支持事务和行级锁)。
java
ALTER TABLE `user` ENGINE = InnoDB;
6. 多线程调用
Spring 事务通过 ThreadLocal 绑定数据库连接,事务范围限定在同一个线程内。如果在事务方法内部启动新线程执行数据库操作,新线程中的操作不在当前事务中。
java
@Transactional
public void process() {
userMapper.update();
new Thread(() -> {
orderMapper.insert(); // 在另一个线程,与当前事务无关
}).start();
throw new RuntimeException("回滚");
}
- 解决方案:避免在事务中开启多线程。若必须使用多线程,可考虑编程式事务或分布式事务(如 Seata)来协调多个数据库操作。
7. 传播属性配置不当
@Transactional 的 propagation 属性控制事务的传播行为。例如默认 REQUIRED 会加入当前事务,但如果设为 NOT_SUPPORTED(非事务执行)或 NEVER(不能有事务),会导致事务不生效。
java
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void update() {
userMapper.update(); // 不在事务中执行
}
- 解决方案:根据业务需求选择合适的传播属性,大多数情况使用默认 REQUIRED。
8. 未被 Spring 管理的类
只有被 Spring 容器管理的 Bean 才能享受 AOP 代理和事务功能。如果类没有通过 @Service、@Component 等注解注册,或者通过 new 手动创建对象,@Transactional 将无效。
java
// 未加 @Service 等注解,不在 Spring 容器中
public class NotManagedService {
@Transactional
public void doSomething() { ... }
}
- 解决方案:确保类被 Spring 扫描并管理,例如添加 @Service。
java
java