惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
N
Netflix TechBlog - Medium
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
T
Tailwind CSS Blog
IT之家
IT之家
博客园 - Franky
雷峰网
雷峰网
博客园 - 聂微东
腾讯CDC
M
MIT News - Artificial intelligence
B
Blog RSS Feed
博客园_首页
罗磊的独立博客
S
SegmentFault 最新的问题
I
InfoQ
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
D
Docker
宝玉的分享
宝玉的分享
B
Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

博客园 - 野鹤闲人

ubantu 耳机调试 keepalived+nginx msg广播到同一个group中,怎么保证所有consumer都消费到,Rocketmq和rabbitmq 分别是怎么做的 mysql优化语句时,关注哪几列 extra要关注吗 CountDownLatch/CyclicBarrier 区别 机器缩容要注意哪些问题 IO模型有哪几种 ConcurrentSkipListMap 并发安全实现 TreeMap hive 小文件优化 redis缓存雪崩问题解决 CAS原理 docker的常用命令 k8s的常用组件,和命令 mysql B+树 如果有3层,能保存多少数据 如何保证rabbitmq或kafka 消息不丢失,从生产到消费端全过程 java排查工具 JAVA 强软弱虚引用 git
Spring 事务失效
野鹤闲人 · 2025-12-23 · via 博客园 - 野鹤闲人

Spring 事务失效是日常开发中高频踩坑点,核心原因是事务注解的生效条件未满足(如代理失效、传播行为不当、异常处理错误等)。以下梳理 10 类典型失效场景,结合原理和解决方案,覆盖开发中 90% 以上的失效情况:

Spring 事务默认基于 动态代理(JDK 动态代理 / CGLIB) 实现,只有通过代理对象调用方法,事务注解(@Transactional)才会生效;且事务管理器(如DataSourceTransactionManager)需正确配置。所有失效场景本质都是 “脱离了代理的管控” 或 “事务规则未满足”。

Spring 事务的代理逻辑(TransactionInterceptor)仅对 public 方法 生效:

  • JDK 动态代理:基于接口,只能代理 public 方法;
  • CGLIB 代理:虽可代理非 public,但 Spring 源码中硬编码限制仅处理 public(AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute方法会过滤非 public 方法)。
@Service
public class UserService {
    

将事务方法改为 public 修饰(必须)。

Spring 事务通过代理对象实现,同类内直接调用方法(而非代理对象),会绕过代理逻辑,事务注解失效。

@Service
public class UserService {
    
  1. 最优:拆分到不同 Service将事务方法抽离到独立的 Service,通过依赖注入调用(走代理):

    @Service
    public class UserService {
        @Resource
        private UserTxService userTxService;
    
