Spring的事务什么情况下会失效

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

Spring事务管理基于AOP动态代理实现,只有通过代理对象调用事务方法时,@Transactional注解才会生效;一旦绕过代理、配置出错、异常处理不当或底层环境不支持,事务就会直接失效。下文梳理开发中所有高频失效场景,兼顾原理讲解与实战修复。

一、AOP代理机制失效(最常见,占80%以上场景)

Spring默认仅对代理对象的public方法生成事务切面,若破坏代理规则,注解会被直接忽略,事务完全不生效。

1. 事务方法非public修饰(private/protected/default)

失效根源:Spring AOP代理仅拦截public方法,非public方法的@Transactional注解会被事务管理器直接跳过,不会生成事务切面。

失效案例

@Service
public class OrderService {
    // 非public方法,事务失效
    @Transactional
    private void createOrder() {
        // 数据库操作
    }
}

修复方案

方案1:改为public修饰(最简修复)

@Service
public class OrderService {
    // 改为public,代理可正常拦截事务
    @Transactional
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

方案2:抽取事务方法到独立public接口实现(架构规范)

public interface OrderOperateService {
    void createOrder();
}

@Service
public class OrderOperateServiceImpl implements OrderOperateService {
    @Transactional
    @Override
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

2. 同类内部方法自调用(this调用)

失效根源:同类内部方法调用用的是原始对象(this),而非Spring代理对象,直接绕过事务切面,事务不生效。这是开发中最易踩坑的场景。

失效案例逐场景判定+原因解析

案例1:非事务方法 + this调用事务方法

@Service
public class OrderService {
    // 非事务方法调用同类事务方法,this调用绕过代理
    public void batchCreate() {
        this.createOrder();
    }
    @Transactional
    public void createOrder() {
        // 数据库操作
    }
}

结论:事务完全失效原因:this指向原始对象而非代理对象,直接跳过事务切面,@Transactional注解不生效。

案例2:非事务方法 + 省略this调用事务方法

@Service
public class OrderService {
    public void batchCreate() {
         createOrder();
    }
    @Transactional
    public void createOrder() {
        // 数据库操作
    }
}

结论:事务完全失效原因:省略this本质仍是当前对象(原始对象)调用,和案例1底层逻辑一致,未走代理拦截。

案例3:事务方法 + 省略this调用事务方法

@Service
public class OrderService {
    @Transactional
    public void batchCreate() {
         createOrder();
    }
    @Transactional
    public void createOrder() {
        // 数据库操作
    }
}

结论:事务失效原因:即使外层方法开启事务,内部方法依旧是原始对象自调用,不触发代理切面,内层事务不生效,仅外层事务生效。

案例4:事务方法 + this调用事务方法

@Service
public class OrderService {
    @Transactional
    public void batchCreate() {
        this.createOrder();
    }
    @Transactional
    public void createOrder() {
        // 数据库操作
    }
}

结论:事务失效原因:典型同类自调用坑位,this绕过代理,内层事务切面不执行,事务传播机制失效。

修复方案(附完整Java代码)

方案1:注入自身代理对象调用(推荐)

核心思路:通过@Autowired注入自身Bean(代理对象),用代理对象调用目标事务方法,触发切面。

@Service
public class OrderService {
    // 注入自身代理对象
    @Autowired
    private OrderService orderService;

    // 外层方法(可加@Transactional,按需配置)
    public void batchCreate() {
        // 代理对象调用,事务生效
        orderService.createOrder();
    }

    @Transactional
    public void createOrder() {
        // 数据库增删改操作
    }
}

方案2:拆分方法到不同类(解耦彻底)

核心思路:将事务方法抽离到独立Service,跨类调用自动走代理,彻底避免自调用问题。

// 事务逻辑独立Service
@Service
public class OrderCreateService {
    @Transactional
    public void createOrder() {
        // 数据库操作
    }
}

// 调用方Service
@Service
public class OrderBatchService {
    @Autowired
    private OrderCreateService orderCreateService;

