Spring事务失效场景

Spring 事务失效是日常开发中非常常见的问题,核心原因是 Spring 声明式事务的实现依赖 AOP 动态代理,一旦代理机制被破坏或不满足事务触发条件,事务就会失效。下面我会逐一讲解最常见的失效场景、原因和解决方案,内容由浅入深,方便你理解和排查。

一、常见的事务失效场景及解决方案

1. 方法不是 public 修饰

原因:Spring 事务管理器(TransactionInterceptor)只会拦截 public 方法,这是因为 Spring AOP 基于 JDK 动态代理时,只能代理接口的 public 方法;即使是 CGLIB 代理,Spring 也做了限制(源码中会检查方法修饰符)。
示例(失效代码)

@Service
public class UserService {
    @Transactional
    private void addUser(String username) { // private 方法,事务失效
        // 数据库操作
    }
}

解决方案:将方法改为 public 修饰:

@Service
public class UserService {
    @Transactional
    public void addUser(String username) { // 改为 public
        // 数据库操作
    }
}

2. 同一个类中方法内部调用

原因:Spring 事务通过代理对象实现,若在同一个类中,普通方法调用加了 @Transactional 的方法,本质是直接调用目标对象的方法,而非代理对象,AOP 无法拦截,事务失效。
示例(失效代码)

@Service
public class UserService {
    // 普通方法
    public void saveUser(String username) {
        // 内部调用事务方法,事务失效
        addUser(username); 
    }

    @Transactional
    public void addUser(String username) {
        // 数据库操作
    }
}

解决方案

  • 方案1(推荐):将事务方法抽取到另一个 Service 类,通过依赖注入调用(走代理);
  • 方案2:自注入代理对象,通过代理对象调用(注意循环依赖问题):
@Service
public class UserService {
    // 自注入代理对象(Spring 4.3+ 支持,或通过 ApplicationContext 获取)
    @Autowired
    private UserService userService;

    public void saveUser(String username) {
        // 通过代理对象调用,事务生效
        userService.addUser(username); 
    }

    @Transactional
    public void addUser(String username) {
        // 数据库操作
    }
}

3. 事务注解属性配置错误

原因@Transactional 的属性配置不符合业务场景,导致事务不生效或回滚异常。
常见错误配置:

  • propagation = Propagation.NOT_SUPPORTED(不支持事务,会挂起当前事务);
  • propagation = Propagation.SUPPORTS(仅当有事务时才生效,无事务则不使用事务);
  • rollbackFor 未配置,默认只回滚 RuntimeExceptionError,检查异常(如 SQLException)不会触发回滚。

示例(失效代码)

@Service
public class UserService {
    // 配置错误:NOT_SUPPORTED 不支持事务
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void addUser(String username) {
        // 数据库操作,事务失效
    }

    // 未配置 rollbackFor,检查异常不会回滚
    @Transactional
    public void updateUser(String username) throws SQLException {
        // 抛 SQLException,事务不回滚
        throw new SQLException("更新失败");
    }
}

解决方案

@Service
public class UserService {
    // 使用默认的 REQUIRED(支持当前事务,无则新建)
    @Transactional
    public void addUser(String username) {
        // 事务生效
    }

    // 配置 rollbackFor 包含检查异常
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(String username) throws SQLException {
        throw new SQLException("更新失败"); // 事务回滚
    }
}

4. 异常被捕获且未抛出

原因:Spring 事务需要捕获到方法抛出的异常才会触发回滚,如果异常被 try-catch 捕获且未重新抛出,事务管理器无法感知异常,不会回滚。
示例(失效代码)

@Service
public class UserService {
    @Transactional
    public void addUser(String username) {
        try {
            // 数据库操作
            int a = 1 / 0; // 抛运行时异常
        } catch (Exception e) {
            // 捕获异常但未抛出,事务不回滚
            e.printStackTrace();
        }
    }
}

解决方案:捕获异常后重新抛出,或手动触发回滚:

@Service
public class UserService {
    @Transactional
    public void addUser(String username) {
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
            // 方案1:重新抛出异常
            throw new RuntimeException(e);
            // 方案2:手动回滚(适用于不想抛异常的场景)
            // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
}

5. 目标对象未被 Spring 容器管理

原因:Spring 事务仅对容器中的 Bean 生效,如果通过 new 关键字手动创建对象(而非依赖注入),该对象不是 Spring 代理对象,事务注解无效。
示例(失效代码)

@Controller
public class UserController {
    @GetMapping("/add")
    public void add() {
        // 手动 new 对象,不是 Spring Bean,事务失效
        UserService userService = new UserService();
        userService.addUser("test");
    }
}

@Service
public class UserService {
    @Transactional
    public void addUser(String username) {
        // 数据库操作
    }
}

解决方案:通过 @Autowired/@Resource 等方式注入 Spring 容器中的 Bean:

@Controller
public class UserController {
    // 注入 Spring 管理的 UserService Bean
    @Autowired
    private UserService userService;

    @GetMapping("/add")
    public void add() {
        userService.addUser("test"); // 事务生效
    }
}

6. 数据库存储引擎不支持事务

原因:Spring 事务的底层依赖数据库事务,如果数据库存储引擎不支持事务(如 MySQL 的 MyISAM),即使 Spring 配置正确,事务也无法生效。
解决方案:将 MySQL 表的存储引擎改为 InnoDB(默认支持事务):

-- 修改表引擎
ALTER TABLE user ENGINE = InnoDB;
-- 创建表时指定引擎
CREATE TABLE user (
    id INT PRIMARY KEY,
    username VARCHAR(20)
) ENGINE = InnoDB;

7. 多线程调用

原因:Spring 事务是基于线程绑定的,子线程无法继承父线程的事务上下文,多线程执行数据库操作时,子线程的操作不在当前事务中,事务失效。
示例(失效代码)

@Service
public class UserService {
    @Transactional
    public void addUser(String username) {
        // 主线程操作
        insertUser(username);
        // 子线程操作,事务失效
        new Thread(() -> updateUser(username)).start();
    }
}

解决方案:避免在事务方法中开启多线程执行数据库操作;若必须使用,需手动管理子线程的事务(如在子线程方法上单独加 @Transactional)。

二、如何快速排查事务失效?

  1. 检查方法修饰符:是否为 public
  2. 检查调用方式:是否为内部调用(非代理调用);
  3. 检查异常处理:是否捕获异常未抛出,或 rollbackFor 配置错误;
  4. 检查 Bean 管理:对象是否由 Spring 容器创建;
  5. 开启日志调试:添加日志配置,查看事务是否被拦截:
<!-- logback 配置示例 -->
<logger name="org.springframework.transaction" level="DEBUG"/>
<logger name="org.springframework.aop" level="DEBUG"/>

总结

  1. Spring 事务依赖 AOP 动态代理,任何破坏代理机制的操作(如内部调用、非 public 方法、手动 new 对象)都会导致事务失效;
  2. 事务生效的核心条件:public 方法 + 代理调用 + 异常抛出(或手动回滚) + 正确的注解配置 + 数据库支持事务;
  3. 排查优先级:先检查调用方式和异常处理,再检查注解配置和 Bean 管理,最后确认数据库层面。
posted @ 2026-03-12 10:11  七星6609  阅读(2)  评论(0)    收藏  举报