Spring 基于注解的自动化事务

Spring 基于注解的自动化事务


事务

开启事务自动化管理

  • 在SpringBoot启动类上,添加注解@EnableTransactionManagement
java 复制代码
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注解属性

java 复制代码
@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
  • 多数据源 场景下,必须为每个 DataSource 显式声明一个独立的事务管理器 Bean,并用 @Qualifier 或限定符注解区分,防止将 MySQL 的事务管理器误用于 Oracle 数据库。

  • 注意看注释PlatformTransactionManagerReactiveTransactionManager
java 复制代码
	/**
	 * @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接口方法说明
java 复制代码
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
  • 事务的底层原理是一个切面。
  • 核心:控制事务何时提交与回滚。他是一个编程方式的切面,不是注解式的切面。
java 复制代码
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
}
  • MethodInterceptor:是 Spring AOP 的核心拦截器接口,也被事务模块(TransactionInterceptor)实现。
  • MethodInterceptor.invoke(...) 就是 Spring 给所有"方法级横切逻辑"(事务、权限、日志等)提供的统一钩子;TransactionInterceptor 利用这个钩子把声明式事务织入到业务方法的前后。

★隔离级别

隔离级别的作用?

为了防止当多个事务读写并发的时候出现的脏读、不可重复读、幻读问题的。

隔离级别分为?
  • 读已提交 :事务只会读取已经提交的数据,可避免脏读,但可能引发不可重复度和幻读。
  • 读未提交:事务可以读取未提交的数据,易产生脏读、不可重复度和幻读。
  • 可重复读 :同一事务期间多次重复读取的数据相同。避免脏读和不可重复读,但仍有幻读的问题
  • 串行化:最高隔离级别,完全禁止并发,只允许一个事务执行完毕之后才能执行另一个事务
级别问题 脏读 不可重复读 幻读
读已提交 ×
读未提交
可重复读 × ×
串行化 × × ×

级别问题解读:

  • 脏读:读到别人未提交的数据。
  • 不可重复读:同一行两次内容不同别人已提交更新)。
  • 幻读:同一范围两次行数不同别人已提交增删)。
读未提交(Isolation.READ_UNCOMMITTED)
java 复制代码
@Transactional(isolation = Isolation.READ_UNCOMMITTED)

代码可以通过隔离级别,读取到事务未提交的数据

  • 容易产生脏读 (第一次与最终结果不一样)、不可重复读(多次读取到的数据不一样)
  • 既能提交到脏数据,又导致了不可重复读,幻读。
读已提交(Isolation.READ_COMMITTED)
java 复制代码
@Transactional(isolation = Isolation.READ_COMMITTED)

只能读取到别人已经提交的数据

  • 会导致不可重复读(别人提交的数据)和幻读。
可重复读(Isolation.REPEATABLE_READ)
  • 也叫:快照读
  • Mysql数据库事务,默认级别为:可重复读。
  • Oracle数据库事务,默认级别为:读已提交。
java 复制代码
@Transactional(isolation = Isolation.REPEATABLE_READ)

一个方法内存在多个同SQL方法,依次执行同一个sql方法,数据结果始终一致。(在这个方法执行期间,可以无法读取到其他方法已提交的结果

  • 只要事务不结束,读取到的数据结果从头到尾都是同一个。即使外界将这条数据删除,也是同一个结果。

★传播行为

传播行为定义这个方法该怎么用一个事务,大事务是否会影响我,我要不要事务。

  • 应用场景:一个增删改方法里面,调用另一个增删改方法。(大事务里面套用另一个小事务),事务的嵌套问题,也就需要用传播行为来控制。

好的,这是对 Propagation 枚举(事务传播行为)的中文详细解释:


概述

Propagation 枚举定义了 Spring 事务的传播行为(Propagation Behavior)。它决定了当一个事务方法被另一个事务方法调用时,事务应该如何传播。例如,是加入现有事务,还是挂起现有事务并开启一个新事务。这是 Spring 事务管理中非常核心且强大的一个概念。


传播行为详解
  1. REQUIRED (需要事务)

    • 行为如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    • 说明 : 这是最常用 的默认设置。它保证了方法一定在一个事务中运行。如果多个方法都使用 REQUIRED 并且相互调用,它们会共享同一个物理事务。如果其中任何一个方法发生回滚,整个事务都将回滚。
    • 适用场景: 绝大多数需要进行数据库写操作的场景。
  2. SUPPORTS (支持事务)

    • 行为如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续执行。
    • 说明: 该方法对事务没有强制要求,"有就用,没有就算了"。它依赖于调用方的事务上下文。
    • 注意: 即使以非事务方式运行,它仍然会参与事务同步(如绑定同一数据库连接),这与完全没有事务注解的方法略有不同。
    • 适用场景: 主要用于查询方法。如果调用方有事务,则查询在该事务内执行(保证一致性视图);如果没有,直接执行查询(效率更高)。
  3. MANDATORY (强制要求事务)

    • 行为如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    • 说明 : 该方法强制要求必须在一个已有的事务中被调用。它自己不会发起新事务。
    • 适用场景: 用于必须作为更大事务的一部分来执行的方法,如果被非事务方法误调用,它会立即抛出异常以提醒开发者。
  4. REQUIRES_NEW (需要新事务)

    • 行为无论如何都会创建一个新的事务。如果当前存在事务,则将其挂起。
    • 说明 : 该方法总会启动一个独立的、全新的物理事务。新事务与旧事务完全隔离,互不影响。新事务提交或回滚后,旧事务再恢复执行。
    • 注意事务挂起 操作需要事务管理器的支持(如 JtaTransactionManager 需要服务器提供 TransactionManager)。
    • 适用场景: 需要独立提交、不受外部事务失败影响的逻辑,例如日志记录(即使业务失败,日志仍需记录入库)。
  5. NOT_SUPPORTED (不支持事务)

    • 行为以非事务方式执行。如果当前存在事务,则将其挂起。
    • 说明: 该方法强制不在事务中运行。它会挂起任何现有的事务,等自己非事务执行完毕后,再恢复原事务。
    • 注意: 同样需要事务管理器支持挂起操作。
    • 适用场景: 需要在不干扰调用方事务的情况下,执行某些不需要事务支持的操作(例如调用一个不支持事务的第三方服务)。
  6. NEVER (绝不)

    • 行为以非事务方式执行。如果当前存在事务,则抛出异常。
    • 说明: 该方法不仅自己不以事务方式运行,还坚决不允许调用方在有事务的情况下调用它。
    • 适用场景: 用于明确不应该在任何事务中执行的方法,防止误用。
  7. NESTED (嵌套事务)

    • 行为如果当前存在事务,则在其内部创建一个嵌套事务(保存点)来执行;如果当前没有事务,则其行为与 REQUIRED 一样。
    • 说明 : 这是一个非常特殊的行为。嵌套事务是外部事务的一个子事务,它的提交会随外部事务一起提交。但它的回滚是部分回滚,只会回滚到它自己创建的保存点,而不会导致整个外部事务回滚。外部事务的回滚则会回滚所有嵌套事务。
    • 注意并非所有事务管理器都支持嵌套事务 。主要支持 JDBC 的 DataSourceTransactionManager(通过保存点实现)。JTA 事务管理器需要看具体厂商实现。
    • 适用场景: 适用于一个复杂业务中,某些步骤可以独立失败而不影响整体主流程的场景。例如,处理一个订单列表,其中某单个订单处理失败不应回滚所有已成功处理的订单,但整个批量处理任务本身失败则应全部回滚。

总结与对比
传播行为 当前有事务 当前无事务 特点
REQUIRED (默认) 加入 创建新事务 保证在事务中运行
SUPPORTS 加入 非事务运行 跟随调用方,不强制
MANDATORY 加入 抛出异常 强制必须在事务中
REQUIRES_NEW 挂起并创建新事务 创建新事务 创建独立的新事务
NOT_SUPPORTED 挂起并非事务运行 非事务运行 强制非事务运行
NEVER 抛出异常 非事务运行 禁止在事务中运行
NESTED 嵌套事务 (保存点) 创建新事务 部分提交/回滚

选择正确的传播行为对于构建健壮、符合预期的事务应用程序至关重要。

timeout(同timeoutString):控制事务的超时时间

  • timeout:事务超时,以秒为单位,int类型。
  • timeoutString:string类型
  • 一旦超过约定时间,事务就会回滚。
  • 超时时间是指:从方法开始,到最后一次数据库操作结束的时间。
java 复制代码
    @Transactional(timeout = 3, timeoutString = "3")

readOnly:只读优化

  • 如果整个事务都是读操作,底层会开启一个只读优化。
  • 如果readOnly,为true,则开启只读优化,默认为false。

rollbackFor(rollbackForClassName):指定哪些编译异常需要回滚

java 复制代码
Class<? extends Throwable>[] rollbackFor() default {};
  • 指明哪些异常需要回滚,不是所有异常都一定会引起事务回滚。

异常分类?

  • 运行时异常
  • 编译时异常
java 复制代码
    @Transactional(rollbackFor = {IOException.class}, rollbackForClassName = "java.lang.Exception")
  • 回滚 = 运行时异常 + 指定异常

noRollbackFor(noRollbackForClassName):指明异常不回滚

  • 不回滚 = 编译时异常 + 指定的不回滚异常
  • 正常情况下,所有运行时异常都会进行回滚。
  • 我们使用noRollbackFor,可以使部分运行时异常不回滚。
java 复制代码
    @Transactional(noRollbackFor = {RuntimeException.class}, noRollbackForClassName = {"java.lang.RuntimeException"})
相关推荐
alf_cee4 小时前
通过Idea 阿里插件快速部署java jar包
java·ide·intellij-idea
坚持每天敲代码4 小时前
【教程】IDEA中导入springboot-maven工程
java·maven·intellij-idea
CodeCraft Studio4 小时前
国产化PDF处理控件Spire.PDF教程:如何在 Java 中通过模板生成 PDF
java·python·pdf·spire.pdf·java创建pdf·从html创建pdf
荣光波比4 小时前
Shell 秘典(卷十)—— 服务器资源自动化监控脚本的设计与实现
运维·服务器·自动化·云计算
阿方.9184 小时前
《数据结构全解析:栈(数组实现)》
java·开发语言·数据结构
YC运维4 小时前
Ansible题目全解析与答案
java·算法·ansible
程序员清风4 小时前
贝壳一面:年轻代回收频率太高,如何定位?
java·后端·面试
考虑考虑5 小时前
Java实现字节转bcd编码
java·后端·java ee
Mr.Entropy5 小时前
请求超过Spring线程池的最大线程(处理逻辑)
数据库·sql·spring