    public void batchCreate() {
        // 跨类调用,代理生效,事务正常执行
        orderCreateService.createOrder();
    }
}

3. 事务方法被final/static修饰

失效根源:JDK动态代理基于接口实现,CGLIB代理基于继承实现;final/static方法无法被重写,代理无法生成事务增强逻辑。

修复方案

方案1:移除final/static关键字(直接修复)

方案2:封装为非静态实例方法(规范写法)

二、@Transactional注解配置错误

注解本身配置或加载异常,会导致事务规则不生效,属于显性配置失误。

1. 事务传播行为配置不当

失效根源:若配置Propagation.NOT_SUPPORTED(不支持事务)、Propagation.NEVER(禁用事务),当前方法会强制脱离事务,即使外层有事务也会挂起/报错。

失效案例:@Transactional(propagation = Propagation.NOT_SUPPORTED) 修饰的方法,无事务生效。

失效代码

@Service
public class OrderService {
    // 配置不支持事务的传播级别,事务强制失效
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

修复方案+代码

方案1:使用默认REQUIRED传播级别(通用场景)

@Service
public class OrderService {
    // 默认REQUIRED,存在事务则加入,不存在则新建
    @Transactional
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

方案2:使用REQUIRES_NEW新建独立事务(子事务隔离)

@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

2. 类未被Spring容器管理

失效根源:@Transactional仅对Spring Bean生效,手动new创建的对象、未加@Service/@Component等注解的类,不会被代理,事务失效。

失效代码

// 未加Spring注解,不属于容器Bean,事务失效
public class OrderService {
    @Transactional
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

// 调用处:手动new对象,不走代理
public class TestController {
    public void test() {
        OrderService orderService = new OrderService();
        orderService.createOrder();
    }
}

修复方案+代码

方案1:添加@Service注解并依赖注入

@Service
public class OrderService {
    @Transactional
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

@RestController
public class TestController {
    // 注入Spring代理对象
    @Autowired
    private OrderService orderService;

