Spring事务

tim-qtp...大约 8 分钟Spring框架

1.Spring 事务

Spring 事务的核心注解

(1)@Transactional

Spring 通过 @Transactional 来管理事务。

  • 通过 AOP(面向切面编程)机制,拦截带有 @Transactional 的方法。
  • 在方法执行前开启事务,执行成功后提交事务,执行失败后回滚事务。
@Transactional
public void transferMoney(String fromUser, String toUser, double amount) {
    // 从fromUser账户扣除金额
    accountDao.debit(fromUser, amount);
    // 向toUser账户增加金额
    accountDao.credit(toUser, amount);
}

(2)@EnableTransactionManagement

  • 启用 Spring 事务管理功能。
  • 一般在配置类或启动类中使用:
@Configuration
@EnableTransactionManagement
public class AppConfig {
}

2.Spring 事务的传播机制

从源码来看,一共有 7 种事务传播行为:

REQUIRED:如果当前有事务,就加入;如果没有,就新建一个事务。

REQUIRES_NEW:不管当前有没有事务,都新建一个事务。如果当前有事务,就把当前事务挂起。

如果 methodA 调用 methodB,即使 methodA 已经在一个事务中,methodB 也会开启一个新事务,并且 methodA 的事务会被挂起,等 methodB 的事务完成后,methodA 的事务再继续。

SUPPORTS:如果当前有事务,就加入;如果没有,就以非事务方式执行。

NOT_SUPPORTED:以非事务方式执行,如果当前有事务,就挂起。

MANDATORY:必须在一个已有的事务中执行,否则抛出异常。

NEVER:不能在事务中执行,否则抛出异常。

NESTED:有事务就开嵌套事务,没有就新建。

3.应用场景

REQUIRED

  • 应用场景:常见的业务逻辑调用。比如,在订单创建时调用库存减少的方法,它们都应该共享同一个事务。
  • 优点:事务复用,性能开销较小,适用于大多数业务逻辑。

REQUIRES_NEW

  • 应用场景:日志记录、通知服务等。即使主事务失败,独立事务的操作也应该成功执行。
  • 优点:事务隔离,防止主要事务的失败影响到辅助操作。

SUPPORTS

  • 应用场景:可选的事务支持。比如,某个方法可以在事务外部或内部执行。
  • 优点:灵活处理事务的加入或不加入。

NOT_SUPPORTED

  • 应用场景:需要明确禁止事务的场景,比如读取配置信息、不需要事务控制的数据查询。
  • 优点:避免不必要的事务开销。

MANDATORY

  • 应用场景:必须在现有事务中执行的场景。常用于确保方法调用链的一致性。
  • 缺点:强依赖外部事务,如果没有事务,则会失败。

NEVER

  • 应用场景:需要保证绝对没有事务的场景,比如某些不允许在事务中执行的数据库操作。
  • 缺点:与 MANDATORY 相反,依赖于没有事务的环境。

NESTED

  • 应用场景:需要部分回滚或局部事务的业务逻辑。比如,订单中的部分操作可能会失败,但不希望整个订单回滚。
  • 优点:灵活处理子事务,局部回滚而不影响整体事务。

4.事务传播行为使用示例

REQUIRED(默认行为)

REQUIRED 是 Spring 的默认事务传播行为,意味着方法会加入现有事务中,如果没有事务就创建一个新事务。通常用于大多数业务逻辑中,确保整个业务过程处于同一个事务中。

@Service
public class OrderService {

    @Autowired
    private InventoryService inventoryService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void placeOrder(Order order) {
        // 保存订单
        saveOrder(order);
        
        // 调用库存减少操作,使用同一个事务
        inventoryService.reduceInventory(order);
    }

    private void saveOrder(Order order) {
        // 保存订单逻辑
    }
}

@Service
public class InventoryService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void reduceInventory(Order order) {
        // 减少库存的逻辑
    }
}

在这个例子中,OrderServiceInventoryService 使用了 REQUIRED,意味着 reduceInventory() 方法和 placeOrder() 方法运行在同一个事务中。如果其中一个方法抛出异常,整个事务都会回滚。

REQUIRES_NEW

REQUIRES_NEW 强制启动一个新事务,无论是否存在当前事务。如果有现有事务,它将被挂起,直到新事务完成。

@Service
public class PaymentService {

    @Autowired
    private LogService logService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void processPayment(Order order) {
        // 执行支付逻辑
        process(order);
        
        // 记录日志,独立事务
        logService.logPayment(order);
    }

    private void process(Order order) {
        // 支付处理逻辑
    }
}

@Service
public class LogService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logPayment(Order order) {
        // 记录支付日志逻辑,使用独立事务
    }
}

在这个例子中,PaymentServiceprocessPayment()LogServicelogPayment() 在不同的事务中执行。如果 processPayment() 方法抛出异常,logPayment() 仍然会提交,因为它运行在独立的事务中。

SUPPORTS

SUPPORTS 表示方法可以支持事务,但不强制要求。如果当前有事务,它将在事务中执行;如果没有事务,它将以非事务方式执行。

