使用 @Transactional 控制事务边界:传播和隔离解释

简介: 本文深入解析了 Spring 框架中的 `@Transactional` 注解,重点介绍了事务管理中的传播行为与隔离级别。内容涵盖事务的基本概念、声明式事务管理、回滚机制、传播模式(如 REQUIRED、REQUIRES_NEW 等)及隔离级别(如 READ_COMMITTED、SERIALIZABLE),并通过示例说明如何在实际开发中灵活应用这些特性,以确保数据一致性与系统性能的平衡。适合 Java 开发人员深入理解 Spring 事务机制。

介绍

在处理数据库时,了解事务边界对于维护数据完整性和一致性至关重要。Java 的 Spring 框架提供了一个名为 Spring 的强大工具@Transactional来控制这些边界。此注释不仅仅是启动和提交/回滚事务的机制,而且它通过其属性提供了更多控制。开发人员需要了解交易的两个关键方面是传播和隔离。本文深入探讨了这些方面,阐明了如何@Transactional在 Spring 应用程序中有效使用。在这边问我想向大家推荐一本Springboot实战派,从中可以看到Springboot的整个实现原理,与源码解读。

@Transactional 简介

在企业应用程序领域,事务在确保操作的原子性、一致性、隔离性和持久性(通常称为 ACID 属性)方面发挥着关键作用。这些属性确保我们的系统即使在遇到故障时也能保持一致的状态。

Spring 框架因其简化 Java 开发的综合工具包而闻名,它提供@Transactional方法级别的事务行为管理。让我们来解开这意味着什么:

了解事务上下文

事务的本质是将多个操作封装为一个单元。如果所有操作都成功,则称事务已提交。即使其中一项操作失败,整个事务也会回滚,就好像所有操作都没有发生一样。注释@Transactional有助于设置方法周围的边界。当您使用 注释方法时@Transactional,您实际上是在告诉 Spring 容器该方法中的所有内容都应被视为单个事务单元。

声明式事务管理

在 Spring 中有两种管理事务的方式:编程方式(使用 Transaction API)和声明方式(使用@Transactional注释或 XML 配置)。后者更受欢迎和推荐,因为它将事务管理排除在业务逻辑之外,从而使代码更干净、更易于维护。

使用@Transactional,您可以以声明方式定义事务管理规则。当调用带注解的方法时,Spring会动态创建一个代理,根据该方法的执行来处理事务的创建、提交和回滚。

@Transactional 如何工作?

在底层,当 Spring 遇到注释时@Transactional,它会围绕实际 bean 动态创建一个代理对象。该代理负责管理事务边界。如果该方法成功完成,它将提交事务。如果存在任何未经检查的异常,它将回滚事务。对于检查异常,它依赖于我们设置的配置(默认情况下,它不会回滚检查异常)。

回滚事务

一种常见的误解是@Transactional仅回滚未经检查的异常(RuntimeException的子类)。默认情况下,这是正确的。但是,您可以自定义此行为。如果需要事务在特定的已检查异常上回滚,可以使用 的属性来rollbackFor指定@Transactional。

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Transactional(rollbackFor = UserNotFoundException.class)
    public User updateUserDetails(long userId, User updatedDetails) throws UserNotFoundException {
        User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("User not found"));
        user.setName(updatedDetails.getName());
        user.setEmail(updatedDetails.getEmail());
        return userRepository.save(user);
    }
}

在上面的示例中,即使UserNotFoundException是检查异常,如果抛出事务也会回滚。

范围和边界

虽然@Transactional可以在类和方法上使用,但明智地使用它至关重要。如果您注释一个类,则该类中的每个公共方法都将成为事务性的。但是,如果您注释特定方法,则只有这些方法具有事务上下文。这允许对代码的哪些部分应该是事务性的进行细粒度控制。

传播:交易如何表现

在 Spring 中,传播行为决定事务方法如何与现有事务交互。了解这一点非常重要,因为不正确的传播设置可能会导致细微的错误、数据不一致和性能问题。

为什么传播很重要

想象这样一种场景,您有多个事务方法,这些方法可以独立调用,也可以相互调用。这些方法如何与现有事务交互或如何启动事务可能会影响整体行为和结果。因此,选择正确的传播策略至关重要。

传播模式解释:

必需的

  1. 这是默认模式,顾名思义,它需要事务上下文。
  2. 如果调用该方法时有正在进行的事务,则该方法将加入该事务。
  3. 如果不存在事务,则开始新的事务。
  4. 此模式非常适合操作相互依赖且需要一起成功或一起失败的典型用例。

支持

  1. 该方法可以在事务内部和外部工作。
  2. 如果存在现有事务,该方法会加入该事务;如果不是,则该方法在没有事务的情况下运行。
  3. 当方法内的操作不一定需要事务时,这是一种灵活的方法,但如果事务已经在进行中,他们仍然可以从事务中受益。

强制的

  1. 此模式严格强制存在活动事务。
  2. 如果没有正在进行的事务,则会引发运行时异常。
  3. 这是确保调用方法已经启动事务的一种方法。