    public void test() {
        orderService.createOrder();
    }
}

方案2:使用@Component注解(通用组件)

@Component
public class OrderManager {
    @Transactional
    public void createOrder() {
        orderMapper.insertOrder(order);
    }
}

3. 注解被覆盖/优先级冲突

失效根源:类级别和方法级别同时加@Transactional,方法级注解会覆盖类级;若自定义切面优先级高于事务切面,会提前捕获异常导致事务回滚失效。

修复方案:统一注解粒度,调整事务切面优先级为最高(@Order(Ordered.LOWEST_PRECEDENCE))。

三、异常处理逻辑缺陷(事务不回滚≠不生效,属于回滚失效)

Spring默认仅对RuntimeExceptionError回滚事务,异常处理不当会导致事务不回滚,看似失效。

1. 手动捕获异常未重新抛出

失效根源:事务切面只有捕获到未处理的异常才会触发回滚;若try-catch吞掉异常,事务管理器感知不到异常,不会回滚。

失效案例

@Transactional
public void createOrder() {
    try {
        // 数据库异常操作
    } catch (Exception e) {
        // 吞掉异常,事务不回滚
        log.error("异常", e);
    }
}

失效代码(异常被吞,事务不回滚)

@Transactional
public void createOrder() {
    try {
        orderMapper.insertOrder(order);
        // 模拟异常
        int i = 1 / 0;
    } catch (Exception e) {
        // 吞掉异常,事务管理器无感知,不回滚
        log.error("订单创建异常", e);
    }
}

修复方案+代码

方案1:捕获后重新抛出运行时异常

@Transactional
public void createOrder() {
    try {
        orderMapper.insertOrder(order);
        int i = 1 / 0;
    } catch (Exception e) {
        log.error("订单创建异常", e);
        // 重新抛出,触发事务回滚
        throw new RuntimeException("订单创建失败");
    }
}

方案2:手动标记事务回滚

@Transactional
public void createOrder() {
    try {
        orderMapper.insertOrder(order);
        int i = 1 / 0;
    } catch (Exception e) {
        log.error("订单创建异常", e);
        // 手动强制回滚当前事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

2. 抛出受检异常未指定rollbackFor

失效根源:Spring默认不回滚受检异常(如IOException、SQLException),直接抛出此类异常事务不会回滚。

失效代码(受检异常不回滚)

@Service
public class OrderService {
    // 默认不回滚受检异常,抛出IOException事务不回滚
    @Transactional
    public void createOrder() throws IOException {
        orderMapper.insertOrder(order);
        throw new IOException("文件读写异常");
    }
}

修复方案+代码

方案1:指定rollbackFor覆盖所有异常

@Service
public class OrderService {
    // 受检/运行时异常全部回滚
    @Transactional(rollbackFor = Exception.class)
    public void createOrder() throws IOException {
        orderMapper.insertOrder(order);
        throw new IOException("文件读写异常");
    }
}

方案2:精准指定受检异常回滚

@Service
public class OrderService {
    // 仅针对IOException回滚
    @Transactional(rollbackFor = IOException.class)
    public void createOrder() throws IOException {
        orderMapper.insertOrder(order);
        throw new IOException("文件读写异常");
    }
}

3. 异常被外层切面提前捕获

失效根源:自定义日志/监控切面优先级高于事务切面,提前捕获异常并处理,事务切面无法感知,回滚失效。

修复方案:降低自定义切面优先级,保证事务切面优先拦截异常。

四、底层环境与数据库限制

1. 数据库引擎不支持事务

失效根源:MySQL的MyISAM引擎无事务特性,即使Spring事务生效,数据库层也无法执行回滚;InnoDB引擎才支持事务。

失效场景说明:建表时指定MyISAM引擎,无事务能力

-- 失效建表语句(MyISAM不支持事务)
CREATE TABLE `order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_no` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

修复方案+代码

方案1:修改表引擎为InnoDB

ALTER TABLE `order` ENGINE=InnoDB;

方案2:重新建表指定InnoDB

CREATE TABLE `order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_no` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. 未配置事务管理器

失效根源:传统Spring项目需手动配置DataSourceTransactionManager,Spring Boot虽自动配置,但多数据源场景若未指定事务管理器,事务会失效。

修复方案:单数据源无需配置,多数据源手动绑定对应事务管理器。

五、特殊业务场景失效

1. 多线程/异步方法调用(@Async)

失效根源:Spring事务绑定线程本地变量(ThreadLocal),异步方法会开启新线程,新线程无事务上下文,事务独立且外层无法控制回滚。

失效代码(异步线程事务脱离)

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void createOrder() {
        orderMapper.insertOrder(order);
        // 异步线程,无事务上下文,外层无法控制回滚
        new Thread(() -> {
            orderMapper.updateStock();
            // 此处异常,外层事务不回滚
            int i = 1 / 0;
        }).start();
    }
}

修复方案+代码

方案1:异步方法单独加@Transactional

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void createOrder() {
        orderMapper.insertOrder(order);
        asyncUpdateStock();
    }

    // 异步方法独立开启事务
    @Async
    @Transactional
    public void asyncUpdateStock() {
        orderMapper.updateStock();
        int i = 1 / 0;
    }
}

方案2:同步执行避免线程隔离

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void createOrder() {
        // 同步执行,共用当前事务
        orderMapper.insertOrder(order);
        orderMapper.updateStock();
        int i = 1 / 0;
    }
}

2. 分布式/跨服务调用

失效根源:Spring事务是本地事务,仅作用于单个JVM、单个数据源;跨服务、跨库调用属于分布式场景,本地事务无法跨进程/跨库生效。

修复方案:引入分布式事务方案(Seata、TCC、可靠消息最终一致性)。

3. 事务超时/只读属性冲突

失效根源:配置readOnly = true的事务方法执行写操作,会抛出异常且事务不生效;超时时间设置过短,事务未执行完就被强制提交/回滚。

修复方案:只读事务仅做查询,写操作关闭readOnly;合理设置timeout时长。

核心避坑总结:事务方法必用public、禁止同类this调用、不吞异常、指定rollbackFor、数据库用InnoDB、多线程/分布式单独处理事务。

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

Spring 文章被收录于专栏

本专栏聚焦Spring全生态体系,从IoC/AOP核心原理入手,覆盖Spring Boot自动配置、事务管理、Web开发等实战内容。拆解循环依赖、动态代理等高频面试难点,助力开发者从入门到精通,打通单体到微服务的技术链路,解决企业级开发痛点,提升架构设计与问题排查能力,成为Java后端进阶的必备技术专栏。

全部评论

相关推荐

不知道怎么取名字_:愚人节收到的吧,刚看到有人也是愚人节说收到offer的
腾讯求职进展汇总
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务