【Spring学习笔记 九】Spring声明式事务管理实现机制

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【Spring学习笔记 九】Spring声明式事务管理实现机制

什么是事务?事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用,关乎数据准确性的地方我们一定要用到事务,防止业务逻辑出错。

什么是事务管理,事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性。就像银行的自助取款机,通常都能正常为客户服务,但是也难免遇到操作过程中机器突然出故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过取款机一样,以保证用户和银行的利益都不受损失

关于事务及事务管理其实我们在MySQL以及Redis部分学习的时候已经深入了解过了,在我的这篇博客:【MySQL数据库原理 七】MySQL数据库事务及锁机制里详细介绍了MySql的事务实现方式,并且在【Java Web编程 十三】深入理解JDBC规范中提及了JDBC是如何使用事务的,在【MyBatis学习笔记 二】MyBatis基本操作CRUD及配置解析中提及了MyBatis事务提交的方法,在另一篇博客:【Redis核心知识 三】Redis的事务机制里详细介绍了Redis的事务实现机制,今天这篇Blog从Spring的角度出发和实现方式去讨论下Spring是如何进行事务管理的,又是通过什么机制来实现事务管理的。

事务特性

在学习MySql中就学习过事务的四大特性,也可以理解为事务的四大原则,也就是我们常说的ACID:

  • 原子性(Atomicity),事务是最⼩的执⾏单位,不允许分割。事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响
  • 一致性(Consistency),是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
  • 隔离性(Isolation),是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离,要串行执行。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
  • 持久性(Durability),是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,导致数据库因为故障而没有执行事务的重大错误。

满足这四个基本原则或者特性,我们就说这个事务是有效的。

事务分类

事务的分类方式有好几种,分别安装事务生效范围、实现方式以及编程方式进行划分。

本地事务全局(分布式)事务

按照事务的生效范围,事务可以分类为:本地事务全局(分布式)事务

  • 本地事务:普通事务,独立一个数据库,能保证在该数据库上操作的ACID;
  • 全局(分布式)事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;

JDBC事务JTA事务

按照Java事务类型实现方式,事务可以分类为:JDBC事务JTA事务

  • JDBC事务:即为上面说的数据库事务中的本地事务,通过connection对象控制管理
  • JTA事务:指Java事务API(Java Transaction API),是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务

声明式事务和编程式事务

按照是否通过编程或是否对业务代码有侵入性分为声明式事务编程式事务

  • 编程式事务:通过编程代码在业务逻辑时需要时自行实现,粒度更小,编程式事务是侵入性事务管理,在代码中直接使用底层的PlatformTransactionManager、使用TransactionTemplate。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法
  • 声明式事务:通过注解或XML配置实现,粒度大一些,但对业务无侵入性

我们通过Spring可以实现全局的JTA类型的声明式事务,这也是我们最常用的一种事务类型组合

Spring事务实现原理

Spring 框架中最重要的事务相关API有三个:TransactionDefinition、PlatformTransactionManager、TransactionStatus。所谓事务管理,其实就是按照给定的事务规则来执行提交或者回滚操作

  • 给定的事务规则就是用 TransactionDefinition 表示的
  • 按照规则执行提交或者回滚操作则是用 PlatformTransactionManager 来表示

TransactionStatus 的作用则是用于表示一个运行着的事务的状态,也就是事务操作的结果或进度。

TransactionDefinition接口功能

TransactionDefinition接口管理了一个事务具体的定义和全部属性,的主要方法如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    //返回事务的传播行为,由是否有一个活动的事务来决定一个事务调用
    default int getPropagationBehavior() {
        return 0;
    }
    //返回事务的隔离级别,事务管理器依据它来控制另外一个事务能够看到本事务内的哪些数据
    default int getIsolationLevel() {
        return -1;
    }
    //返回事务必须在多少秒内完毕
    default int getTimeout() {
        return -1;
    }
    //事务是否仅仅读,事务管理器可以依据这个返回值进行优化。确保事务是仅仅读的
    default boolean isReadOnly() {
        return false;
    }
    @Nullable
    default String getName() {
        return null;
    }
    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }
}

1 事务隔离级别

指若干个并发的事务之间的隔离程度,TransactionDefinition接口中定义了5个表示隔离级别的常量

int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;

各个隔离级别解释如下:

  • ISOLATION_DEFAULT :默认值-1,表示使用底层数据库的默认隔离级别,对大部分数据库而言,通常这值就是ISOLATION_READ_COMMITTED,也就是读已提交
  • ISOLATION_READ_UNCOMMITTED读未提交,该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据,该级别可能导致脏读、不可重复读和幻读,因此很少使用该隔离级别,比如PostgreSQL实际上并没有此级别
  • ISOLATION_READ_COMMITTED
  • ISOLATION_READ_COMMITTED读已提交,(Oracle默认级别)该隔离级别表示一个事务只能读取另一个事务已经提交的数据,即允许从已经提交的并发事务读取,该级别可以防止脏读,但幻读和不可重复读仍可能会发生;
  • ISOLATION_REPEATABLE_READ可重复读,(MySQL默认级别)该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同,即对相同字段的多次读取的结果是一致的,除非数据被当前事务本事改变。该级别可以防止脏读和不可重复读,但幻读仍可能发生;
  • ISOLATION_SERIALIZABLE串行化,(完全服从ACID的隔离级别)所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读和幻读,但严重影响程序的性能,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的

关于脏读、幻读和不可重复读的一些概念,可以参照我这篇Blog:【MySQL数据库原理 七】MySQL数据库事务及锁机制这里不再赘述。

2 事务传播机制

