跳至主要內容

Spring核心之事务管理

bsfc.tech大约 8 分钟框架Spring

事务管理

Spring事务管理是Spring框架中用于处理数据库操作时保证数据一致性和完整性的关键功能。它允许你在服务层的代码中声明性地管理事务,而不需要在具体的SQL操作中手动控制事务边界。Spring事务管理支持编程式和声明式两种方式,但声明式事务管理因其简洁和解耦的特性而被更广泛使用。

事务配置
事务配置

基本概念

  1. 事务:事务是数据库操作的基本单元,它包含了一组数据库操作命令。这些操作要么全部成功,要么全部失败,以确保数据的完整性。

  2. ACID原则:事务应遵循原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)四个基本属性。

Spring事务管理类型

  • 编程式事务管理:通过编写代码(如使用TransactionTemplate或者直接使用PlatformTransactionManager)来管理事务的开始、提交或回滚。这种方式更加灵活,但会使得代码与事务管理逻辑紧密耦合。

  • 声明式事务管理:通过在配置文件或者注解中声明事务的边界,由Spring自动管理事务的开始、提交或回滚。这种方式更加简洁,易于维护。

声明式事务管理实现方式

  1. 基于XML的配置:在Spring的XML配置文件中,使用<tx:advice>标签定义事务属性,并通过<aop:config><aop:pointcut>来指定哪些方法需要进行事务管理。

  2. 基于注解的配置:在Spring 3.0及以上版本,可以使用@Transactional注解直接在类或方法上声明事务属性,这是最常用的声明式事务管理方式。

@Transactional注解使用

  • 放置位置:可以放在类级别或方法级别。类级别表示该类中的所有公共方法都将启用相同的事务配置;方法级别则覆盖类级别的配置,为特定方法提供不同的事务设置。

  • 常用属性:

    • propagation:事务传播行为,默认为REQUIRED,定义了事务方法之间的交互规则。
    • isolation:事务的隔离级别,如READ_COMMITTED、REPEATABLE_READ等。
    • readOnly:是否为只读事务,默认为false。
    • timeout:事务超时时间,默认无限制。
    • rollbackFor:遇到指定异常时回滚事务。
    • noRollbackFor:遇到指定异常时不回滚事务。

注意事项

  • 确保使用Spring的代理对象调用事务方法,直接实例化对象将无法触发事务管理。
  • 数据访问层(如JdbcTemplate, Hibernate等)应由Spring管理,以确保事务拦截器能够生效。
  • 在异常处理时,要根据业务需求合理选择是否抛出或捕获检查型异常(继承自java.lang.Exception),因为未被捕获的检查型异常默认会导致事务回滚。

Spring事务管理为开发者提供了强大而灵活的机制来处理数据库操作中的事务问题,极大地简化了企业级应用的开发复杂度。

七种事务传播行为

Spring框架定义了七种事务传播行为,每种行为适用于不同的业务场景,下面是对这些传播行为及其典型应用场景的简要说明:

1. PROPAGATION_REQUIRED(默认)

  • 场景: 这是最常用的传播行为。如果当前没有事务,就新建一个事务;如果已经存在一个事务,则加入到这个事务中。适用于大多数业务操作,确保操作的一致性。
  • 示例: 服务层的一个方法需要更新两个不同表的数据,这两个操作应该在一个事务中完成,以保持数据一致性。

2. PROPAGATION_SUPPORTS

  • 场景: 该传播行为表示当前方法不需要事务上下文,但是如果存在当前事务的话,也可以在这个事务中执行。适用于那些可以独立于事务之外运行的查询操作。
  • 示例: 查询方法,不需要改变数据状态,可以在有事务环境中运行,也可以在无事务环境中运行。

3. PROPAGATION_MANDATORY

  • 场景: 如果当前存在事务,则方法在该事务中运行;如果不存在事务,则抛出异常。适用于那些必须在事务上下文中执行的方法。
  • 示例: 执行一些审计记录的操作,这些操作必须在一个存在的事务中进行,以确保它们与业务操作保持一致。