@Service
public class AccountService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void transferFunds(Account fromAccount, Account toAccount, BigDecimal amount) {
        // 转账逻辑
        withdraw(fromAccount, amount);
        deposit(toAccount, amount);
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public BigDecimal getBalance(Account account) {
        // 获取余额,如果存在事务,则在事务中执行;否则无事务执行
        return account.getBalance();
    }

    private void withdraw(Account fromAccount, BigDecimal amount) {
        // 扣款逻辑
    }

    private void deposit(Account toAccount, BigDecimal amount) {
        // 存款逻辑
    }
}

getBalance() 方法可以在事务内或非事务中执行。如果 transferFunds() 调用了 getBalance(),它将在 REQUIRED 事务中执行;否则,它以非事务方式执行。

NOT_SUPPORTED

NOT_SUPPORTED 表示该方法不支持事务,如果当前有事务,它将挂起该事务,并在非事务环境中执行。

@Service
public class ReportService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void generateMonthlyReport() {
        // 生成月度报告,支持事务
        generateReportData();
        
        // 发送报告邮件,无事务
        sendReportEmail();
    }

    private void generateReportData() {
        // 生成报告数据逻辑
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void sendReportEmail() {
        // 发送邮件逻辑,挂起现有事务
    }
}

在这个例子中,generateMonthlyReport() 方法中的 generateReportData() 运行在事务中,而 sendReportEmail() 运行在非事务环境中,即使 generateMonthlyReport() 已经有事务,它也会挂起现有事务。

MANDATORY

MANDATORY 表示该方法必须在已有事务中运行,如果没有现有事务则抛出异常。

@Service
public class TransactionalService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void performAction() {
        // 执行主要操作
        performSecondaryAction();
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void performSecondaryAction() {
        // 必须在事务内执行
    }
}

在这个例子中,performSecondaryAction() 必须在一个事务中运行。如果它被独立调用而没有事务,则抛出异常。如果 performAction() 方法已经启动了一个事务,那么 performSecondaryAction() 可以正常运行。

NEVER

NEVER 表示该方法不允许在事务中运行,如果当前存在事务,则抛出异常。

@Service
public class TransactionFreeService {

    @Transactional(propagation = Propagation.NEVER)
    public void executeWithoutTransaction() {
        // 执行逻辑,不能在事务中执行
    }
}

如果在事务上下文中调用 executeWithoutTransaction() 方法,会抛出异常,因为它要求不在任何事务中执行。

NESTED

NESTED 表示如果当前存在事务,则在该事务中创建一个嵌套事务(支持保存点),子事务可以独立回滚而不影响主事务。

@Service
public class NestedTransactionService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void mainOperation() {
        // 执行主操作
        try {
            nestedOperation();
        } catch (Exception e) {
            // 嵌套事务回滚,不影响主事务
        }
    }

    @Transactional(propagation = Propagation.NESTED)
    public void nestedOperation() {
        // 嵌套事务操作
        // 可以在这里回滚嵌套事务
        throw new RuntimeException("嵌套事务回滚");
    }
}

在这个例子中,mainOperation() 是主事务,而 nestedOperation() 是嵌套事务。如果 nestedOperation() 失败,它只会回滚嵌套事务,主事务 mainOperation() 不会受影响。

5.事务的隔离级别

①、DEFAULT:使用数据库默认的隔离级别(你们爱咋咋滴 😁),MySQL 默认的是可重复读,Oracle 默认的读已提交。

②、READ_UNCOMMITTED:读未提交,允许事务读取未被其他事务提交的更改。这是隔离级别最低的设置,可能会导致“脏读”问题。

③、READ_COMMITTED:读已提交,确保事务只能读取已经被其他事务提交的更改。这可以防止“脏读”,但仍然可能发生“不可重复读”和“幻读”问题。

④、REPEATABLE_READ:可重复读,确保事务可以多次从一个字段中读取相同的值,即在这个事务内,其他事务无法更改这个字段,从而避免了“不可重复读”,但仍可能发生“幻读”问题。

⑤、SERIALIZABLE:串行化,这是最高的隔离级别,它完全隔离了事务,确保事务序列化执行,以此来避免“脏读”、“不可重复读”和“幻读”问题,但性能影响也最大。

它们定义在 TransactionDefinition 接口中。

隔离级别与脏读、不可重复读、幻读表格汇总

隔离级别脏读不可重复读幻读
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE

在 Spring 中,使用 @Transactional 注解可以方便地设置事务的隔离级别。例如:

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void processTransaction() {
    // 事务逻辑
}

选择隔离级别需要根据应用的具体需求进行权衡:

  • 低隔离级别(READ_UNCOMMITTED 和 READ_COMMITTED):性能高,但可能存在并发问题,适合数据一致性要求不高的场景。
  • 高隔离级别(REPEATABLE_READ 和 SERIALIZABLE):数据一致性强,但性能较差,适合高数据一致性要求的场景。