知识模块
☕ Java 知识模块
八、Spring 全家桶
Spring 事务失效

Spring 事务失效

失效场景汇总

场景原因
方法非 publicAOP 代理限制
同类方法调用不走代理
异常被捕获无异常抛出
异常类型不匹配默认只回滚 RuntimeException
数据库不支持事务如 MyISAM
类未被 Spring 管理无代理
方法是 final/static无法重写

详细分析

1. 方法非 public

@Service
public class UserService {
    
    @Transactional
    private void createUser() {  // 失效
        userDao.insert(new User());
    }
    
    @Transactional
    protected void updateUser() {  // 失效
        userDao.update(new User());
    }
}
 
// 原因:Spring AOP 基于代理,private/protected 方法无法被代理

2. 同类方法调用

@Service
public class UserService {
    
    public void methodA() {
        methodB();  // 直接调用,不走代理
    }
    
    @Transactional
    public void methodB() {
        userDao.insert(new User());
    }
}
 
// 解决方案一:注入自己
@Service
public class UserService {
    
    @Autowired
    private UserService self;  // 注入自己
    
    public void methodA() {
        self.methodB();  // 走代理
    }
    
    @Transactional
    public void methodB() {
        userDao.insert(new User());
    }
}
 
// 解决方案二:使用 AopContext
@Service
public class UserService {
    
    public void methodA() {
        ((UserService) AopContext.currentProxy()).methodB();
    }
    
    @Transactional
    public void methodB() {
        userDao.insert(new User());
    }
}
 
// 需要配置:@EnableAspectJAutoProxy(exposeProxy = true)

3. 异常被捕获

@Service
public class UserService {
    
    @Transactional
    public void createUser() {
        try {
            userDao.insert(new User());
            throw new RuntimeException("异常");
        } catch (Exception e) {
            // 异常被捕获,事务不会回滚
            log.error("创建失败", e);
        }
    }
}
 
// 解决:手动标记回滚
@Service
public class UserService {
    
    @Transactional
    public void createUser() {
        try {
            userDao.insert(new User());
            throw new RuntimeException("异常");
        } catch (Exception e) {
            // 手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.error("创建失败", e);
        }
    }
}
 
// 或者重新抛出异常
@Service
public class UserService {
    
    @Transactional
    public void createUser() {
        try {
            userDao.insert(new User());
            throw new RuntimeException("异常");
        } catch (Exception e) {
            log.error("创建失败", e);
            throw e;  // 重新抛出
        }
    }
}

4. 异常类型不匹配

@Service
public class UserService {
    
    @Transactional
    public void createUser() throws Exception {
        userDao.insert(new User());
        throw new Exception("受检异常");  // 默认不会回滚
    }
}
 
// 解决:指定回滚异常类型
@Service
public class UserService {
    
    @Transactional(rollbackFor = Exception.class)  // 指定所有异常都回滚
    public void createUser() throws Exception {
        userDao.insert(new User());
        throw new Exception("受检异常");
    }
}
 
// rollbackFor 默认值
@Transactional(rollbackFor = RuntimeException.class)  // 默认

5. 数据库不支持事务

-- MyISAM 不支持事务
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100)
) ENGINE=MyISAM;
 
-- 使用 InnoDB
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100)
) ENGINE=InnoDB;

6. 类未被 Spring 管理

// 失效:没有 @Service/@Component 等注解
public class UserService {
    
    @Transactional
    public void createUser() {
        userDao.insert(new User());
    }
}
 
// 解决:添加注解
@Service
public class UserService {
    
    @Transactional
    public void createUser() {
        userDao.insert(new User());
    }
}

7. 方法是 final/static

@Service
public class UserService {
    
    @Transactional
    public final void createUser() {  // final,无法代理
        userDao.insert(new User());
    }
    
    @Transactional
    public static void deleteUser() {  // static,无法代理
        userDao.deleteById(1L);
    }
}
 
// 原因:CGLIB 通过继承生成代理,final/static 方法无法重写

8. 错误的传播行为

@Service
public class UserService {
    
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void createUser() {
        // NOT_SUPPORTED:以非事务方式执行,事务失效
        userDao.insert(new User());
    }
    
    @Transactional(propagation = Propagation.NEVER)
    public void updateUser() {
        // NEVER:不能在事务中执行,会抛异常
        userDao.update(new User());
    }
}

9. 多线程调用

@Service
public class UserService {
    
    @Transactional
    public void createUser() {
        new Thread(() -> {
            userDao.insert(new User());  // 新线程,事务失效
        }).start();
    }
}
 
// 原因:事务信息存储在 ThreadLocal,新线程无法继承

10. 事务管理器配置错误

@Configuration
public class DataSourceConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
 
// 如果有多个数据源,需要指定正确的事务管理器
@Transactional("secondaryTransactionManager")
public void createUser() {
    // ...
}

检查清单

□ 方法是否为 public?
□ 是否通过代理调用(非同类直接调用)?
□ 异常是否正确抛出?
□ 异常类型是否匹配(rollbackFor)?
□ 数据库是否支持事务(InnoDB)?
□ 类是否被 Spring 管理?
□ 方法是否为 final/static?
□ 传播行为是否正确?
□ 是否在多线程中执行?
□ 事务管理器是否正确配置?

面试高频问题

Q1: Spring 事务什么时候会失效?

列出上述 10 种场景即可。

Q2: 为什么 private 方法事务失效?

Spring AOP 基于代理:

  • JDK 动态代理:只能代理接口方法
  • CGLIB:通过继承代理,private 方法无法被子类访问

Q3: 同类方法调用如何解决事务失效?

  1. 注入自己,通过代理调用
  2. 使用 AopContext.currentProxy()
  3. 拆分到不同类

Q4: 捕获异常后如何让事务回滚?

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

总结

事务失效核心要点:
1. 方法必须是 public
2. 同类调用需通过代理
3. 异常要正确抛出和匹配
4. 数据库要支持事务
5. 类要被 Spring 管理
6. 方法不能是 final/static