Spring 基于注解的自动化事务
Spring 基于注解的自动化事务
- 事务
- 开启事务自动化管理
- 声明式事务
- **回滚的默认机制?**
- @Transacitonal注解属性
- 注解属性:
- 事务管理器:transactionManager
- 原理
- 举例:PlatformTransactionManager接口方法说明
- 核心:事务拦截器TransactionInterceptor
- ★隔离级别
- 隔离级别的作用?
- 隔离级别分为?
- 读未提交(Isolation.READ_UNCOMMITTED)
- 读已提交(Isolation.READ_COMMITTED)
- 可重复读(Isolation.REPEATABLE_READ)
- ★传播行为
- 概述
- 传播行为详解
- 总结与对比
- timeout(同timeoutString):控制事务的超时时间
- readOnly:只读优化
- rollbackFor(rollbackForClassName):指定哪些编译异常需要回滚
- noRollbackFor(noRollbackForClassName):指明异常不回滚
- 参考连接:https://www.bilibili.com/video/BV14WtLeDEit/?p=80&share_source=copy_web&vd_source=053075eb8bd04ebb7bc161d3f4386d4f
事务
开启事务自动化管理
- 在SpringBoot启动类上,添加注解
@EnableTransactionManagement
package com.ssg.tx;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;// 开启基于注解的自动化事务管理
@EnableTransactionManagement
@SpringBootApplication
public class SsgTxApplication {public static void main(String[] args) {SpringApplication.run(SsgTxApplication.class, args);}}
声明式事务
声明式
vs 编程式
- 编程式:通过编写业务代码,程序员自行完成指定功能
- 声明式:通过声明业务需求,框架自动完成指定功能
声明式事务
- 定义:只需要告诉框架,这个方法需要事务,框架会自动在运行方法时执行事务的流程控制逻辑。
- Spring支持:@Transactional
@Transactional
属性
回滚的默认机制?
- 运行时异常,事务进行回滚。例如:空指针异常
- 编译时异常,事务不会滚。例如:IO异常
@Transacitonal注解属性
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {// 事务管理器,别名为transactionManager@AliasFor("transactionManager")String value() default "";// 事务管理器,别名为value@AliasFor("value")String transactionManager() default "";String[] label() default {};Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default -1;String timeoutString() default "";boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};
}
注解属性:
事务管理器:transactionManager
- 事务管理器是控制事务提交回滚的。
原理
- Spring 在运行时通过事务拦截器(TransactionInterceptor)根据 @Transactional 的限定符或 Bean 名称 找到对应的事务管理器 Bean,并由该事务管理器真正驱动事务。
- 默认不指定限定符时,Spring 会按照类型匹配唯一的事务管理器 Bean;若存在多个同类型 Bean,则必须显式用限定符或名字区分。
- 连接池(DataSource) 仅提供物理数据库连接;事务管理器依赖连接池。 事务管理器(DataSourceTransactionManager)里有一个字段
dataSource
,启动时就会注入一个DataSource
(通常就是连接池 HikariDataSource)。 - 事务管理器离不开这个 DataSource;没有它,事务管理器连数据库都连不上,更别提 commit/rollback。
- 连接池(HikariDataSource)本身并不知道也不关心“事务管理器”是谁。它只负责给出
Connection
,至于这连接是被事务管理器用、还是被你自己裸用,它根本无所谓。 - 连接池并不依赖事务管理器,可以独立存在。这就是“事务管理器依赖连接池,而非连接池去选择事务管理器”的含义。
- 事务管理器接口的选用与数据库访问技术有关:
- 关系型数据库(JDBC、Hibernate、MyBatis 等)→ 默认实现
PlatformTransactionManager
; - 响应式数据库(R2DBC、Mongo-Reactive 等)→ 实现
ReactiveTransactionManager
。
- 关系型数据库(JDBC、Hibernate、MyBatis 等)→ 默认实现
- 多数据源场景下,必须为每个 DataSource 显式声明一个独立的事务管理器 Bean,并用
@Qualifier
或限定符注解区分,防止将 MySQL 的事务管理器误用于 Oracle 数据库。
- 注意看注释
PlatformTransactionManager
、ReactiveTransactionManager
/*** @see #value* @see org.springframework.transaction.PlatformTransactionManager* @see org.springframework.transaction.ReactiveTransactionManager*/@AliasFor("value")String transactionManager() default "";
- transactionManager:事务管理器;控制事务的获取、提交、回滚。
底层默认使用哪个事务管理器?
- 根据数据源的不同,选择不同的事务管理器。
- 传统的JDBC/Mybatis这类阻塞式事务:选择
PlatformTransactionManager
- 对应R2DBC、Mongo-Reactive这类响应式事务:选择
ReactiveTransactionManager
举例:PlatformTransactionManager接口方法说明
public interface PlatformTransactionManager extends TransactionManager {// TransactionStatus:返回事务的连接信息、数据库的连接信息TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;void commit(TransactionStatus status) throws TransactionException;void rollback(TransactionStatus status) throws TransactionException;
}
- getTransaction()方法:作用:Spring 每次进入带有 @Transactional 的方法时,都会先调用它来 拿或开一个事务。返回值 TransactionStatus 里保存了当前连接(数据库连接,也可以理解为获取了一次数据库会话)、是否为新事务、是否已完成等内部信息。
- commit()方法作用:当业务方法正常返回、没有抛异常时,Spring 会调用它 提交 事务。这里的 status 就是刚才 getTransaction 返回的那个对象,里面记录了要提交哪一个连接/会话。
- rollback()方法作用:业务方法抛出运行时异常或受检异常(满足 rollback-rules)时,Spring 会调用它 回滚 事务。同样靠 status 找到对应的连接/会话来执行回滚。
核心:事务拦截器TransactionInterceptor
org.springframework.transaction.interceptor.TransactionInterceptor
- 事务的底层原理是一个切面。
- 核心:控制事务何时提交与回滚。他是一个编程方式的切面,不是注解式的切面。
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
}
MethodInterceptor
:是 Spring AOP 的核心拦截器接口,也被事务模块(TransactionInterceptor)实现。- MethodInterceptor.
invoke(...)
就是 Spring 给所有“方法级横切逻辑”(事务、权限、日志等)提供的统一钩子;TransactionInterceptor
利用这个钩子把声明式事务织入到业务方法的前后。
★隔离级别
隔离级别的作用?
为了防止当多个事务读写并发的时候出现的脏读、不可重复读、幻读问题的。
隔离级别分为?
- 读已提交:事务只会读取已经提交的数据,可避免脏读,但可能引发不可重复度和幻读。
- 读未提交:事务可以读取未提交的数据,易产生脏读、不可重复度和幻读。
- 可重复读:同一事务期间多次重复读取的数据相同。避免脏读和不可重复读,但仍有幻读的问题
- 串行化:最高隔离级别,完全禁止并发,只允许一个事务执行完毕之后才能执行另一个事务
级别问题 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读已提交 | × | √ | √ |
读未提交 | √ | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |
级别问题解读:
- 脏读:读到别人未提交的数据。
- 不可重复读:同一行两次内容不同(别人已提交更新)。
- 幻读:同一范围两次行数不同(别人已提交增删)。
读未提交(Isolation.READ_UNCOMMITTED)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
代码可以通过隔离级别,读取到事务未提交的数据
- 容易产生脏读(第一次与最终结果不一样)、不可重复读(多次读取到的数据不一样)
- 既能提交到脏数据,又导致了不可重复读,幻读。
读已提交(Isolation.READ_COMMITTED)
@Transactional(isolation = Isolation.READ_COMMITTED)
只能读取到别人已经提交的数据。
- 会导致不可重复读(别人提交的数据)和幻读。
可重复读(Isolation.REPEATABLE_READ)
- 也叫:快照读
- Mysql数据库事务,默认级别为:可重复读。
- Oracle数据库事务,默认级别为:读已提交。
@Transactional(isolation = Isolation.REPEATABLE_READ)
一个方法内存在多个同SQL方法,依次执行同一个sql方法,数据结果始终一致。(在这个方法执行期间,可以无法读取到其他方法已提交的结果)
- 只要事务不结束,读取到的数据结果从头到尾都是同一个。即使外界将这条数据删除,也是同一个结果。
★传播行为
传播行为定义这个方法该怎么用一个事务,大事务是否会影响我,我要不要事务。
- 应用场景:一个增删改方法里面,调用另一个增删改方法。(大事务里面套用另一个小事务),事务的嵌套问题,也就需要用传播行为来控制。
好的,这是对 Propagation
枚举(事务传播行为)的中文详细解释:
概述
Propagation
枚举定义了 Spring 事务的传播行为(Propagation Behavior)。它决定了当一个事务方法被另一个事务方法调用时,事务应该如何传播。例如,是加入现有事务,还是挂起现有事务并开启一个新事务。这是 Spring 事务管理中非常核心且强大的一个概念。
传播行为详解
-
REQUIRED
(需要事务)- 行为: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- 说明: 这是最常用的默认设置。它保证了方法一定在一个事务中运行。如果多个方法都使用
REQUIRED
并且相互调用,它们会共享同一个物理事务。如果其中任何一个方法发生回滚,整个事务都将回滚。 - 适用场景: 绝大多数需要进行数据库写操作的场景。
-
SUPPORTS
(支持事务)- 行为: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续执行。
- 说明: 该方法对事务没有强制要求,“有就用,没有就算了”。它依赖于调用方的事务上下文。
- 注意: 即使以非事务方式运行,它仍然会参与事务同步(如绑定同一数据库连接),这与完全没有事务注解的方法略有不同。
- 适用场景: 主要用于查询方法。如果调用方有事务,则查询在该事务内执行(保证一致性视图);如果没有,直接执行查询(效率更高)。
-
MANDATORY
(强制要求事务)- 行为: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- 说明: 该方法强制要求必须在一个已有的事务中被调用。它自己不会发起新事务。
- 适用场景: 用于必须作为更大事务的一部分来执行的方法,如果被非事务方法误调用,它会立即抛出异常以提醒开发者。
-
REQUIRES_NEW
(需要新事务)- 行为: 无论如何都会创建一个新的事务。如果当前存在事务,则将其挂起。
- 说明: 该方法总会启动一个独立的、全新的物理事务。新事务与旧事务完全隔离,互不影响。新事务提交或回滚后,旧事务再恢复执行。
- 注意: 事务挂起操作需要事务管理器的支持(如
JtaTransactionManager
需要服务器提供TransactionManager
)。 - 适用场景: 需要独立提交、不受外部事务失败影响的逻辑,例如日志记录(即使业务失败,日志仍需记录入库)。
-
NOT_SUPPORTED
(不支持事务)- 行为: 以非事务方式执行。如果当前存在事务,则将其挂起。
- 说明: 该方法强制不在事务中运行。它会挂起任何现有的事务,等自己非事务执行完毕后,再恢复原事务。
- 注意: 同样需要事务管理器支持挂起操作。
- 适用场景: 需要在不干扰调用方事务的情况下,执行某些不需要事务支持的操作(例如调用一个不支持事务的第三方服务)。
-
NEVER
(绝不)- 行为: 以非事务方式执行。如果当前存在事务,则抛出异常。
- 说明: 该方法不仅自己不以事务方式运行,还坚决不允许调用方在有事务的情况下调用它。
- 适用场景: 用于明确不应该在任何事务中执行的方法,防止误用。
-
NESTED
(嵌套事务)- 行为: 如果当前存在事务,则在其内部创建一个嵌套事务(保存点)来执行;如果当前没有事务,则其行为与
REQUIRED
一样。 - 说明: 这是一个非常特殊的行为。嵌套事务是外部事务的一个子事务,它的提交会随外部事务一起提交。但它的回滚是部分回滚,只会回滚到它自己创建的保存点,而不会导致整个外部事务回滚。外部事务的回滚则会回滚所有嵌套事务。
- 注意: 并非所有事务管理器都支持嵌套事务。主要支持 JDBC 的
DataSourceTransactionManager
(通过保存点实现)。JTA 事务管理器需要看具体厂商实现。 - 适用场景: 适用于一个复杂业务中,某些步骤可以独立失败而不影响整体主流程的场景。例如,处理一个订单列表,其中某单个订单处理失败不应回滚所有已成功处理的订单,但整个批量处理任务本身失败则应全部回滚。
- 行为: 如果当前存在事务,则在其内部创建一个嵌套事务(保存点)来执行;如果当前没有事务,则其行为与
总结与对比
传播行为 | 当前有事务 | 当前无事务 | 特点 |
---|---|---|---|
REQUIRED (默认) | 加入 | 创建新事务 | 保证在事务中运行 |
SUPPORTS | 加入 | 非事务运行 | 跟随调用方,不强制 |
MANDATORY | 加入 | 抛出异常 | 强制必须在事务中 |
REQUIRES_NEW | 挂起并创建新事务 | 创建新事务 | 创建独立的新事务 |
NOT_SUPPORTED | 挂起并非事务运行 | 非事务运行 | 强制非事务运行 |
NEVER | 抛出异常 | 非事务运行 | 禁止在事务中运行 |
NESTED | 嵌套事务 (保存点) | 创建新事务 | 部分提交/回滚 |
选择正确的传播行为对于构建健壮、符合预期的事务应用程序至关重要。
timeout(同timeoutString):控制事务的超时时间
- timeout:事务超时,以秒为单位,int类型。
- timeoutString:string类型
- 一旦超过约定时间,事务就会回滚。
- 超时时间是指:从方法开始,到最后一次数据库操作结束的时间。
@Transactional(timeout = 3, timeoutString = "3")
readOnly:只读优化
- 如果整个事务都是读操作,底层会开启一个只读优化。
- 如果readOnly,为true,则开启只读优化,默认为false。
rollbackFor(rollbackForClassName):指定哪些编译异常需要回滚
Class<? extends Throwable>[] rollbackFor() default {};
- 指明哪些异常需要回滚,不是所有异常都一定会引起事务回滚。
异常分类?
- 运行时异常
- 编译时异常
@Transactional(rollbackFor = {IOException.class}, rollbackForClassName = "java.lang.Exception")
- 回滚 = 运行时异常 + 指定异常
noRollbackFor(noRollbackForClassName):指明异常不回滚
- 不回滚 = 编译时异常 + 指定的不回滚异常
- 正常情况下,所有运行时异常都会进行回滚。
- 我们使用noRollbackFor,可以使部分运行时异常不回滚。
@Transactional(noRollbackFor = {RuntimeException.class}, noRollbackForClassName = {"java.lang.RuntimeException"})