Spring源码解析--深入Spring事务原理

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: 本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。

本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。

1、事务基本概念

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

特点:事务是恢复和并发控制的基本单位。事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。

这四个属性通常称为 ACID 特性。

原子性(Automicity)

一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(Consistency)

事务必须是使数据库从一个一致性状态变到另一个一致性状态。

一致性与原子性是密切相关的。

隔离性(Isolation)

一个事务的执行不能被其他事务干扰。

即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(Durability)

持久性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

2、事务的基本原理

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring 是无法提供事务功能的。

对于纯 JDBC 操作数据库,想要用到事务,可以按照以下步骤进行:

1、获取连接 Connection con = DriverManager.getConnection()

2、开启事务 con.setAutoCommit(true/false);

3、执行 CRUD

4、提交事务/回滚事务 con.commit() / con.rollback();

5、关闭连接 conn.close();

使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。

那么 Spring 是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?

解决这个问题,也就可以从整体上理解 Spring 的事务管理实现原理了。

下面简单地介绍下,注解方式为例子配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional 标识。

Spring 在启动的时候会去解析生成相关的 bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction 的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。

真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的。

3、Spring 事务的传播属性

所谓 spring 事务的传播属性,就是定义在存在多个事务同时存在的时候,spring 应该如何处理这些事务的行为。这些属性在 TransactionDefinition 中定义,具体常量的解释见下表:
在这里插入图片描述
在这里插入图片描述
4、数据库隔离级别
在这里插入图片描述
脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。

如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。

不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。

幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。

总结:

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle,少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB

相关Mysql的事务,在《深入精通Mysql(四)》MySQL 事务机制,中高级开发面试必问!中也已经进行了详细的介绍,感兴趣的同学可以去看看。

5、Spring 中的隔离级别
在这里插入图片描述
6、事务的嵌套

通过上面的理论知识的铺垫,我们大致知道了数据库事务和 Spring 事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解 Spring 事务传播的机制。

假设外层事务 Service A 的 Method A() 调用 内层 Service B 的 Method B()

PROPAGATION_REQUIRED(Spring 默认)

如果 ServiceB.MethodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行ServiceA.MethodA() 的时候 Spring 已经起了事务,这时调用 ServiceB.MethodB(),ServiceB.MethodB() 看到自己已经运行在 ServiceA.MethodA() 的事务内部,就不再起新的事务。

假如 ServiceB.MethodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在 ServiceA.MethodA() 或者在 ServiceB.MethodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW

比如我们设计 ServiceA.MethodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.MethodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。

那么当执行到 ServiceB.MethodB() 的时候,ServiceA.MethodA() 所在的事务就会挂起,ServiceB.MethodB() 会起一个新的事务,等待 ServiceB.MethodB() 的事务完成以后,它才继续执行。

他 与 PROPAGATION_REQUIRED 的 事 务 区 别 在 于 事 务 的 回 滚 程 度 了 。

因 为ServiceB.MethodB() 是新起一个事务,那么就是存在两个不同的事务。

如果ServiceB.MethodB() 已 经 提 交 , 那 么 ServiceA.MethodA() 失 败 回 滚 ,ServiceB.MethodB() 是不会回滚的。如果 ServiceB.MethodB() 失败回滚,如果他抛出的异常被 ServiceA.MethodA() 捕获,ServiceA.MethodA() 事务仍然可能提交(主要看B抛出的异常是不是 A 会回滚的异常)。

PROPAGATION_SUPPORTS

假设 ServiceB.MethodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.MethodB()时,如果发现 ServiceA.MethodA()已经开启了一个事务,则加入当前的事务,如果发现 ServiceA.MethodA()没有开启事务,则自己也不开启事务。

这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED

现 在 的 情 况 就 变 得 比 较 复 杂 了 , ServiceB.MethodB() 的 事 务 属 性 被 配 置 为PROPAGATION_NESTED, 此时两者之间又将如何协作呢?

ServiceB.MethodB() 如果 rollback, 那么内部事务(即 ServiceB.MethodB()) 将回滚到它执行前的 SavePoint而外部事务(即 ServiceA.MethodA()) 可以有以下两种处理方式:

捕获异常,执行异常分支逻辑
在这里插入图片描述
这 种 方 式 也 是 嵌 套 事 务 最 有 价 值 的 地 方 , 它 起 到 了 分 支 执 行 的 效 果 , 如 果ServiceB.MethodB()失败, 那么执行 ServiceC.MethodC(), 而 ServiceB.MethodB()已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过),这 种 特 性 可 以 用 在 某 些 特 殊 的 业 务 中 , 而 PROPAGATION_REQUIRED 和PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB.MethodB()) rollback, 那么首先 ServiceB.MethodB() 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA.MethodA()) 将根据具体的配置决定自己是commit 还是 rollback。

另外三种事务传播属性基本用不到,在此不做分析。

7、Spring 事务 API 架构图
在这里插入图片描述
使用 Spring 进行基本的 JDBC 访问数据库有多种选择。

Spring 至少提供了三种不同的工作模式:JdbcTemplate, 一个在 Spring2.5 中新提供的 SimpleJdbc 类能够更好的处理数据库元数据;

还有一种称之为 RDBMS Object 的风格的面向对象封装方式, 有点类似于 JDO 的查询设计。 我们在这里简要列举你采取某一种工作方式的主要理由. 不过请注意, 即使你选择了其中的一种工作模式, 你依然可以在你的代码中混用其他任何一种模式以获取其带来的好处和优势。

所有的工作模式都必须要求 JDBC 2.0 以上的数据库驱动的支持, 其中一些高级的功能可能需要 JDBC 3.0 以上的数据库驱动支持。

JdbcTemplate - 这是经典的也是最常用的 Spring 对于 JDBC 访问的方案。

这也是最低级别的封装, 其他的工作模式事实上在底层使用了 JdbcTemplate 作为其底层的实现基础。

JdbcTemplate 在 JDK 1.4 以上的环境上工作得很好。

NamedParameterJdbcTemplate - 对 JdbcTemplate 做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的 JDBC 所使用的“?”作为参数的占位符。

这种方式在你需要为某个 SQL 指定许多个参数时,显得更加直观而易用。该特性必须工作在 JDK1.4 以上。

SimpleJdbcTemplate - 这 个 类 结 合 了 JdbcTemplate 和NamedParameterJdbcTemplate 的最常用的功能,同时它也利用了一些 Java 5 的特性所带来的优势,例如泛型、varargs 和 autoboxing 等,从而提供了更加简便的 API 访问方式。需要工作在 Java 5 以上的环境中。

SimpleJdbcInsert 和 SimpleJdbcCall - 这两个类可以充分利用数据库元数据的特性来简化配置。通过使用这两个类进行编程,你可以仅仅提供数据库表名或者存储过程的名称以及一个 Map 作为参数。其中 Map 的 key 需要与数据库表中的字段保持一致。

这两个类通常和 SimpleJdbcTemplate 配合使用。这两个类需要工作在 JDK 5 以上,同时数据库需要提供足够的元数据信息。RDBMS 对象包括 MappingSqlQuery, SqlUpdate and StoredProcedure - 这种方式允许你在初始化你的数据访问层时创建可重用并且线程安全的对象。

该对象在你定义了你的查询语句,声明查询参数并编译相应的 Query 之后被模型化。一旦模型化完成,任何执行函数就可以传入不同的参数对之进行多次调用。这种方式需要工作在 JDK 1.4 以上。

异常处理

异常结构如下:
在这里插入图片描述
SQLExceptionTranslator 是 一 个 接 口 , 如 果 你 需 要 在 SQLException 和org.springframework.dao.DataAccessException 之间作转换,那么必须实现该接口。

转换器类的实现可以采用一般通用的做法(比如使用 JDBC 的 SQLState code),如果为了使转换更准确,也可以进行定制(比如使用 Oracle 的 error code)。

SQLErrorCodeSQLExceptionTranslator 是 SQLExceptionTranslator 的默认实现。 该实现使用指定数据库厂商的 error code,比采用 SQLState 更精确。转换过程基于一个JavaBean ( 类 型 为 SQLErrorCodes ) 中 的 error code 。

这 个 JavaBean 由SQLErrorCodesFactory 工厂类创建,其中的内容来自于 “sql-error-codes.xml”配置文 件 。