REQUIRES_NEW

  1. 此模式确保该方法始终在新事务中运行。
  2. 如果存在现有事务,则该事务会在该事务开始之前暂停。完成后,外部事务将恢复。
  3. 当方法中的操作需要与任何外部事务隔离的事务边界时,这非常有用。

不支持

  1. 即使有正在进行的事务,该方法也将在没有任何事务上下文的情况下执行。
  2. 任何活动事务都会在该方法执行之前暂停,然后再恢复。
  3. 当某些操作不应该是事务性的(尽管是在事务性上下文中调用)时很有用。

绝不

  1. 此模式确保不存在事务上下文。
  2. 如果有正在进行的事务,则会引发运行时异常。
  3. 它保证方法内的操作始终在没有事务的情况下运行。

嵌套

  1. 如果存在活动事务,则该方法将在该现有事务的嵌套事务中运行。
  2. 嵌套事务是一组可以独立提交或回滚的操作,同时仍然是较大事务的一部分。
  3. 这很复杂,需要仔细考虑。并非所有事务管理器都支持此模式。

传播模式的实际意义

了解这些模式至关重要,但了解何时使用它们更为重要。让我们举一个简单的例子。

假设您有一个用于下订单的电子商务应用程序,并且您还需要更新库存。你可能有方法placeOrder和updateStock。如果updateStock失败,您可能不想下订单。在这种情况下,placeOrder可以启动一个新事务(使用REQUIRED)并updateStock可以加入正在进行的事务(REQUIRED也使用)。如果任何方法失败,整个事务将回滚。

但是,如果您希望updateStock其交易即使失败,下单也不会回滚,那么updateStock应该使用REQUIRES_NEW.

隔离:保持数据完整性

使用数据库时,尤其是在多个事务同时运行的系统中,由于多个事务操作访问同一组数据,数据完整性可能会面临风险。隔离级别确定一个事务中的操作与其他并发事务中的操作隔离的程度。

隔离的必要性

在多用户或多服务环境中,同时执行事务时可能会出现各种问题:

  1. 脏读:一个事务读取另一事务未提交的更改。
  2. 不可重复读取:在单个事务中,相同的查询在不同时间产生不同的结果。
  3. 幻读:一个事务读取了另一个事务已插入或删除的行,导致重复读取结果不一致。

为了管理这些问题,数据库提供了各种隔离级别。通过了解并设置适当的级别,您可以在数据完整性和系统性能之间取得平衡。

Spring @Transactional 中的隔离级别

Spring 的@Transactional注释允许您设置与标准 SQL 隔离级别紧密一致的隔离级别:

默认

  1. 它依赖于底层数据存储的默认隔离级别。
  2. 除非需要特定的隔离行为,否则这通常是一个安全的选择。

READ_UNCOMMITTED

  1. 这是最低级别的隔离。
  2. 事务可能会读取其他事务中未提交的更改,从而导致脏读。
  3. 虽然它提供了更好的性能,但它可能会损害数据完整性。

已提交读

  1. 事务只能读取已提交的更改。
  2. 它可以防止脏读,但仍然允许不可重复读。
  3. 这是一种常用的隔离级别,因为它提供了性能和数据一致性之间的平衡。

可重复读取

  1. 保证如果在同一个事务中多次读取某个值,结果将始终相同。
  2. 它可以防止脏读和不可重复读,但仍然允许幻读。

可串行化

  1. 最高级别的隔离。
  2. 确保与其他事务完全隔离,防止脏读、不可重复读和幻读。
  3. 虽然它提供了最强大的数据完整性保证,但由于采用了更严格的锁,它可能会变慢。

选择正确的隔离级别

选择隔离级别通常需要考虑数据完整性要求和系统的性能期望。以下是一些指导原则:

  1. 如果数据完整性至关重要并且您可以容忍潜在的性能开销,那么这SERIALIZABLE可能是最佳选择。
  2. 在某些情况下,为了获得更好的性能,偶尔的不一致是可以接受的,READ_COMMITTED甚至READ_UNCOMMITTED可能是合适的。
  3. 对于大多数典型的用例,READ_COMMITTED提供了良好的平衡。

例子:

@Service 
public  class  FinancialService { 
    @Autowired 
    private TransactionRepository transactionRepository; 
    @Transactional(isolation = Isolation.READ_COMMITTED) 
    public  void  transferFunds (Account from, Account to, double amount) { 
        // 资金转账的业务逻辑
    } 
}

结合传播和隔离

当我们谈论事务管理时,传播决定了事务的“边界”或“生命周期”,而隔离则定义了事务操作对其他并发事务的“可见性”。当组合起来时,它们共同塑造系统的事务行为,决定性能和数据完整性。

为什么要结合两者?

在某些情况下,仅设置传播或隔离是不够的。例如,您可能希望一个方法始终在其事务中执行(使用REQUIRES_NEW),但根据所涉及的业务逻辑具有不同的隔离级别。

常见场景

