基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

如何在 Spring Boot 中使用事务管理?

知识点图片

在 Spring Boot 中使用事务管理非常简单,主要依靠 Spring Framework 的声明式事务(Declarative Transaction Management)。Spring Boot 通过自动配置(Auto-configuration)极大地简化了这一过程。

以下是关于如何在 Spring Boot 中使用事务管理的完整指南,包括基础用法、进阶配置和常见避坑指南。


1. 引入依赖

首先,确保你的项目中引入了数据库相关的 Starter。通常使用 spring-boot-starter-data-jpaspring-boot-starter-jdbc,它们都会自动引入事务管理器(TransactionManager)。

Maven:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 别忘了数据库驱动,例如 MySQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

注意:Spring Boot 会自动检测依赖并配置 DataSourceTransactionManager (JDBC) 或 JpaTransactionManager (JPA)。你通常不需要手动配置 Bean。


2. 基础用法:声明式事务 (@Transactional)

这是最常用的方式。你只需要在Service 层的类或方法上添加 @Transactional 注解。

示例代码

java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 1. 作用于方法:仅该方法开启事务
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        
        // 模拟异常:如果这里抛出 RuntimeException,上面的 save 操作会回滚
        if (user.getName().equals("error")) {
            throw new RuntimeException("Simulated Error");
        }
    }

    // 2. 作用于类:该类所有 public 方法都开启事务
    // @Transactional
    // public class AnotherService { ... }
}

3. 进阶配置:@Transactional 的属性

@Transactional 注解提供了丰富的属性来控制事务行为。

3.1 回滚规则 (rollbackFor) (非常重要)

默认情况下,Spring 只在抛出 RuntimeException (运行时异常) 或 Error 时回滚。如果抛出的是 Checked Exception(受检异常,如 IOException 或自定义的 Exception),事务不会回滚。

解决方案: 显式指定回滚异常类型。

java
// 任何 Exception (包括 Checked Exception) 发生时都回滚
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) throws Exception {
    userRepository.save(user);
    throw new Exception("这是一个受检异常");
}

3.2 传播行为 (propagation)

控制业务方法之间相互调用时,事务如何传递。

  • Propagation.REQUIRED (默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
  • Propagation.REQUIRES_NEW:无论当前是否存在事务,都挂起当前事务,创建一个新的独立事务。(常用于记录日志,无论业务成功与否,日志都要入库)。
  • Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行(使用 Savepoint);如果当前没有事务,则新建事务。
java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
    // 即使外部事务回滚,这个方法的提交也不会受影响
}

3.3 隔离级别 (isolation)

控制并发事务之间的数据可见性(通常使用数据库默认级别即可,如 Read Committed)。

java
@Transactional(isolation = Isolation.READ_COMMITTED)
public void checkStock() { ... }

3.4 只读事务 (readOnly)

如果是查询操作,设置为 true 可以帮助数据库引擎优化性能(例如 Hibernate 不会进行脏检查)。

java
@Transactional(readOnly = true)
public User getUser(Long id) { ... }

4. 编程式事务 (手动控制)

虽然注解很方便,但有时你需要更细粒度的控制(例如只在代码块的一小部分开启事务,而不是整个方法)。这时可以使用 TransactionTemplate

java
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class OrderService {

    private final TransactionTemplate transactionTemplate;

    public OrderService(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void createOrder() {
        // 非事务逻辑
        validateOrder();

        // 开启事务执行核心逻辑
        Boolean result = transactionTemplate.execute(status -> {
            try {
                // 数据库操作...
                return true;
            } catch (Exception e) {
                // 手动回滚
                status.setRollbackOnly();
                return false;
            }
        });
    }
}

5. 常见失效场景 (避坑指南)

很多新手在使用 @Transactional 时发现事务不生效,通常是以下原因造成的:

  1. 同类自调用 (Self-Invocation)

    • 问题:在同一个类中,一个没有事务的方法 A 调用了有事务的方法 B。
    • 原因:Spring 事务基于 AOP 代理。同类调用是 this.methodB(),绕过了代理对象,导致事务切面不执行。
    • 解决:将方法 B 移到另一个 Service 中,或者注入自身(@Autowired private UserService self; 然后 self.methodB())。
    java
    // 错误示例
    public void methodA() {
        methodB(); // 事务失效
    }
    @Transactional
    public void methodB() { ... }
  2. 方法修饰符不是 Public

    • @Transactional 只能应用在 public 方法上。如果在 privateprotected 方法上使用,会被忽略(除非使用 AspectJ 代理模式)。
  3. 异常被 Catch 吃了

    • 如果在代码中 try-catch 捕获了异常且没有重新抛出,Spring 认为方法正常结束,会提交事务。
    • 解决:在 catch 块中 throw new RuntimeException(e) 或者手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    java
    // 错误示例
    @Transactional
    public void wrong() {
        try {
            repo.save(obj);
            throw new RuntimeException();
        } catch (Exception e) {
            e.printStackTrace(); // 异常被吞,事务会提交!
        }
    }
  4. 数据库引擎不支持

    • 例如 MySQL 的 MyISAM 引擎不支持事务,必须使用 InnoDB

总结

  1. 引入依赖spring-boot-starter-data-jpa/jdbc
  2. 使用注解:在 Service 方法上加 @Transactional
  3. 注意回滚:推荐养成习惯写 @Transactional(rollbackFor = Exception.class)
  4. 避开陷阱:注意同类调用失效和异常捕获问题。
00:00
00:00