SqlSession的线程安全问题源码分析
🎮 作者主页:点击
🎁 完整专栏和代码:点击
🏡 博客主页:点击
文章目录
- SqlSession 是线程安全的吗?
- 为什么说是线程不安全的?
- 事务管理问题
- 数据库连接的共享问题
- 一级缓存线程安全问题
- 一级缓存占位符EXECUTION_PLACEHOLDER线程安全问题分析
- 如何避免线程安全问题?
- Spring是如何解决这个问题的?
SqlSession 是线程安全的吗?
SqlSession 本身并不是线程安全的。这意味着,不同线程不应当共享同一个 SqlSession 实例。如果在多线程环境下共享 SqlSession,可能会引发并发问题。
MyBatis 官方文档明确指出,SqlSession 是 非线程安全的,并且推荐每个线程都应该拥有独立的 SqlSession 实例。通常做法是为每个请求创建一个 SqlSession,并在操作完成后关闭它。

为什么说是线程不安全的?
事务管理问题
SqlSession 中包含了对事务的管理,事务在数据库连接上下文中是绑定的。如果多个线程同时使用同一个 SqlSession,就有可能在同一个事务中执行不同的操作,造成不可预知的结果。例如:
SqlSession sqlSession = sqlSessionFactory.openSession();
Thread thread1 = new Thread(() -> {sqlSession.update("update User set name = 'zhangsan' where id = 1");sqlSession.commit(); // 提交事务
});
Thread thread2 = new Thread(() -> {sqlSession.delete("delete from User where id = 2");sqlSession.commit(); // 提交事务
});thread1.start();
thread2.start();
在上述例子中,thread1 和 thread2 会同时操作同一个 SqlSession 实例,执行不同的 SQL 操作。如果 SqlSession 是线程安全的,两个线程的事务提交应该不会互相干扰,但实际上,由于事务是由同一个数据库连接维护的,在并发环境下会出现事务不一致、提交顺序错误等问题。因此,SqlSession 必须是每个线程独立的。
数据库连接的共享问题
SqlSession 会持有数据库连接,这些连接是不可共享的。多个线程如果共享同一个 SqlSession,就可能在同一时刻使用同一个数据库连接,这会导致连接池中的连接竞争,进而引发连接池溢出、死锁等问题。
一级缓存线程安全问题
MyBatis 支持缓存机制,包括一级缓存和二级缓存。一级缓存是 SqlSession 局部的缓存,它的生命周期与 SqlSession 一致。二级缓存是跨 SqlSession 的缓存,与 SqlSessionFactory 绑定。虽然二级缓存是线程安全的,但一级缓存的设计并没有考虑到并发情况下的安全性。
假设有两个线程同时使用同一个 SqlSession 查询数据,并且 SqlSession 内部的一级缓存被修改:
SqlSession sqlSession = sqlSessionFactory.openSession();
Thread thread1 = new Thread(() -> {User user1 = sqlSession.selectOne("select * from Users where id = 1");System.out.println(user1);
});
Thread thread2 = new Thread(() -> {User user2 = sqlSession.selectOne("select * from Users where id = 2");System.out.println(user2);
});thread1.start();
thread2.start();
这里,两个线程可能在同一个 SqlSession 中同时操作数据,SqlSession 内部的一级缓存会被并发修改,导致缓存中的数据不一致。一个线程查询缓存的数据可能是另一个线程未提交的内容,从而引发数据错误。
一级缓存占位符EXECUTION_PLACEHOLDER线程安全问题分析
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}// 存入缓存localCache.putObject(key, list);return list;}
这段代码定义了一个泛型方法 queryFromDatabase,其主要功能是从数据库查询数据并利用缓存机制优化查询性能。首先,方法通过 localCache.putObject(key, EXECUTION_PLACEHOLDER) 将查询的 key 和一个占位符存入本地缓存,表示该查询正在执行。接着,它调用 doQuery 方法进行实际的数据库查询操作,并将查询结果存入 list。查询完成后,无论成功与否,都会在 finally 代码块中移除缓存中的占位符。然后,查询结果 list 被存入缓存,以便后续相同的查询可以直接从缓存中获取,避免重复查询。若该查询为存储过程(StatementType.CALLABLE),则输出参数被存入 localOutputParameterCache。最后,方法返回查询结果列表 list。
public enum ExecutionPlaceholder {EXECUTION_PLACEHOLDER
}
【重点分析】为什么在查询数据库前将key插入缓存中,并且值是一个占位符呢?
ExecutionPlaceholder.EXECUTION_PLACEHOLDER 是一个查询标记,这个占位符可以避免在查询缓存时出现“脏读”,当多个线程同时查询同一个 key 的缓存,线程 A 还在数据库查询过程中,线程 B 也开始查询相同的 key,但此时线程 A 还没完成查询,缓存中的数据尚未更新,假设此时是同一个 SqlSession,因为cacheKey 是一模一样的,线程B会去一级缓存中取值,取出的数据就是旧的值。
MyBatis是如何解决这个问题的呢,它在执行数据库查询前,将改变缓存的值为一个“错误的标记值”,这个值是一个枚举类型,假设此时线程B过来,会经过下面的代码
List<E> list;try {queryStack++;// 从一级缓存获取结果list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 若缓存获取不到,从数据库获取list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}
在执行 (List) localCache.getObject(key) ,此时获取到是标记的值EXECUTION_PLACEHOLDER,那么就是出现类型转换异常,Mybatis是直接通过设置一个异常标记值,直接抛出异常的方式避免这种多线程同一个SqlSession问题。SqlSession不是线程安全,所以尽量不要多个线程混用一个SqlSession,应该是一个线程一个SqlSession,每个线程独立的connection。
如何避免线程安全问题?
每个线程使用独立的 SqlSession:在多线程环境下,每个线程应该创建一个独立的 SqlSession 实例,避免共享实例。
请求范围内管理 SqlSession:对于 Web 应用程序,通常在每个请求中创建并使用 SqlSession,请求结束后关闭 SqlSession。
使用 ThreadLocal:如果需要在多个方法或类中共享 SqlSession,可以使用 ThreadLocal 来确保每个线程都有自己的 SqlSession 实例。
ThreadLocal<SqlSession> threadLocalSession = new ThreadLocal<SqlSession>() {@Overrideprotected SqlSession initialValue() {return sqlSessionFactory.openSession();}
};
Spring是如何解决这个问题的?
在 Spring 中,SqlSession 是通过 Spring 提供的事务管理和依赖注入机制来管理的。Spring 通过一系列的技术(如 @Transactional 注解、@Autowired 注解、TransactionManager 等)来避免 SqlSession 的线程安全问题,确保每个线程(通常是每个请求)都能使用一个独立的 SqlSession 实例。
Spring 会为每个请求创建独立的 SqlSession,并在请求结束时自动关闭,从而避免了线程共享 SqlSession 实例的问题。
Spring 内部使用 ThreadLocal 来为每个线程提供独立的 SqlSession。ThreadLocal 是一种线程局部存储机制,它可以确保每个线程都有自己的 SqlSession 实例。
具体来说,TransactionSynchronizationManager 类通过 ThreadLocal 维护了与当前事务相关的资源(如 SqlSession)。当一个请求(或一个线程)执行时,Spring 会将该请求的 SqlSession 实例绑定到当前线程的 ThreadLocal 中,这样其他线程就无法访问同一个 SqlSession 实例,从而避免了线程安全问题。
在 Spring 中,事务的生命周期通常由 PlatformTransactionManager 管理。当事务开始时,Spring 会在当前线程上通过 TransactionSynchronizationManager 来保存当前事务的信息。这个信息包括了当前事务管理器以及任何与事务相关的资源(如 SqlSession)。
private SqlSession getSqlSession() {// 根据当前线程的事务上下文来获取 SqlSession 实例SqlSession session = (SqlSession) TransactionSynchronizationManager.getResource(sqlSessionFactory);if (session == null) {session = sqlSessionFactory.openSession();// 将 SqlSession 绑定到当前线程TransactionSynchronizationManager.bindResource(sqlSessionFactory, session);}return session;}
当 Spring 开始一个新的事务时,SqlSession 会被绑定到当前线程的 ThreadLocal 上。这个绑定操作使得每个线程都有自己的独立 SqlSession。
当线程请求 SqlSession 时,Spring 会首先从当前线程的 ThreadLocal 中获取已经绑定的 SqlSession,如果没有绑定的 SqlSession,则会通过 SqlSessionFactory 创建新的 SqlSession。
相关文章:
SqlSession的线程安全问题源码分析
🎮 作者主页:点击 🎁 完整专栏和代码:点击 🏡 博客主页:点击 文章目录 SqlSession 是线程安全的吗?为什么说是线程不安全的?事务管理问题 数据库连接的共享问题 一级缓存线程安全问题…...
Java 8 及经典面试题全解析
Java 是目前非常流行的编程语言之一,其强大的生态系统和丰富的功能使得它在企业级开发中占据重要地位。在面试中,Java 的基础知识、集合框架、多线程、JVM,以及 Java 8 的新特性是重点考查内容。本文将结合 Java 8 和经典知识点,为…...
MySQL:安装配置(完整教程)
这里写目录标题 一、MySQL 简介二、下载 MySQL三、安装 MySQL四、配置环境变量五、配置 MySQL5.1 初始化 MySQL5.2 启动 MySQL 服务 六、修改 MySQL 密码七、卸载 MySQL八、结语 一、MySQL 简介 MySQL 是一款广泛使用的开源关系型数据库管理系统(RDBMS)…...
Java - 日志体系_Apache Commons Logging(JCL)日志接口库_桥接Logback 及 源码分析
文章目录 PreApache CommonsApache Commons ProperLogging (Apache Commons Logging ) JCL 集成logbackPOM依赖配置文件 logback.xml使用 源码分析jcl-over-slf4j 的工作原理1. LogFactory 的实现2. SLF4JLogFactory 和 Log 的实例化过程3. SLF4JLog 和 …...
高性能网络框架--fstack
【欢迎关注编码小哥,学习更多实用的编程方法和技巧】 Fstack 是一个高性能的网络框架,主要用于构建高性能的网络应用程序,特别是在处理大量并发连接时。它基于 Linux 的 epoll 机制,使用了多线程和事件驱动的编程模型。以下是对 …...
Unity Mesh生成Cube
1. 配置一个Cube的每个面的数据 一共是6个面,每个面包含的数据包括4个顶点的相对顶点坐标(Cube的中心为原点),法线方向,UV坐标,顶点渲染顺序,以及这个面用到的材质,因为这里是Top&am…...
2、pycharm常用快捷命令和配置【持续更新中】
1、常用快捷命令 Ctrl / 行注释/取消行注释 Ctrl Alt L 代码格式化 Ctrl Alt I 自动缩进 Tab / Shift Tab 缩进、不缩进当前行 Ctrl N 跳转到类 Ctrl 鼠标点击方法 可以跳转到方法所在的类 2、使用pip命令安装request库 命令:pip install requests 安装好了…...
Go语言方法和接收器类型详解
Go语言方法和接收器类型详解 1. 方法接收器类型 1.1 值接收器 值接收器方法不会改变接收器的状态,因为Go语言会在调用时复制接收器的值。因此,任何对接收器成员变量的修改都只会影响副本,而不会影响原始结构体实例。 type Person struct …...
Flutter:打包apk,详细图文介绍(一)
困扰了一天,终于能正常打包apk安装了,记录下打包的流程。建议参考我这篇文章时,同时看下官网的构建说明。 官网构建并发布 Android 应用详情 1、AS创建Flutter项目 2、cmd执行命令 生成一个sunluyi.jks的文件,可以自行把sunluyi替…...
Vue.js组件开发-实现动态切换菜单简单示例
在Vue.js中,实现动态切换菜单通过组件化开发和Vue的响应式数据绑定来实现。 示例: 展示如何创建一个可以动态切换菜单的Vue组件。 首先,需要定义一个Vue组件,该组件将包含菜单项和用于切换菜单的状态。 1. 创建Vue组件 <t…...
如何在 Ubuntu 22.04 上优化 Apache 以应对高流量网站教程
简介 在本教程中,我们将学习如何优化 Apache 以应对高流量网站。 当运行高流量网站时,确保你的 Apache Web 服务器得到优化对于有效处理负载至关重要。在本指南中,我们将介绍配置 Apache 以提高性能和可扩展性的基本技巧。 为高流量网站优…...
17爬虫:关于DrissionPage相关内容的学习01
概述 前面我们已经大致了解了selenium的用法,DerssionPage同selenium一样,也是一个基于Python的网页自动化工具。 DrissionPage既可以实现网页的自动化操作,也能够实现收发数据包,也可以把两者的功能合二为一。 DressionPage的…...
【HarmonyOS之旅】HarmonyOS概述(一)
目录 1 -> HarmonyOS简介 2 -> HarmonyOS发展历程 3 -> HarmonyOS技术特性 3.1 -> 硬件互助,资源共享 3.1.1 -> 分布式软总线 3.1.2 -> 分布式设备虚拟化 3.1.3 -> 分布式数据管理 3.1.4 -> 分布式任务调度 3.1.5 -> 分布式连接…...
chatwoot 开源客服系统搭建
1. 准备开源客服系统(我是用的Chatwoot ) 可以选择以下开源客服系统作为基础: Chatwoot: 开源,多语言,跟踪和分析,支持多渠道客户对接,自动化和工作流等。源码Zammad: 现代的开源工单系统。Fr…...
30分钟搭建 Typecho 个人博客教程
Typecho是一款PHP博客程序,相比于WordPress,Typecho显得更加的轻量级和简洁。现在越来越多的人倾向于用Typecho来搭建个人博客——众所周知,能跑WordPress的机器都不便宜。 Typecho是一款国人团结打造的开源博客系统,和WordPress…...
智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之7 附件(文档)
为AI聊天工具添加一个知识系统 Part1 人性化&去中心化 前情提要 这一次我们暂时抛开前面对“智能工厂的软件设计”的考虑--其软件智能 产品就是 应用程序。直接将这些思维方式和方法论 运用在其具体应用场景中。本文是其中的一个应用场景。 今天用了 一个新的AI助手工具…...
鸿蒙应用开发启航计划
以前有过简单的学习了解,但是现在工作内容的原因,要专门搞这个,因此需要更加熟练地掌握鸿蒙应用开发。 1.开发IDE -- DevEco Studio Windows环境 运行环境要求 为保证DevEco Studio正常运行,建议电脑配置满足如下要求ÿ…...
基本算法——回归
目录 创建工程 加载数据 分析属性 创建与评估回归模型 线性回归 回归树 评估 完整代码 结论 本节将通过分析能源效率数据集(Tsanas和Xifara,2012)学习基本的回归算法。我们将基 于建筑的结构特点(比如表面、墙体与屋顶面…...
深度学习——神经网络中前向传播、反向传播与梯度计算原理
一、前向传播 1.1 概念 神经网络的前向传播(Forward Propagation)就像是一个数据处理的流水线。从输入层开始,按照网络的层次结构,每一层的神经元接收上一层神经元的输出作为自己的输入,经过线性变换(加权…...
解决git push报错:not valid: is this a git repository?
今天想把代码更新到仓库里,执行git push origin master:main的时候报错:not valid: is this a git repository? 查了好多方法都没用。后来经过这篇文章的启发:https://zhuanlan.zhihu.com/p/301518109 可能是由于校园网的问题,…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
