Spring 事务失效的 8 大场景 + 原因 + 解决方案

1. 同类内部方法调用(最常见)

场景

java 复制代码
@Service
public class UserService {

    // 没有 @Transactional
    public void saveUser() {
        // 同类内部直接调用 → 不经过代理 → 事务失效
        doSave();
    }

    @Transactional
    public void doSave() {
        // 增删改操作
    }
}

原因

  • Spring 事务基于 AOP 动态代理
  • 同类内部 this.方法() 调用,不走代理对象 → 切面不生效

解决方案

  1. 注入自身 Bean

    java 复制代码
    @Autowired
    private UserService userService;
    
    public void saveUser() {
        userService.doSave(); // 走代理
    }
  2. 使用 AopContext.currentProxy()

  3. 把方法拆到不同类


2. 方法不是 public(private/protected/default)

场景

java 复制代码
@Transactional
private void update() {
    // 事务不生效
}

原因

  • Spring AOP 代理只支持 public 方法
  • 非 public 方法会被直接跳过事务切面

解决方案

  • 改成 public

3. @Transactional 加在了接口上,类没被 Spring 管理

场景

java 复制代码
public interface UserService {
    @Transactional
    void add();
}

// 没加 @Service、@Component
public class UserServiceImpl implements UserService {}

原因

  • 没被 Spring 管理 → 不生成代理 → 事务无效

解决方案

  • 类上加 @Service

4. 异常被 try-catch 吃掉,没抛出去

场景

java 复制代码
@Transactional
public void save() {
    try {
        // 数据库操作
    } catch (Exception e) {
        // 只打印日志,不抛异常
        log.error("...");
    }
}

原因

  • Spring 默认只对 RuntimeException & Error 回滚
  • 异常被捕获 → 事务感知不到 → 不会回滚

解决方案

  1. catch 后重新抛出

    java 复制代码
    catch (Exception e) {
        throw new RuntimeException(e);
    }
  2. 注解指定回滚异常

    java 复制代码
    @Transactional(rollbackFor = Exception.class)

5. 抛出了非 RuntimeException(受检异常)

场景

java 复制代码
@Transactional
public void test() throws Exception {
    // 抛出 Exception / IOException 等
}

原因

  • 默认只回滚 RuntimeExceptionError
  • Exception 这种受检异常 不触发回滚

解决方案

java 复制代码
@Transactional(rollbackFor = Exception.class)

6. 多线程调用,事务不共享

场景

java 复制代码
@Transactional
public void save() {
    new Thread(() -> {
        // 子线程里的数据库操作
    }).start();
}

原因

  • Spring 事务基于 ThreadLocal
  • 子线程和主线程不是同一个线程 → 独立连接,独立事务

解决方案

  • 子线程方法自己加 @Transactional
  • 不用异步,或用 CountDownLatch 同步控制

7. 使用了错误的传播机制(NOT_SUPPORTED/NEVER)

场景

java 复制代码
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void update() {
    // 以非事务方式执行
}

原因

  • NOT_SUPPORTED:挂起事务,非事务执行
  • NEVER:强制非事务,有事务直接抛异常

解决方案

  • 使用默认 REQUIRED

8. 数据库引擎不支持事务(MyISAM)

场景

建表时用了 MyISAM

sql 复制代码
CREATE TABLE t(...) ENGINE=MyISAM;

原因

  • MyISAM 不支持事务
  • 代码怎么写都不会回滚

解决方案

  • 改成 InnoDB

一张表速记

场景 失效原因 一句话解决
同类内部调用 不走代理 注入自身调用
非 public AOP不代理 改成public
异常被catch 没抛出去 重新抛出
抛受检异常 默认不回滚 rollbackFor=Exception
多线程 ThreadLocal不共享 子线程独立事务
传播机制错 禁用事务 用REQUIRED
没被Spring管理 无代理 加@Service
MyISAM 引擎不支持 换InnoDB
相关推荐
FreeCultureBoy10 小时前
用 jenv 管理 Java 环境:从安装 JDK 到多版本切换
后端
心态与习惯11 小时前
Julia 初探,及与 C++,Java,Python 的比较
java·c++·python·julia·比较
IT_陈寒11 小时前
Vite的热更新突然失效,原来是因为这个配置
前端·人工智能·后端
考虑考虑11 小时前
SQL语句中的order by可能造成时间重复
数据库·后端·mysql
一叶飘零_sweeeet11 小时前
优秀文章合集
java
zopple11 小时前
ThinkPHP5.x与3.x核心差异解析
java·python·php
Pkmer11 小时前
古法编程: 代理模式
后端·设计模式
文心快码BaiduComate11 小时前
Comate搭载Kimi K2.6,长程13h!
前端·后端·程序员
南境十里·墨染春水11 小时前
C++ 笔记 thread
java·开发语言·c++·笔记·学习
南境十里·墨染春水11 小时前
C++ 笔记 高级线程同步原语与线程池实现
java·开发语言·c++·笔记·学习