该 文 件 中 的 数 据 库 厂 商 代 码 基 于 Database MetaData 信 息 中 的DatabaseProductName,从而配合当前数据库的使用。

SQLErrorCodeSQLExceptionTranslator 使用以下的匹配规则:首 先 检 查 是 否 存 在 完 成 定 制 转 换 的 子 类 实 现 。 通 常SQLErrorCodeSQLExceptionTranslator 这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用。

接着将 SQLException 的 error code 与错误代码集中的 error code 进行匹配。 默认情况下错误代码集将从 SQLErrorCodesFactory 取得。 错误代码集来自 classpath 下的sql-error-codes.xml 文件,它们将与数据库 metadata 信息中的 database name 进行映射。

使用 fallback 翻译器。SQLStateSQLExceptionTranslator 类是缺省的 fallback 翻译器。config 模块NamespaceHandler 接口,DefaultBeanDefinitionDocumentReader 使用该接口来处理在 spring xml 配置文件中自定义的命名空间。
在这里插入图片描述
在 jdbc 模块,我们使用 JdbcNamespaceHandler 来处理 jdbc 配置的命名空间,其代码如下:
在这里插入图片描述
其中 , EmbeddedDatabaseBeanDefinitionParser 继 承 了AbstractBeanDefinitionParser , 解 析 元 素 , 并 使 用EmbeddedDatabaseFactoryBean 创建一个 BeanDefinition。

在core 模块

1.JdbcTeamplate 对象,其结构如下:
在这里插入图片描述
2.RowMapper
在这里插入图片描述
3.元数据 metaData 模块

本节中 Spring 应用到工厂模式,结合代码可以更具体了解。

在这里插入图片描述
CallMetaDataProviderFactory 创建 CallMetaDataProvider 的工厂类,其代码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
TableMetaDataProviderFactory 创建 TableMetaDataProvider 工厂类,其创建过程如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用 SqlParameterSource 提供参数值

使用 Map 来指定参数值有时候工作得非常好,但是这并不是最简单的使用方式。Spring提供了一些其他的 SqlParameterSource 实现类来指定参数值。

我们首先可以看看BeanPropertySqlParameterSource 类,这是一个非常简便的指定参数的实现类,只要你有一个符合 JavaBean 规范的类就行了。它将使用其中的 getter 方法来获取参数值。

SqlParameter 封 装 了 定 义 sql 参 数 的 对 象 。 CallableStateMentCallback ,PrePareStateMentCallback,StateMentCallback,ConnectionCallback 回调类分别对应 JdbcTemplate 中的不同处理方法。
在这里插入图片描述
simple 实现
在这里插入图片描述
Spring 通过 DataSource 获取数据库的连接。Datasource 是 jdbc 规范的一部分,它通过 ConnectionFactory 获取。一个容器和框架可以在应用代码层中隐藏连接池和事务管理。

当使用 spring 的 jdbc 层,你可以通过 JNDI 来获取 DataSource,也可以通过你自己配置的第三方连接池实现来获取。

流行的第三方实现由 apache Jakarta Commons dbcp 和 c3p0。
在这里插入图片描述

TransactionAwareDataSourceProxy 作为目标 DataSource 的一个代理, 在对目标DataSource 包装的同时,还增加了 Spring 的事务管理能力, 在这一点上,这个类的功能非常像 J2EE 服务器所提供的事务化的 JNDI DataSource。

该类几乎很少被用到,除非现有代码在被调用的时候需要一个标准的 JDBC DataSource接口实现作为参数。 这种情况下,这个类可以使现有代码参与 Spring 的事务管理。

通常最好的做法是使用更高层的抽象 来对数据源进行管理,比如 JdbcTemplate 和DataSourceUtils 等等。

注意:DriverManagerDataSource 仅限于测试使用,因为它没有提供池的功能,这会导致在多个请求获取连接时性能很差。

object 模块
在这里插入图片描述
再聊JdbcTemplate

JdbcTemplate 是 core 包的核心类。

它替我们完成了资源的创建以及释放工作,从而简化了我们对 JDBC 的使用。 它还可以帮助我们避免一些常见的错误,比如忘记关闭数据库连接。