        public void outerMethod(Long id) {
            userTxService.innerMethod(id); 
  2. 次优:自注入代理对象在本类中注入自身的代理对象(需开启暴露代理配置):

    // 1. 开启暴露代理(application.yml)
    spring:
      aop:
        proxy-target-class: true
        expose-proxy: true
    
    
  3. 兜底:手动编程式事务放弃声明式事务,用TransactionTemplate手动控制:

    @Service
    public class UserService {
        @Resource
        private TransactionTemplate transactionTemplate;
    
        public void outerMethod(Long id) {
            transactionTemplate.execute(status -> {
                innerMethod(id);
                return null;
            });
        }
    
        public void innerMethod(Long id) {
            

Spring 事务默认只捕获 未处理的 RuntimeException/Error,并在异常抛出时触发回滚;若异常被try-catch捕获且未重新抛出,事务管理器感知不到异常,会执行提交而非回滚。

@Service
public class UserService {
    @Transactional
    public void updateUser(Long id) {
        try {
            
  1. 捕获后重新抛出异常(推荐):

    @Transactional
    public void updateUser(Long id) {
        try {
            
  2. 手动触发回滚(适合需捕获异常的场景):

    @Transactional
    public void updateUser(Long id) {
        try {
            

Spring 事务默认只对 RuntimeException 和 Error 回滚,若抛出的是 Checked 异常(如IOExceptionSQLException),且未配置rollbackFor,事务不会回滚。

@Service
public class UserService {
    @Transactional
    public void updateUser(Long id) throws IOException {
        

通过rollbackFor指定需要回滚的异常类型:

// 指定回滚所有异常(或精准指定IOException)
@Transactional(rollbackFor = Exception.class)
public void updateUser(Long id) throws IOException {
    

@Transactional(propagation = ...) 定义了事务的传播规则,若配置为以下值,会导致事务失效:

  • Propagation.NOT_SUPPORTED:以非事务方式执行,若有当前事务则挂起;
  • Propagation.NEVER:以非事务方式执行,若有当前事务则抛异常;
  • Propagation.SUPPORTS:有事务则加入,无则以非事务执行(无事务时失效)。
@Service
public class UserService {
    

使用正确的传播行为(默认Propagation.REQUIRED即可满足 99% 场景):

// 默认REQUIRED:有事务则加入,无则新建
@Transactional
public void updateUser(Long id) {
    

Spring 事务需要绑定对应的PlatformTransactionManager(如DataSourceTransactionManager),若未配置,事务注解仅为 “空注解”,不会生效。

// 仅配置数据源,未配置事务管理器
@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        

手动配置事务管理器(Spring Boot 2.x+ 若只配置一个数据源,会自动配置,多数据源需手动指定):

@Configuration
public class TransactionConfig {
    @Resource
    private DataSource dataSource;

    @Bean
    public PlatformTransactionManager transactionManager() {
        

多数据源场景下,若未通过@Transactional(value = "xxxTransactionManager")指定对应的事务管理器,Spring 无法识别该用哪个,事务失效。

@Service
public class UserService {
    

指定事务管理器的 bean 名称:

// 假设配置了两个事务管理器:txManager1、txManager2
@Transactional(value = "txManager1")
public void updateUser(Long id) {
    

Spring 事务依赖数据库的事务支持,若 MySQL 表使用MyISAM引擎(不支持事务),即使配置了 Spring 事务,也无法回滚。

将表引擎改为InnoDB(MySQL 5.5+ 默认 InnoDB):

-- 修改表引擎
ALTER TABLE user ENGINE = InnoDB;

  • JDK 动态代理:基于接口,仅代理接口中的方法;若 Service 未实现接口,且未开启 CGLIB 代理,会导致代理失效;
  • Spring Boot 2.x+ 默认开启proxy-target-class=true(优先 CGLIB),但手动关闭后可能失效。
  1. 开启 CGLIB 代理(application.yml):
    spring:
      aop:
        proxy-target-class: true 
  2. 让 Service 实现接口(适配 JDK 代理):
    public interface UserService {
        void updateUser(Long id);
    }
    
    @Service
    public class UserServiceImpl implements UserService {
        @Transactional
        @Override
        public void updateUser(Long id) {
            

@Transactional(timeout = n) 表示事务超时时间(秒),若业务执行时间超过 timeout,事务会被强制回滚,看似 “失效”(实际是超时回滚)。

@Transactional(timeout = 1) 

根据业务实际耗时调整 timeout 值(避免过小):

@Transactional(timeout = 30) 
  1. 检查方法修饰符:是否为 public;
  2. 检查调用方式:是否为同类内部调用(this.xxx ());
  3. 检查异常处理:是否捕获异常未抛出,或异常类型不匹配;
  4. 检查传播行为:是否为 REQUIRED/NESTED/REQUIRES_NEW(避免 NOT_SUPPORTED/NEVER);
  5. 检查事务管理器:是否配置,多数据源是否指定;
  6. 检查数据库引擎:是否为 InnoDB;
  7. 日志排查:开启 Spring 事务日志,查看事务是否创建 / 回滚:
    logging:
      level:
        org.springframework.transaction: DEBUG
        org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG

Spring 事务失效的核心可归纳为三类:

  1. 代理失效:非 public 方法、同类内部调用、代理方式错误;
  2. 规则不满足:异常处理错误、传播行为错误、超时设置不合理;
  3. 底层不支持:数据源未配事务管理器、表引擎不支持事务。

生产环境中,优先使用声明式事务(@Transactional)+ 避免同类内部调用 + 正确处理异常,复杂场景(如多数据源、手动回滚)可结合编程式事务(TransactionTemplate),既能保证可读性,又能避免失效。