一、Spring 事务的底层原理
1. 核心机制
- 动态代理(AOP):
 Spring 通过动态代理(JDK 或 CGLIB)生成代理对象,拦截被@Transactional注解标记的方法。
- 事务拦截器:
 TransactionInterceptor负责管理事务的生命周期(开启、提交、回滚)。
- 事务管理器:
 PlatformTransactionManager实现类(如DataSourceTransactionManager)负责底层事务操作(如 JDBC 的commit())。
- 线程绑定:
 通过ThreadLocal(TransactionSynchronizationManager)存储当前事务的数据库连接,确保同一线程内多个操作共享同一事务。
2. 关键流程
// 伪代码:事务拦截器逻辑
 public Object invoke(MethodInvocation invocation) {
 // 1. 获取事务属性(@Transactional配置)
 TransactionAttribute txAttr = getTransactionAttribute(invocation.getMethod());
// 2. 获取事务管理器
PlatformTransactionManager tm = determineTransactionManager(txAttr);// 3. 开启事务(根据传播行为决定是否新建事务)
TransactionStatus status = tm.getTransaction(txAttr);try {// 4. 执行目标方法Object result = invocation.proceed();// 5. 提交事务tm.commit(status);return result;
} catch (Exception ex) {// 6. 回滚事务(根据rollbackFor规则)completeTransactionAfterThrowing(txAttr, status, ex);throw ex;
}
}
二、常见陷阱及代码示例
陷阱 1:自调用导致事务失效
问题:同类内部方法调用(未经过代理对象),事务注解失效。
@Service
 public class UserService {
 public void createUser() {
 // 直接调用内部方法,事务不生效!
 this.insertUser();
 }
@Transactional
public void insertUser() {// 插入用户到数据库
}
}
原因:this.insertUser() 是目标对象直接调用,未经过代理对象,事务拦截器未被触发。
解决:
- 方法 1:注入自身代理对象(通过 AopContext):
@EnableAspectJAutoProxy(exposeProxy = true) // 启动类开启暴露代理
 public class UserService {
 public void createUser() {
 UserService proxy = (UserService) AopContext.currentProxy();
 proxy.insertUser(); // 通过代理对象调用
 }
 }
- 方法 2:拆分类,将 insertUser放到另一个 Bean 中。
陷阱 2:异常被捕获未抛出
问题:事务方法中捕获异常但未重新抛出,导致事务无法回滚。
@Transactional
 public void updateUser() {
 try {
 userDao.update(user); // 可能抛出SQLException
 } catch (SQLException e) {
 // 捕获异常但未抛出,事务不会回滚!
 log.error(“更新失败”, e);
 }
 }
原因:Spring 默认只对 RuntimeException 和 Error 回滚,且必须抛出异常。
解决:
- 方法 1:抛出 RuntimeException:
catch (SQLException e) {
 throw new RuntimeException(“更新失败”, e); // 触发回滚
 }
- 方法 2:配置 @Transactional(rollbackFor = SQLException.class)。
陷阱 3:事务传播行为误解
问题:嵌套事务未按预期回滚。
@Transactional
 public void outerMethod() {
 userDao.insertUser();
 try {
 innerService.innerMethod();
 } catch (Exception e) {
 // 期望 innerMethod 回滚,但 outerMethod 继续提交
 }
 }
@Service
 public class InnerService {
 @Transactional(propagation = Propagation.REQUIRES_NEW)
 public void innerMethod() {
 userDao.updateUser(); // 抛出异常
 }
 }
现象:如果 innerMethod 抛出异常,innerMethod 的事务会回滚,但 outerMethod 的事务仍会提交(因为 innerMethod 的事务是独立的)。
解决:
- 如果希望 outerMethod在innerMethod失败时整体回滚,需在outerMethod中不捕获异常,或重新抛出异常。
陷阱 4:数据库引擎不支持事务
问题:使用 MyISAM 引擎的 MySQL 表不支持事务。
CREATE TABLE user (
 id INT PRIMARY KEY
 ) ENGINE=MyISAM; – 不支持事务
现象:即使代码正确配置事务,操作仍不会回滚。
解决:使用 InnoDB 引擎:
CREATE TABLE user (…) ENGINE=InnoDB;
陷阱 5:非 public 方法事务失效
问题:@Transactional 标记在非 public 方法上,事务不生效。
@Service
 public class UserService {
 @Transactional
 private void internalUpdate() { // 非 public 方法!
 userDao.update(user);
 }
 }
原因:Spring 默认通过代理实现 AOP,无法拦截 private/protected 方法。
解决:
- 将方法改为 public。
- 使用 AspectJ 模式(配置 @EnableTransactionManagement(mode = AdviceMode.ASPECTJ))。
陷阱 6:多线程下事务上下文丢失
问题:新线程无法继承原线程的事务上下文。
@Transactional
 public void process() {
 new Thread(() -> {
 userDao.updateUser(); // 新线程无法共享事务
 }).start();
 }
原因:TransactionSynchronizationManager 使用 ThreadLocal,不同线程无法共享事务资源。
解决:
- 避免在事务方法中启动新线程操作数据库。
- 使用编程式事务管理(手动控制事务边界)。
三、总结
关键点
- 动态代理 + 事务管理器 + ThreadLocal 是 Spring 事务的核心。
- 自调用、异常处理、传播行为、数据库支持 是常见陷阱。
- 通过代码审查、日志(如 AbstractPlatformTransactionManager的DEBUG日志)排查问题。
最佳实践
- 使用 @Transactional时明确指定rollbackFor。
- 避免同类自调用(通过代理对象或拆分类)。
- 确保数据库引擎支持事务(如 InnoDB)。
- 事务方法保持 public修饰符。