让我们探讨一些将两者结合起来会带来好处的情况:

高风险操作:

  • 对于金融交易等关键操作,您可能希望该方法始终在新事务中运行,以避免对其他操作产生任何干扰 ( REQUIRES_NEW)。此外,为了确保高数据完整性,您可能会选择更高的隔离级别,例如SERIALIZABLE.

批量处理:

  • 在批处理过程中,块操作很常见。每个块可能在其事务中运行,但不应看到其他块的未提交工作。在这里,REQUIRES_NEW传播和READ_COMMITTED隔离可以是一个恰当的组合。

报告操作:

  • 对于数据一致性不是主要关注点但性能才是主要关注点的报告或分析,操作可以加入现有事务 ( SUPPORTS),并且可以在较低的隔离度上进行操作,例如READ_UNCOMMITTED更快地获取数据。

Spring如何搭配

在 Spring 中,使用注释组合这两个设置非常简单@Transactional

@Service 
public  class  FinancialService { 
    @Autowired 
    private TransactionRepository transactionRepository; 
    @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.SERIALIZABLE) 
    public  void  highStakesOperation (Account from, Account to, double amount) { 
        // 这里的业务逻辑
    } 
}

组合时的注意事项

虽然组合这些设置可以提供对事务的强大控制,但必须谨慎:

  1. 死锁:较高的隔离级别(尤其是)SERIALIZABLE会增加系统中死锁的风险,尤其是与某些传播行为结合使用时。
  2. 性能:在确保数据完整性的同时,较高的隔离级别会影响性能。始终在实际负载下对系统进行分析和基准测试,以确保组合不会过度降低性能。
  3. 复杂性:自定义事务行为越多,维护和调试问题就越困难。确保团队充分了解所选设置并理解其含义。

结论

控制事务边界对于确保应用程序中的数据一致性和完整性至关重要。Spring框架在@Transactional注释中提供了一个强大的工具,允许开发人员微调事务的传播和隔离方面。通过理解并有效利用这些控件,开发人员可以构建健壮且容错的数据库驱动应用程序。

相关文章
|
Java 数据库 Spring
Spring事务的传播机制(行为、特性)
Spring事务的传播机制(行为、特性)
710 0
|
Java API 数据安全/隐私保护
掌握Spring Boot中的@Validated注解
【4月更文挑战第23天】在 Spring Boot 开发中,@Validated 注解是用于开启和利用 Spring 的验证框架的一种方式,特别是在处理控制层的输入验证时。本篇技术博客将详细介绍 @Validated 注解的概念和使用方法,并通过实际的应用示例来展示如何在项目中实现有效的数据验证
1123 3
|
9月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
1064 22
|
10月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1204 1
|
9月前
|
消息中间件 Java Kafka
消息队列比较:Spring 微服务中的 Kafka 与 RabbitMQ
本文深入解析了 Kafka 和 RabbitMQ 两大主流消息队列在 Spring 微服务中的应用与对比。内容涵盖消息队列的基本原理、Kafka 与 RabbitMQ 的核心概念、各自优势及典型用例,并结合 Spring 生态的集成方式,帮助开发者根据实际需求选择合适的消息中间件,提升系统解耦、可扩展性与可靠性。
639 1
消息队列比较:Spring 微服务中的 Kafka 与 RabbitMQ
|
9月前
|
存储 缓存 Java
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
缓存是提升应用性能的重要技术,Spring框架提供了丰富的缓存注解,如`@Cacheable`、`@CacheEvict`等,帮助开发者简化缓存管理。本文介绍了如何在Spring中配置缓存管理器,使用缓存注解优化数据访问,并探讨了缓存的最佳实践,以提升系统响应速度与可扩展性。
430 0
Spring中@Cacheable、@CacheEvict以及其他缓存相关注解的实用介绍
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
存储 NoSQL Java
Redis如何处理Hash冲突?
在 Redis 中,哈希表是一种常见的数据结构,通常用于存储对象的属性,对于哈希表,最常遇到的是哈希冲突,那么,当 Redis遇到Hash冲突会如何处理?这篇文章,我们将详细介绍Redis如何处理哈希冲突,并探讨其性能和实现细节。
510 2
|
Java 测试技术 数据库
Spring事务传播机制(最全示例)
在使用Spring框架进行开发时,`service`层的方法通常带有事务。本文详细探讨了Spring事务在多个方法间的传播机制,主要包括7种传播类型:`REQUIRED`、`SUPPORTS`、`MANDATORY`、`REQUIRES_NEW`、`NOT_SUPPORTED`、`NEVER` 和 `NESTED`。通过示例代码和数据库插入测试,逐一展示了每种类型的运作方式。例如,`REQUIRED`表示如果当前存在事务则加入该事务,否则创建新事务;`SUPPORTS`表示如果当前存在事务则加入,否则以非事务方式执行;`MANDATORY`表示必须在现有事务中运行,否则抛出异常;
2038 4
Spring事务传播机制(最全示例)
|
Java 关系型数据库 MySQL
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
2047 12