JdbcTemplate 将完成 JDBC 核心处理流程,比如 SQL 语句的创建、执行,而把 SQL 语句的生成以及查询结果的提取工作留给我们的应用代码。

它可以完成 SQL 查询、更新以及调用存储过程,可以对ResultSet进行遍历并加以提取。

它还可以捕获JDBC异常并将其转换成 org.springframework.dao 包中定义的,通用的,信息更丰富的异常。

使用 JdbcTemplate 进行编码只需要根据明确定义的一组契约来实现回调接口。

PreparedStatementCreator 回调接口通过给定的 Connection 创建一个PreparedStatement,包含 SQL 和任何相关的参数。

CallableStatementCreateor 实现同样的处理,只不过它创建的是 CallableStatement。 RowCallbackHandler 接口则从数据集的每一行中提取值。

我们可以在DAO 实现类中通过传递一个 DataSource 引用来完成 JdbcTemplate 的实例化,也可以在 Spring 的 IOC 容器中配置一个 JdbcTemplate 的 bean 并赋予 DAO 实现类作为一个实例。

需要注意的是 DataSource 在 Spring 的 IOC 容器中总是配制成一个bean,第一种情况下,DataSource bean 将传递给 service,第二种情况下 DataSource bean 传递给 JdbcTemplate bean。

NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 类为 JDBC 操作增加了命名参数的特性支持,而不是传 统 的 使 用 ( '?' ) 作 为 参 数 的 占 位 符 。

NamedParameterJdbcTemplate 类 对JdbcTemplate 类进行了封装, 在底层,JdbcTemplate 完成了多数的工作。

浅谈分布式事务

现今互联网界,分布式系统和微服务架构盛行。一个简单操作,在服务端非常可能是由多个服务和数据库实例协同完成的。在一致性要求较高的场景下,多个独立操作之间的一致性问题显得格外棘手。

基于水平扩容能力和成本考虑,传统的强一致的解决方案(e.g.单机事务)纷纷被抛弃。其理论依据就是响当当的 CAP 原理。往往为了可用性和分区容错性,忍痛放弃强一致支持,转而追求最终一致性。

分布式系统的特性

在分布式系统中,同时满足 CAP 定律中的一致性 Consistency、可用性 Availability 和分区容错性 Partition Tolerance 三者是不可能的。

在绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。

分布式事务服务(Distributed Transaction Service,DTS)是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。

CAP 理论告诉我们在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的,所以我们只能在一致性和可用性之间进行权衡。

为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性。

数据一致性理解:

强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。

弱一致性:系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。

最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。

想了解下期面试题请关注我!!!

相关文章
|
5月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
5月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
693 22
|
5月前
|
缓存 安全 Java
Spring Security通用权限管理模型解析
Spring Security作为Spring生态的核心安全框架,结合RBAC与ACL权限模型,基于IoC与AOP构建灵活、可扩展的企业级权限控制体系,涵盖认证、授权流程及数据库设计、性能优化等实现策略。
405 0
|
5月前
|
缓存 安全 Java
Spring Security权限管理解析
Spring Security是Spring生态中的核心安全框架,采用认证与授权分离架构,提供高度可定制的权限管理方案。其基于过滤器链实现认证流程,通过SecurityContextHolder管理用户状态,并结合RBAC模型与动态权限决策,支持细粒度访问控制。通过扩展点如自定义投票器、注解式校验与前端标签,可灵活适配多租户、API网关等复杂场景。结合缓存优化与无状态设计,适用于高并发与前后端分离架构。
438 0
|
5月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
5月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1970 0
|
5月前
|
SQL Java 数据库连接
Spring Data JPA 技术深度解析与应用指南
本文档全面介绍 Spring Data JPA 的核心概念、技术原理和实际应用。作为 Spring 生态系统中数据访问层的关键组件,Spring Data JPA 极大简化了 Java 持久层开发。本文将深入探讨其架构设计、核心接口、查询派生机制、事务管理以及与 Spring 框架的集成方式,并通过实际示例展示如何高效地使用这一技术。本文档约1500字,适合有一定 Spring 和 JPA 基础的开发者阅读。
593 0
|
4月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
4月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
4月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。

热门文章

最新文章

推荐镜像

更多
  • DNS