disgare 的博客
首页
博客
分类
标签
首页
博客
分类
标签
  • 网络

    • 计算机网络学习笔记
    • 网络安全相关
    • 域名和子网掩码
    • CORS 跨域资源共享
    • DNS、HTTP 与 HTTPS
    • Server-Sent Events (SSE)
    • WebSocket 长连接
  • 计算机基础

    • 操作系统 IO 相关知识
    • 操作系统学习笔记
    • 程序的机器级表示
    • 音频文件基础
    • 正则表达式相关概念
    • ffmpeg 的安装以及实现音频切分功能
    • Hex 和 Base64 编码
    • XML 的使用
  • 数据结构与算法

    • 动态规划算法学习笔记
    • 基于比较的排序算法的最坏情况下的最优下界为什么是O(nlogn)
    • 集合与数据结构学习笔记
    • 面试常见算法总结
    • 算法导论第二部分排序学习笔记
    • 算法导论第一部分学习笔记
  • Java

    • 对象之间的映射与转换
    • 反射学习笔记
    • 泛型相关概念
    • 关于 boolean 类型的坑
    • 如何使用 lambda 表达式实现排序
    • CompletableFuture 相关用法
    • CompletableFuture 源码浅要阅读
    • FutureTask 源码阅读
    • Guava 常用 API
    • Guava 源码阅读:Multimap 相关
    • Jackson 的各种使用
    • Java 的 Excel 相关操作
    • java 的常见性能问题分析以及出现场景
    • java 基础知识
    • JAVA 枚举的基础和原理
    • Java 图片文件上传下载处理
    • Java 序列化
    • Java 异常
    • Java 语法糖
    • Java 中关于字符串处理的常用方法
    • Java 中强、软、弱、虚引用
    • JAVA 注解小结
    • Java Http 访问框架
    • Java Stream 的使用
    • Java8 新特性
    • netty 学习笔记
    • Scanner 的各种用法
    • Servlet 学习笔记
    • String、StringBuffer、StringBuilder 学习笔记
  • JVM

    • 虚拟机执行子系统
    • JVM 自动内存管理
    • Linux 中 JVM 常用工具以及常见问题解决思路
  • Linux

    • crontab 表达式
    • Linux 常见命令
    • Linux 文件系统
  • 中间件

    • 关于定时任务原理
    • 详解 kafka
    • ES 搜索引擎
    • flink 提交流程
    • Grape-RAG
    • Hadoop 基础原理
  • 多线程

    • 多线程基础学习笔记
    • 简单了解并发集合
    • 如何手写单例
    • 深入理解 java 多线程安全
    • 生产者消费者问题
    • 线程池作用、用法以及原理
    • AQS 组件
    • ThreadLocal 原理以及使用
  • 非关系型数据库

    • Redis 集群
    • Redis 数据结构、对象与数据库
    • Redis 学习笔记
  • 关系型数据库

    • B+ 树的插入、删除和数据页分裂机制
    • MySQL 的 binglog、redolog、undolog
    • MySQL 的记录存储结构、存储引擎与 Buffer Pool
    • MySQL 基本的特性
    • MySQL 开发规范
    • MySQL 事务与锁与 MVCC
    • MySQL 数据类型、字符集相关内容
    • MySQL 索引与索引优化
    • PostgreSQL 更新数据时 HOT优化
    • PostgreSQL 相关用法
  • Python

    • Python 基础语法
    • Python 学习
  • Spring 项目

    • Lombok 的常用注解
    • maven 小结
    • MyBatis 框架的使用
    • MyBatis 重要知识点总结
    • MybatisPlus 的使用
    • Spring 框架基础使用
    • Spring 事务相关
      • 编程式事务与声明式事务
      • 事务的隔离级别 isolation
      • 事务传播行为 propagation
      • 事务超时属性 int
      • 指定异常类型 norollbackFor
      • 大事务优化
      • 事务失效情况
      • 事务传播行为原理
    • Spring IOC 的原理及源码
    • Spring AOP 的使用和原理
    • SpringBoot 的原理
    • SpringBoot 基础使用
    • SpringWeb 重要知识点
  • 分布式

    • 初步了解 docker
    • 从 ACID 到 BASE 事务处理的实现
    • 访问远程服务
    • 分布式 id
    • 分布式缓存相关问题
    • 分布式集群理论和分布式事务协议
    • 分布式架构的观测
    • 分布式一致性算法
    • 负载均衡 Load Balancing
    • 关于分布式系统 RPC 中高可用功能的实现
    • 集群间数据同步的目的
    • 三高问题下的系统优化
    • 数据库分库分表
    • 详解 Spring Cloud
    • Dubbo 基础概念
    • Gossip 协议
    • nginx 学习笔记
    • Protobuf 通信协议
    • Zookeeper 基础学习
  • 架构设计

    • 参数校验与异常处理
    • 抽象方法与设计模式
    • 代码整洁之道
    • 权限系统设计
    • 用低内存处理大量数据
    • 设计模式——策略模式
    • 设计模式——过滤器模式在 Spring 中的实践
    • 状态模式
    • 统一结果返回
    • 为什么要打日志?怎么打日志?打什么日志?
    • 运维监控常见指标含义
    • 资深研发进阶
    • DDD 架构学习笔记
    • Java 常用的规则引擎
    • MVC 架构学习笔记
  • AI

    • 如何编写 Prompt
    • Agent 工程架构
    • LLM 相关内容
    • NLP 相关知识
    • vibe coding 最佳实践
    • windows 下 ollama 迁移到 D 盘
  • 开发工具

    • 如何画时序图、流程图、状态流转图
    • excel 关于 =vlookup 的用法
    • git 的学习以及使用
    • IDEA 插件推荐
    • IDEA 常用快捷键以及调试
    • Shell 脚本
    • swagger 的使用
  • 前端

    • 简单了解前端页面开发
    • 伪静态是什么
    • GitHub Pages 部署教程
    • Vercel 部署教程
    • vue-admin-template 简单使用
    • VuePress 博客搭建指南
  • 项目

    • 面试刷题网——技术方案
    • 影视资源聚合站——技术方案
  • 问题记录

    • 定时任务单线程消费 redis 中数据导致消费能力不足
    • 提供可传递的易受攻击的依赖项
    • Liteflow 在 SpringBoot 启动时无法注入组件问题 couldn‘t find chain with the id[THEN(NodeComponent)]
  • 金融

    • 股票分析——关于电力
    • 股票技术面——量价关系
    • 股票技术面——盘口
    • 股票技术面——基础
    • 基础的金融知识
    • 基金与股票
    • 韭菜的自我总结
    • 聊聊价值投资
  • 其他

    • 程序员职场工作需要注意什么
    • 创业全链路SOP:从灵光一现到系统化增长的实战指南
    • 观罗翔讲刑法随笔
    • 价格和价值
    • 立直麻将牌效益理论
    • 梅花易数学习笔记
    • 压力管理
