找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2318

积分

0

好友

324

主题
发表于 18 小时前 | 查看: 5| 回复: 0

Spring Boot 事务逻辑示意图

Spring Boot 项目里,下面这种代码你一定写过,甚至写过很多次:

@Transactional
public void updateUser(User user) {
    try {
        userMapper.update(user);
        otherService.doSomething();
    } catch (Exception e) {
        log.error("更新失败", e);
    }
}

代码能跑,日志也打了,看起来“异常已经处理干净了”。

但一个问题随之而来:事务还会回滚吗?

很多人对这个问题的判断,和 Spring 实际做的事,完全不一致。

确定图标

一、事务回滚的判定条件

这是一个很多人不愿意接受的事实:异常一旦被 catch 且没有重新抛出,事务默认不会回滚。

原因不复杂,但很容易被忽略。

Spring 的事务判断逻辑是:

  • 方法正常返回 → 提交事务
  • 方法抛出异常 → 判断是否回滚

而你在 catch 里把异常“消化”掉了,对 Spring 来说,这个方法是正常结束的

二、Spring 事务的回滚机制

Spring 的事务,本质是靠 AOP(面向切面编程) 包起来的。

它只关心一件事:目标方法是正常结束,还是异常结束

而不是你业务逻辑里“有没有出过问题”。

所以这段代码:

catch (Exception e) {
    log.error("xxx", e);
}

在事务看来,等价于:

// 什么都没发生

这也是为什么很多人会遇到:

  • 日志提示失败
  • 数据却已经写进数据库

三、异常类型与默认回滚规则

这里再打破一个常见误解。

很多人以为:

只要抛异常,事务就一定回滚

但 Spring 默认只对 RuntimeException 和 Error 回滚

比如:

@Transactional
public void test() throws Exception {
    userMapper.insert(user);
    throw new Exception("error");
}

默认不会回滚。

除非你这样写:

@Transactional(rollbackFor = Exception.class)

所以事务是否回滚,其实取决于两个条件:

  1. 异常有没有被抛出
  2. 抛出的异常类型是否在回滚规则内

四、常见的回滚失效写法

下面这些写法,风险都非常高。

写法一:只记录日志

catch (Exception e) {
    log.error("失败", e);
}

结果:❌ 不回滚

写法二:返回失败结果

catch (Exception e) {
    return false;
}

结果:❌ 不回滚

写法三:吞掉异常继续执行

catch (Exception e) {
    log.error("异常", e);
}
// 后面还有数据库操作

结果:❌ 不回滚,还可能写更多脏数据

五、触发回滚的正确方式

方式一:catch 后重新抛出运行时异常(最常用)

catch (Exception e) {
    log.error("失败", e);
    throw new RuntimeException(e);
}

优点:

  • 行为清晰
  • 符合事务默认规则

这是最推荐的方式

方式二:显式标记回滚(不抛异常)

catch (Exception e) {
    TransactionAspectSupport.currentTransactionStatus()
            .setRollbackOnly();
}

这种方式适合:

  • 不希望异常继续向上抛
  • 但事务必须回滚

⚠️ 但要注意:这种写法非常依赖上下文,不适合滥用。

方式三:一开始就别 catch(能不 catch 就不 catch)

很多业务代码,其实根本不需要在 Service 层 catch 异常

@Transactional
public void update() {
    userMapper.update();
    otherService.doSomething();
}

让异常自然抛出,由全局异常处理统一兜底,反而是最干净、最安全的做法。

六、catch 的真正适用场景是什么

一个简单但实用的判断标准:如果你 catch 了异常,是为了“改变程序流程”,那你必须明确处理事务结果。

比如:

  • 降级处理
  • 部分失败可接受
  • 尝试补偿逻辑

这时你要非常清楚:

  • 哪些数据可以提交
  • 哪些必须回滚

否则,catch 就是在给事务埋雷。

顺带提一个高频坑:

this.otherMethod();

如果 otherMethod() 也加了 @Transactional事务很可能根本没生效

因为 Spring 的事务是通过代理生效的,自调用不会走代理

这也是很多“明明抛异常了,事务还是没回滚”的根源之一。

铅笔简笔画

小结

事务是否回滚,和你“有没有 catch 异常”关系非常大。

  • catch 了,不抛 → 默认不回滚
  • 抛了受检异常 → 默认不回滚
  • 想回滚 → 要么抛 RuntimeException,要么显式标记

事务不是“自动兜底”,它只对明确的异常信号负责。

希望这篇解析能帮你彻底理清 Spring 事务与异常处理的纠葛。对于更多深入的Java系统架构讨论,欢迎访问云栈社区进行交流。

开心卡通形象




上一篇:Qt开发中如何优雅移除焦点虚线框?兼顾美观与可访问性实践
下一篇:嵌入式开发中的DMA技术详解:原理、配置与STM32实例
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-15 23:14 , Processed in 0.213226 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表