事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就需要事务传播机制的配置来确定怎么样执行;在TransactionDefinition接口中定义了以下几个表示传播机制的常量,值为0~6:

int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;

各个传播性配置解释如下:

  • PROPAGATION_REQUIRED默认值,能满足绝大部分业务需求如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行,假设 ServiceX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中,也就是我们刚才的几个方法存在调用,所以会被放在一组事务当中!
  • PROPAGATION_SUPPORTS :如果外层有事务,则加入外层事务;如果外层没有事务,则直接以非事务的方式继续运行。完全依赖外层的事务
  • PROPAGATION_MANDATORY:如果外层有事务,则加入外层事务,如果外层没有事务,则抛出异常
  • PROPAGATION_REQUIRES_NEW :该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复外层事务的执行。如果外层没有事务,执行当前新开启的事务即可,挂起事务指的是将当前事务的属性如事务名称,隔离级别等属性保存在一个变量中,同时将当前线程中所有和事务相关的ThreadLocal变量设置为从未开启过线程一样。Spring维护着一个当前线程的事务状态,用来判断当前线程是否在一个事务中以及在一个什么样的事务中,挂起事务后,当前线程的事务状态就好像没有事务
  • PROPAGATION_NOT_SUPPORTED:该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
  • PROPAGATION_NEVER,该传播机制不支持外层事务,即如果外层有事务就抛出异常
  • PROPAGATION_NESTED:该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚

传播机制回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行

3 只读

如果一个事务只对数据库执行读操作,那么该数据库就可能利用事务的只读特性采取优化措施。通过把一个事务声明为只读,可以给数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义,在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读

//事务是否仅仅读,事务管理器可以依据这个返回值进行优化。确保事务是仅仅读的
    default boolean isReadOnly() {
        return false;
    }

4 事务超时

指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务,在TransactionDefinition中以int的值来表示超时时间,其单位是秒;默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制

//返回事务必须在多少秒内完毕
    default int getTimeout() {
        return -1;
    }

5 Spring事务回滚规则

默认配置下,Spring只有在抛出的异常为运行时异常(runtime exception)时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Error也会导致事务回滚),而抛出受检查异常(checked exception)则不会导致事务回滚,除此之外:

  • 可以声明在抛出哪些异常时回滚事务,包括checked异常
  • 可以声明哪些异常抛出时不回滚事务,即使异常是运行时异常
  • 可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚

也就是说比较灵活,我们一般使用默认的方式就可以了

PlatformTransactionManager接口功能

Spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口,用于执行具体的事务操作。PlatformTransactionManager 接口中定义的主要方法如下

package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager extends TransactionManager {
    //获得当前事务状态
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

根据底层所使用的不同的持久化 API 或框架,PlatformTransactionManager 的主要实现类大致如下:

  • DataSourceTransactionManager:适用于使用JDBC和iBatis进行数据持久化操作的情况,因为我们的Spring框架整合的是MyBatis,所以我们重点讨论这种情况
  • HibernateTransactionManager:适用于使用Hibernate进行数据持久化操作的情况。
  • JpaTransactionManager:适用于使用JPA进行数据持久化操作的情况。

TransactionStatus接口功能

TransactionStatus 表示事务状态, TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;方法返回一个 TransactionStatus 对象,表示一个事务的状态,部分方法如下:

package org.springframework.transaction;
import java.io.Flushable;
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    boolean hasSavepoint();
    void flush();
}

其实现接口实现的一些判断方法如下:

//判断事务是否只读
    boolean isReadOnly() ;
    //是否是一个新的事务
    boolean isNewTransaction();
    //判断是否有回滚点
    boolean hasSavepoint();
    //将一个事务标识为不可提交的。在调用完setRollbackOnly()后只能被回滚
    //在大多数情况下,事务管理器会检测到这一点,在它发现事务要提交时会立刻结束事务。
    //调用完setRollbackOnly()后,数数据库可以继续执行select,但不允许执行update语句,因为事务只可以进行读取操作,任何修改都不会被提交。
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    //判断事务是否已经完成
    boolean isCompleted();
    Object createSavepoint() throws TransactionException;
    void rollbackToSavepoint(Object var1) throws TransactionException;
    void releaseSavepoint(Object var1) throws TransactionException;



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
2月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理
|
3月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
3月前
|
Java 开发工具 Spring
Spring的Factories机制介绍
Spring的Factories机制介绍
61 1
|
3月前
|
Java Spring 开发者
掌握Spring事务管理,打造无缝数据交互——实用技巧大公开!
【8月更文挑战第31天】在企业应用开发中,确保数据一致性和完整性至关重要。Spring框架提供了强大的事务管理机制,包括`@Transactional`注解和编程式事务管理,简化了事务处理。本文深入探讨Spring事务管理的基础知识与高级技巧,涵盖隔离级别、传播行为、超时时间等设置,并介绍如何使用`TransactionTemplate`和`PlatformTransactionManager`进行编程式事务管理。通过合理设计事务范围和选择合适的隔离级别,可以显著提高应用的稳定性和性能。掌握这些技巧,有助于开发者更好地应对复杂业务需求,提升应用质量和可靠性。
42 0
|
4月前
|
Java 数据库连接 API
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
206 1
|
4月前
|
存储 安全 Java
实现基于Spring Cloud的分布式配置管理
实现基于Spring Cloud的分布式配置管理
|
4月前
|
安全 Java API
构建基于Spring Boot的REST API安全机制
构建基于Spring Boot的REST API安全机制
|
4月前
|
XML Java 关系型数据库
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景
117 0
|
4月前
|
安全 Java Spring
Spring Boot中的环境配置和管理
Spring Boot中的环境配置和管理