2023-04-03
Spring 项目
目录

Spring 事务相关

聊过了 MySQL 事务相关内容,来看看 spring 事务有关内容吧。我们都知道在 spring 中用 @Transaction 来管理事务,那它里面的属性是什么意思呢,还有其他方式来管理事务吗

@Transaction 注解用于将某个方法定义为原子的,该方法中的操作要么全部成功,要么全部失败。该注解主要使用场景是引入数据库的情况,我们希望操作数据库后,紧接着的一系列操作不出现异常,如果出现异常,将数据库的操作撤销。这种情况下就需要使用到该注解了

# 编程式事务与声明式事务

使用注解就是声明式事务,声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务

除此之外还有编程式事务管理方式,编程式事务允许用户在代码中精确定义事务的边界,通过 TransactionTemplate 或者 TransactionManager 手动管理事务,实际应用中很少使用

调用 TransactionTemplate 的 execute 方法来执行事务,传入一个 TransactionCallbackWithoutResult 对象

@Resource
private TransactionTemplate transactionTemplate;
public void test() {

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    // ....  
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }
            }
        });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

还有一个调用 TransactionManager 的 getTransaction 方法,这里就不写了

那么声明式事务会回滚什么内容呢?是不是所有的放在注解里的内容都会被回滚呢?以下是我们可能遇到的几种情况:

  • 如果 MySQL、PG 等操作在同一个 Spring 事务中,那么在异常发生时,所有未提交的数据库更改会被回滚。值得注意的是,在一个 spring 事务中的数据库操作是在一个连接一个数据库事务中实现的。这也就是说,如果 spring 事务逻辑太长,会导致上的锁范围过大,我们可能会阻滞其他事务的运行
  • Redis 中的操作不会被回滚,可能需要写补偿代码来回滚数据
  • 在分布式系统中,如果涉及多个服务和数据库,Spring的声明式事务管理可能不足以处理跨服务的事务一致性。这时可能需要使用分布式事务解决方案,比如 TCC、2PC 等(当然线上不可能这么使用,从业务方面调用 RPC 失败就会删除已经创建好的数据,比如我们创建了订单,调用下游的支付接口失败了,我们直接删除这笔订单然后给用户返回错误信息就行了)