4. PROPAGATION_REQUIRES_NEW

  • 场景: 总是新建一个事务,如果当前存在事务,则把当前事务挂起。适用于需要独立事务处理的场景,如支付过程中的扣款和发送通知,两者需要各自独立成功或失败。
  • 示例: 在一个事务中需要调用第三方服务,我们希望这个调用不影响当前事务,即使它失败也不应该回滚主事务。

5. PROPAGATION_NOT_SUPPORTED

  • 场景: 该传播行为明确表示该方法不应该在事务中运行,如果存在事务,则将当前事务挂起。适用于那些不需要事务且可能对性能有严格要求的操作。
  • 示例: 大批量的数据读取操作,为了提高效率,可以选择不在事务中执行。

6. PROPAGATION_NEVER

  • 场景: 方法不应该在事务中执行,如果当前存在事务,则抛出异常。适用于绝对不允许在事务中执行的操作。
  • 示例: 一些只读的统计分析操作,如果发现它们被错误地放在了一个事务中,应该立即报错。

7. PROPAGATION_NESTED

  • 场景: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则其行为类似于PROPAGATION_REQUIRED。嵌套事务可以有多个保存点,回滚时可以回滚到某一个保存点,而不是直接回滚整个事务。
  • 示例: 需要在事务中执行一系列操作,但某些操作失败时,只想回滚这些失败的操作,而保留之前成功的操作,例如银行转账中的一部分手续费扣除操作。

理解这些传播行为并根据具体业务场景选择合适的传播模式,对于构建健壮、高效的应用系统至关重要。

事务失效

有多种场景可能会导致事务失效

在使用Spring框架的@Transactional注解进行声明式事务管理时,有多种场景可能会导致事务失效,以下是常见的一些原因:

  1. 非public方法:如果@Transactional注解应用在非public修饰的方法上,事务将不会生效。因为Spring使用Java的动态代理来实现事务管理,非public方法无法被外部调用,因此代理对象无法介入事务管理。

  2. 内部方法调用:当一个类的某个方法(假设为methodA)被@Transactional注解,并在同一个类的另一个方法(如methodB)内部直接调用methodA时,事务可能不会生效。这是因为在这种情况下,调用是直接发生在目标对象上而非代理对象,Spring的AOP代理无法拦截到这次调用,从而无法应用事务管理。

  3. 异常处理不当

    • 如果在事务方法内捕获了异常并直接处理了,没有再次抛出,那么Spring默认的事务回滚策略不会生效。
    • 抛出了非运行时异常(即检查型异常,继承自Exception而非RuntimeException),但没有在rollbackFor属性中指定该异常类型,也会导致事务不回滚。
  4. 事务传播行为设置错误:如果@Transactionalpropagation属性配置不恰当,例如设置为NOT_SUPPORTEDNEVER,则即使方法中发生异常也不会启动事务或回滚事务。

  5. 方法被非事务方法捕获异常:当事务方法抛出异常被同类中的非事务方法捕获时,事务可能不会回滚。

  6. 注解放置位置不当:如果事务管理配置正确但在不应该的地方使用(比如应用在接口上而不是实现类上),或者在Spring未管理的bean上使用@Transactional注解,事务同样不会工作。

  7. 数据库不支持事务:如果你使用的数据库引擎不支持事务(如某些MySQL存储引擎设置为MyISAM),即使应用层面配置了事务也不会有效果。

  8. 没有启用事务管理器:忘记在Spring配置中启用事务管理器(如DataSourceTransactionManagerJtaTransactionManager)也会导致事务注解失效。

  9. Timeout超时:如果事务等待资源超时,事务可能被回滚,具体行为取决于超时设置和配置。

  10. 注解在private或static方法上:由于Spring使用JDK动态代理或CGLIB代理来应用事务,这些代理不能代理private或static方法,所以@Transactional注解对它们无效。