Spring高手之路24——事务类型及传播行为实战指南

文章目录

  • [1. 编程式事务(不推荐)](#1. 编程式事务(不推荐))
  • [2. 声明式事务(推荐)](#2. 声明式事务(推荐))
  • [3. 事务的传播行为(复杂混合事务场景及时序图说明)](#3. 事务的传播行为(复杂混合事务场景及时序图说明))
    • [3.1 NESTED和REQUIRES_NEW传播行为的区别](#3.1 NESTED和REQUIRES_NEW传播行为的区别)

1. 编程式事务(不推荐)

定义:编程式事务是指通过显式的编程代码来管理事务的开始、提交和回滚。开发者需要手动控制事务的每个步骤。

优点

  • 更加灵活:开发者可以根据具体的业务逻辑细节对事务进行精细控制。
  • 适用于需要精细控制的事务逻辑:当事务行为需要根据特定条件进行复杂控制时,编程式事务更为合适。

缺点

  • 代码冗长:需要手动编写大量的事务管理代码,增加了代码复杂性。
  • 易出错:手动管理事务容易导致漏写提交或回滚的代码,增加了发生错误的风险。

示例(以Spring的编程式事务为例):

java 复制代码
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

public class TransactionService {
    private final PlatformTransactionManager transactionManager;

    public TransactionService(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void executeInTransaction() {
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 业务逻辑
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

这段代码展示了如何使用Spring进行编程式事务管理。executeInTransaction方法中,首先创建了一个事务定义和事务状态。然后,在try代码块中执行业务逻辑,并在成功时提交事务。如果发生异常,则回滚事务。通过这种方式,可以精细控制事务的开始、提交和回滚,适用于需要复杂事务控制的场景。

2. 声明式事务(推荐)

定义:声明式事务是通过配置或注解的方式来管理事务。开发者无需手动编写事务管理代码,事务的控制交由框架处理。

优点

  • 简洁明了:使用注解或配置文件即可实现事务管理,代码更加简洁。
  • 降低出错率:自动管理事务,减少手动管理带来的错误。

缺点

  • 灵活性较差:声明式事务在面对非常复杂的事务逻辑时,可能不够灵活,但可以通过结合使用不同的事务传播行为来部分解决这个问题。

示例(以Spring的声明式事务为例):

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransactionService {

    @Transactional
    public void executeInTransaction() {
        // 业务逻辑
    }
}

Spring中,通常推荐使用声明式事务,因为它更加简洁并且可以充分利用Spring框架的事务管理功能。在需要复杂事务控制时,可以考虑使用编程式事务。

3. 事务的传播行为(复杂混合事务场景及时序图说明)

  • REQUIRED:如果当前没有事务,则创建一个新的事务;如果已经存在一个事务,则加入当前事务。
  • REQUIRES_NEW:每次都创建一个新的事务,如果当前已经有一个事务,则挂起当前事务。挂起当前事务的意思是,当前事务的执行暂停,待新事务完成后再恢复执行。新事务提交后,主事务回滚不会影响这个新事务。
  • NESTED:如果当前有事务,则在当前事务中嵌套一个事务,若外部事务回滚,则嵌套事务也会回滚;如果当前没有事务,则创建一个新的事务。
  • MANDATORY:必须在一个现有事务中运行,如果当前没有事务,则抛出异常。
  • NEVER:不能在事务中运行,如果当前有事务,则抛出异常。
  • NOT_SUPPORTED:当前方法不支持事务,如果当前有事务,则将事务挂起。
  • SUPPORTS:如果当前有事务,则加入该事务;如果当前没有事务,也可以非事务方式运行。

对于需要部分事务回滚的复杂场景,Spring中的声明式事务确实可以通过传播行为来实现一定的灵活控制。以下是一个详细的示例,展示如何使用常见的传播行为来实现部分事务回滚。

复杂混合事务场景示例:

假设有一个订单处理系统,包含以下步骤:

  1. 创建订单
  2. 扣减库存
  3. 发送确认邮件

我们希望在处理过程中:

  • 如果创建订单失败,整个事务回滚。
  • 如果扣减库存失败,只回滚扣减库存这部分,不影响订单创建。
  • 发送确认邮件可以在一个独立的事务中进行,即使失败也不影响前面的步骤。

代码如下:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private EmailService emailService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void processOrder(Order order) {
        // Step 1: 创建订单(主事务)
        createOrder(order);

        try {
            // Step 2: 扣减库存(嵌套事务)
            inventoryService.deductInventory(order);
        } catch (Exception e) {
            // 仅回滚扣减库存这部分,不影响订单创建
            System.err.println("扣减库存失败: " + e.getMessage());
        }

        // Step 3: 发送确认邮件(新事务)
        emailService.sendOrderConfirmation(order);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createOrder(Order order) {
        // 创建订单的逻辑
        // 如果失败,抛出异常,整个事务回滚
        if (order == null) {
            throw new RuntimeException("订单创建失败");
        }
    }
}

库存服务InventoryService

java 复制代码
@Service
public class InventoryService {

    @Transactional(propagation = Propagation.NESTED)
    public void deductInventory(Order order) {
        // 扣减库存的逻辑
        // 如果失败,抛出异常,仅回滚此嵌套事务
        if (order.getItems().isEmpty()) {
            throw new RuntimeException("库存扣减失败");
        }
    }
}

邮件服务EmailService

java 复制代码
@Service
public class EmailService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendOrderConfirmation(Order order) {
        // 发送确认邮件的逻辑
        // 失败也不会影响主事务
        if (order.getEmail() == null) {
            throw new RuntimeException("邮件发送失败");
        }
    }
}

解释

  • 创建订单

    使用 REQUIRED 传播行为,作为主事务。如果失败,会抛出异常,导致整个事务回滚。

  • 扣减库存

    使用 NESTED 传播行为,在主事务内创建一个嵌套事务。如果扣减库存失败,只会回滚这个嵌套事务,不会影响到主事务。

  • 发送确认邮件

    使用 REQUIRES_NEW 传播行为,总是创建一个新的事务。即使发送邮件失败,也不会影响前面的事务。

通过结合使用事务不同的传播行为 ,可以在声明式事务中实现复杂的事务管理需求,如部分事务回滚。

整个事务处理过程时序图如下:

时序图解释

  1. 客户端调用 OrderServiceprocessOrder 方法开始主事务。
  2. OrderService 中创建订单,使用 REQUIRED 传播行为。
  3. 如果订单创建成功,调用 InventoryService 扣减库存,使用 NESTED 传播行为。如果库存扣减失败,只回滚嵌套事务,不影响主事务。
  4. 无论库存扣减是否成功,调用 EmailService 发送确认邮件,使用 REQUIRES_NEW 传播行为。即使邮件发送失败,也不影响主事务。
  5. 如果订单创建失败,回滚主事务,并且所有嵌套事务也会回滚,但独立的新事务不会受到影响。

3.1 NESTED和REQUIRES_NEW传播行为的区别

有人可能疑惑了,这里NESTEDREQUIRES_NEW的传播行为看起来很像,这里详细对比一下。

  1. 事务关系:

REQUIRES_NEW

  • 创建一个独立的新事务,与外部事务无关。
  • 如果当前有事务,暂停当前事务,启动一个新事务。
  • 新事务提交或回滚后,外部事务继续。

NESTED

  • 在当前事务内创建一个嵌套事务。
  • 嵌套事务依赖于外部事务,嵌套事务提交必须等待外部事务提交。
  • 如果当前没有事务,则行为与 REQUIRED 相同,创建一个新事务。
  1. 回滚和提交行为

REQUIRES_NEW

  • 回滚行为:如果新事务失败,只回滚新事务,不影响外部事务。
  • 提交行为:新事务提交后,外部事务继续。即使外部事务回滚,新事务的提交也不会受到影响。

NESTED

  • 回滚行为:如果嵌套事务失败,只回滚嵌套事务,不影响外部事务。
  • 提交行为:嵌套事务在外部事务提交时才真正提交。如果外部事务回滚,嵌套事务也会回滚。
  1. 使用场景

REQUIRES_NEW

  • 适用于需要独立事务的场景,例如记录日志或发送通知,这些操作应独立完成,不受主事务影响。

NESTED

  • 适用于部分独立处理但依赖于外部事务的场景,例如部分业务操作需要独立回滚,但整体上需要保持一致性。

欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------

相关推荐
alonewolf_9918 小时前
Java类加载机制深度解析:从双亲委派到热加载实战
java·开发语言
追梦者12318 小时前
springboot整合minio
java·spring boot·后端
云游18 小时前
Jaspersoft Studio community edition 7.0.3的应用
java·报表
帅气的你18 小时前
Spring Boot 集成 AOP 实现日志记录与接口权限校验
java·spring boot
zhglhy18 小时前
Spring Data Slice使用指南
java·spring
win x19 小时前
Redis 主从复制
java·数据库·redis
weixin_4239950019 小时前
unity 处理图片:截图,下载,保存
java·unity·游戏引擎
帅气的你19 小时前
从零封装一个通用的 API 接口返回类:统一前后端交互格式
java·设计模式
qq_1780570719 小时前
基于minio实现的分片上传-支持断点续传
java
高山上有一只小老虎19 小时前
灵异背包?
java·算法