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

简介: 本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。

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

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

优点

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

缺点

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

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

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的声明式事务为例):

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. 发送确认邮件

我们希望在处理过程中:

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

代码如下:

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

@Service
public class InventoryService {
   

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

邮件服务EmailService

@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-----------------------

目录
相关文章
|
1天前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
Java 关系型数据库 MySQL
Spring事务失效,我总结了这7个主要原因
本文详细探讨了Spring事务在日常开发中常见的七个失效原因,包括数据库不支持事务、类不受Spring管理、事务方法非public、异常被捕获、`rollbackFor`属性配置错误、方法内部调用事务方法及事务传播属性使用不当。通过具体示例和源码分析,帮助开发者更好地理解和应用Spring事务机制,避免线上事故。适合所有使用Spring进行业务开发的工程师参考。
27 2
|
1月前
|
Java 程序员 Spring
Spring事务的1道面试题
每次聊起Spring事务,好像很熟悉,又好像很陌生。本篇通过一道面试题和一些实践,来拆解几个Spring事务的常见坑点。
Spring事务的1道面试题
|
26天前
|
安全 算法 Java
强大!基于Spring Boot 3.3 六种策略识别上传文件类型
【10月更文挑战第1天】在Web开发中,文件上传是一个常见的功能需求。然而,如何确保上传的文件类型符合预期,防止恶意文件入侵,是开发者必须面对的挑战。本文将围绕“基于Spring Boot 3.3 六种策略识别上传文件类型”这一主题,分享一些工作学习中的技术干货,帮助大家提升文件上传的安全性和效率。
37 0
|
1月前
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
42 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
27天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
139 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
27天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
44 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
4月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。