# 事务的隔离级别 isolation

一共五种隔离级别,使用 isolation(隔离)属性来配置

四种隔离级别与 MySQL 的隔离级别相同,分别是读未执行,读已执行,不可重复读,串行化

另外一种是默认隔离级别,使用后端数据库默认的隔离级别

在使用 @Transaction 注解的时候,该属性不要求一定要填入

# 事务传播行为 propagation

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播

使用 propagation(交易)属性来配置传播行为,一共有七种传播行为

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;
    ......
}
1
2
3
4
5
6
7
8
9
10

Spring 为了方便使用,定义了一个枚举类:Propagation

package org.springframework.transaction.annotation;

import org.springframework.transaction.TransactionDefinition;

public enum Propagation {

    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

    NEVER(TransactionDefinition.PROPAGATION_NEVER),

    NESTED(TransactionDefinition.PROPAGATION_NESTED);

    private final int value;

    Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

七种传播行为的详细解释如下,支持外围事务的情况:

1,PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,创建一个新事务,这个是默认的,并且也使用的最多

举个例子,当两个方法都用这个传播行为,a 调用 b,此时b中的行为加入a,形成同一个事务,此时b中错误出现回滚a也跟这回滚;a如果是一个普通方法,调用事务方法b,此时b生成一个新事务干自己的事情

2,PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行

3,PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常

4,PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,创建一个事务。所谓嵌套事务,就是 aMethod 调用 bMethod 时,如果 bMethod 回滚的话,aMethod 不会回滚。如果 aMethod 回滚的话,bMethod 会回滚

不支持外围事务:

1,PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起

听说过线程挂起听说过事务挂起吗?当事务创建时,就会被绑定到一个线程上。该线程会伴随着事务整个生命周期,直到事务提交、回滚或挂起(临时解绑)。线程和事务的关系是1:1,当线程绑定了一个事务后,其他事务不可以再绑定该线程,反之亦然

了解事务和线程的关系,也很容易理解事务挂起。对事务的配置在 Spring 内部会被封装资源(Resource),线程绑定了事务,自然也绑定了事务相关的资源。挂起事务时,把这些资源取出临时存储,等待执行完成后,把之前临时存储的资源重新绑定到该线程上

这种情形下,事务 a 调用事务 b,a 在 b 执行完后发生异常,b 不会回滚,因为两个是独立的事务;但是如果 b 抛出异常,并且这个异常没有被捕获的话,a 会回滚

2,PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起

3,PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常

# 事务超时属性 int

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间

# 指定异常类型 norollbackFor

在阿里编程规范中建议使用事务注解时一定要填入 rollbackFor 属性,该属性用于指定能够触发事务回滚的异常类型,可以指定多个异常类型,比如常见的 runtime 异常、空指针异常,数组越界异常等等

于此对应的是 norollbackFor 属性,该属性意思是发生了或者抛出了指定的异常不会发生回滚操作

比如添加 @Transactional(rollbackFor = NullPointerException.class) 注解,指定了空指针类型异常,而我们抛出的是数组越界异常,结果是该方法没有发生回滚,数据库中数据更新。如果抛出了空指针异常,该方法会发生回滚,数据库中数据不更新

# 大事务优化

也不是任何时候都需要使用注解定义事务,在下单的时候,如果对下单方法进行无脑加注解,很有可能让这个事务变的很大,导致回滚的难度加大。同时,由于占用的资源很多,死锁的概率也变大了

举个例子,我们有时候可能会 debug 线上代码,如果走到了事务逻辑中,我们会长时间占用数据库资源,可能会给表中上大量的锁,导致其他的事务阻塞

在使用事务的时候,需要尽可能注意以下几点:

  • 将查询方法放到事务外,因为一般情况下这类方法是不需要事务
  • 事务中避免远程调用,理由同上,如果不需要调用拿数据(就算放在事务中也回滚不了,Spring 的声明式事务管理在默认情况下并不支持分布式事务),直接异步处理一下就可以了
  • 事务中避免一次性处理太多数据,如果一次批量更新1000条数据,这样会导致大量数据锁等待,特别在高并发的系统中问题尤为明显

# 事务失效情况

不是只要加了注解事务就一定会回滚的,如果代码写的不是很好,事务失效也是很有可能的,以下是常见的几种情况:

  • 未指定回滚异常。@Transactional 注解默认的回滚异常类型是运行时异常(RuntimeException),如果我们自定义了一个异常直接继承了 Exception,事务 AOP 是监控不到的
  • 当抛出的异常被 try-catch 捕获并且吞掉时,事务也会失效,原因是 AOP 操作需要拿到方法中抛出的异常才会执行回滚逻辑,处理方案可以是在 catch 中将这个异常抛出来
  • 只有目标方法在外部进行调用,目标方法才会由 Spring 生成的代理对象来进行管理。这个是 AOP 相关知识。但是如果你使用 aspectJ 就不用考虑这个问题了。同理,方法被 private 或者 final 修饰导致 AOP 不能生成同级或者子类方法,也会导致事务失效
  • 数据库不支持事务,比如 mysql 的 myisam
  • 如果事务中使用了两个线程,比如 @Async 或者 CompletableFuture 异步,事务回滚只会回滚主逻辑中的内容而不会回滚异步代码中的内容

# 事务传播行为原理

有没有想过 spring 事务是如何实现的?实现事务可能会遇到以下问题:

1,请求 A 需要开启事务,过程为使用线程1,连接到数据库,执行 SQL,未提交。此时请求 B 也需要开启一个事务处理问题,如果此时复用了线程1,请求 B 会看到请求 A 未提交的数据。因为同一个数据库连接通常只能处理一个事务。如果在一个未完成的事务上再次发起新的事务,可能会导致错误或不确定的状态。这是因为数据库连接在处理事务时会被锁定,无法处理其他请求

解决方案是每个线程必须有自己的数据库连接和事务上下文,就是每个线程开一个连接,同时使用连接池在不影响事务的情况下复用连接

连接池可以有效管理数据库连接。通过为每个并发事务分配不同的连接,可以避免事务之间的干扰。在大多数现代数据库驱动和 ORM 中,都支持连接池的功能

那如果一个线程上开了多个事务怎么办,也就是事务的传播行为如何实现?Spring 使用 ThreadLocal 巧妙的解决了这个问题

用户请求执行事务方法时,ThreadLocal 初始化,检查当前线程是否已有事务(从 ThreadLocal 取,ThreadLocalMap 存放数据库为 key,连接为 value 的映射)。根据 @Transactional 配置决定是加入已有事务、开启新事务等等操作,然后再做 ThreadLocal 存储,在业务代码执行完毕后执行 ThreadLocal 清理,释放连接回连接池

ThreadLocal 会保存很多信息,当前活动的连接、当前事务状态、挂起的事务资源等等

// Spring TransactionSynchronizationManager 中的 ThreadLocal
private static final ThreadLocal<Map<Object, Object>> resources = 
    new NamedThreadLocal<>("Transactional resources");

private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = 
    new NamedThreadLocal<>("Transaction synchronizations");

private static final ThreadLocal<String> currentTransactionName = 
    new NamedThreadLocal<>("Current transaction name");

private static final ThreadLocal<Boolean> currentTransactionReadOnly = 
    new NamedThreadLocal<>("Current transaction read-only status");

private static final ThreadLocal<Integer> currentTransactionIsolationLevel = 
    new NamedThreadLocal<>("Current transaction isolation level");

private static final ThreadLocal<Boolean> actualTransactionActive = 
    new NamedThreadLocal<>("Actual transaction active");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#Spring
最后更新: 1/17/2026, 2:51:21 AM
Spring 框架基础使用
Spring IOC 的原理及源码

← Spring 框架基础使用 Spring IOC 的原理及源码→

最近更新
01
vibe coding 最佳实践
02-24
02
立直麻将牌效益理论
02-23
03
伪静态是什么
02-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式