Mybatis之Sqlsession、Connection和Transaction三者间的关系
前言
最近在看Mybatis的源码,搜到这篇文章Sqlsession、Connection和Transaction原理与三者间的关系,debug之后发现有不少疑惑,于是按照原文整理了一下,记录下debug中的一些困惑点。
对于我们开发来讲,不管跟任何关系型数据库打交道都无法规避这三巨头,数据库的会话-Sqlsession、连接-Connection和事务-Transaction,今天让我们一起来梳理下这三者之间的工作原理和关系。
1、首先来解析下会话Sqlsession
会话是Mybatis持久化层跟关系型数据库交互的基础,所有的查询、数据更新(包含保存、更新、删除)操作都在与数据库建立会话的基础上进行的;MyBatis中的会话是SqlSession,默认实现是DefaultSqlSession。可以通过SqlSessionFactory的openSession来获取的。
通过SqlSessionFactory获取SqlSession的代码如下:
String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();
打开一个会话的时序图大致流程如下:
第一步:通过new SqlSessionFactoryBuilder().build(inputStream)来构造SqlSessionFactory,参数是配置文件的输入流。
主要实现的代码块如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
第二步:XMLConfigBuilder的parse方法会解析配置文件,解析的结果就是得出一个Configuration对象。其中一步就是根据配置文件中的datasource节点解析出数据源
主要实现的代码块如下:
<dataSource type="POOLED"><!--这里会替换为local-mysql.properties中的对应字段的值--><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true"/><property name="username" value="root"/><property name="password" value="12345678"/><property name="poolMaximumActiveConnections" value="2"/><property name="poolMaximumIdleConnections" value="2"/>
</dataSource>
第三步:SqlSessionFactory的openSession会获取SqlSession。具体实现代码如下:
Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
2、接下来再分析下Mybatis的连接Connection
MyBatis在每次执行SQL操作时,在获取Statement时,会去获取数据库连接(下面源码在SimpleExecutor)。因为每个sql的Statement不同,所以连接也不同,也就是一个Sqlsession对应多个Connection。只有在开启spring事务的的前提下,当前线程才是共用一个Connection,这是因为spring在获取连接时,使用ThreadLocal在DataSource层面对Connection做了缓存(即你连接的数据源没变,Connection不变,比如没改变查询的数据库)。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}
事务没有交spring管理
// 这里是单独运行mybatis,没和spring整合
public class MybatisHelloWorld {public static void main(String[] args) {String resource = "mybatis-config.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {TestAMapper mapper = session.getMapper(TestAMapper.class);TestA testA = mapper.getUser(1);mapper.getUser(2);System.out.println(testA);} finally {session.close();}} catch (IOException e) {e.printStackTrace();}}
}
查看mapper.getUser(1)和mapper.getUser(2)时,Connection获取如下图:


显然这两个Connection并不是同一个。
事务交spring管理(配置的druid数据源)
// 这里重点不是mybatis和spring的整合,相关代码省略
// 自己可以在springboot中快速导入,然后写单测走以下代码
// 也不关注回滚和提交,只关注开启事务管理后的连接获取情况即可
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
mapper.getUser(1);
mapper.getUser(2);
查看mapper.getUser(1)和mapper.getUser(2)时,Connection获取如下图:


显然这两个是同一个(这不是复制了一遍图啊,看handler地址就知道是两次Mapper方法的执行)。下面是获取当前线程同一数据源连接的缓存,由于不是本文重点,具体如何设置进去的就不展开了。

类似的原理在SqlSessionTemplate(功能之一就是帮我们管理SqlSession的新建和关闭,我们单独运行Mybatis是自己手动操作的,整合Spring后,直接调Mapper就行了,不用管SqlSession)也出现了,这里缓存的是当前线程的SqlSession(如果当前线程的每次执行Mapper的SqlSession不一样,那么底层Connection也会不一样,事务就没法实现了):

不好意思,以上结论是错误的!!!!设置事务手动提交:SqlSession session = sqlMapper.openSession(false);假设把mapper.getUser(1)和mapper.getUser(2)换成两个更新语句,调用session.commit()会发现提交成功。问题来了,两个更新是两个不同的connection,session.commit()底层也是调connection.commit(),一个connection不可能提交两个connection的事务啊,那么一个SqlSession就是对应一个Connection!
那么上面的debug截图明明显示不同啊,为啥呢?注意看prepareStatement方法这里生成的Connetion是带Proxy的,说明这个地方的虽然每次创建不一样的Connetion,但只是代理对象,里面其实还是一个Connetion(下图中的realConnection),如下图:



知道为啥叫realConnection了吧,这里涉及了JDK动态代理。因为代理对象的存在,导致在debug过程中,发现不同Mapper方法创建的Connection和JdbcTransaction的Connection全都不一样,如果基础比较劳,知道Connection只能提交基于它自己生成的statement执行的sql的事务,那么就不会有这个困惑了。


回到主线,我们配置的数据源为POOLED,这里会使用PooledDataSource来获取Connection。
@Overridepublic Connection getConnection() throws SQLException {return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();}
这里进行了数据库的连接进行了池化管理,MyBatis通过自身的数据源PooledDataSource来进行数据库连接的管理。
3、然后再来说下事务Transaction
在执行sqlSession.commit时,会去提交事务。
UserMapperExt userMapperExt = sqlSession.getMapper(UserMapperExt.class);userMapperExt.insert(new UserDTO("houliu",23));userMapperExt.findUserListByName("zhangsan");userMapperExt.update("name" ,"wangwu",22);sqlSession.commit();
执行commit后,会调用如下代码:
一个sqlSession中可以进行多个事务提交:
userMapperExt1.insert(new UserDTO("houliu",23));userMapperExt1.findUserListByName("zhangsan");userMapperExt1.update("name" ,"wangwu",22);sqlSession1.commit();//SqlSession sqlSession2 = sqlSessionFactory.openSession();UserMapperExt userMapperExt2 = sqlSession1.getMapper(UserMapperExt.class);userMapperExt2.insert(new UserDTO("houliu",23));userMapperExt2.findUserListByName("zhangsan");userMapperExt2.update("name" ,"wangwu",22);sqlSession1.commit();
原生jdbc中一个connection可以执行多次commit:
Class.forName(“com.mysql.cj.jdbc.Driver”); //classLoader,加载对应驱动
Connection connection = DriverManager.getConnection(“jdbc:mysql://127.0.0.1:3306/test?useUnicode=true”, “root”, “12345678”);
connection.setAutoCommit(false);
PreparedStatement preparedStatement = connection.prepareStatement(“update cnt_user set age = 201 where name = ‘zhangsan’”);
preparedStatement.execute();connection.commit();preparedStatement = connection.prepareStatement("update cnt_user set age = 233 where name = 'zhangsan'");
preparedStatement.execute();
preparedStatement = connection.prepareStatement("insert into cnt_user (age , name) values(100 ,'liusi')");
preparedStatement.execute();connection.commit();
可以看出,事务是依附在SqlSession上的。好了,讲完了上面三者的原理后,最后我们来总结下三者的关系。
Sqlsession、Connection和Transaction之间关系
连接可以通过数据库连接池被复用。在MyBatis中,不同时刻的SqlSession可以复用同一个Connection,同一个SqlSession中可以提交多个事务。事务回滚与提交其实都是调Connection的相关方法,即同一个事务内,sql操作使用同一个数据库连接。因此,连接—会话—事务的关系如下:

- Connection与SqlSession一 一对应(Connection一对多SqlSession是SqlSession关闭了情况下又新建一个SqlSession,即上面说的所谓不同时刻,因为连接池的存在,不可能多个SqlSession同时对应同一个连接,所以只能算一 一对应)。
- Connection与SqlSession两者与Transaction是一对多。
从经典的JDBC操作数据库的几个步骤出发再看Mybatis的源码,就能明白,SqlSession是对Connection的高级封装,它作为Mybatis的接口层,提供了一系列增删改查接口,我们通过它可以直接操作数据库,而原生的Connection,我们还得创建PreparedStatement,预处理sql,处理返回结果等等,而这些操作,SqlSession接口底层已经为我们实现了。
相关文章:
Mybatis之Sqlsession、Connection和Transaction三者间的关系
前言 最近在看Mybatis的源码,搜到这篇文章Sqlsession、Connection和Transaction原理与三者间的关系,debug之后发现有不少疑惑,于是按照原文整理了一下,记录下debug中的一些困惑点。 对于我们开发来讲,不管跟任何关系…...
WRT1900ACS搭建openwrt服务器小记
参考链接 wrt1900acs openwrt wrt1900acs openwrt 刷机 wrt1900acs原生固件刷openwrt-23.05.3-mvebu-cortexa9-linksys_wrt1900acs-squashfs-factory.img wrt1900acs openwrt更新刷openwrt-23.05.3-mvebu-cortexa9-linksys_wrt1900acs-squashfs-sysupgrade.bin 通过WEB UI来…...
Spring AOP(3)
目录 Spring AOP原理 代理模式 代理模式中的主要角色 静态代理 动态代理 总结:面试题 什么是AOP? Spring AOP实现的方式有哪些? Spring AOP实现原理 Spring使用的是哪种代理方式? JDK和CGLIB动态代理的区别? Spring AOP原理 代理模式 代理模式, 也叫委托模式. …...
推荐5个免费的国内平替版GPT
提起AI,大家第一个想到的就是GPT。 虽然它确实很厉害,但奈何于我们水土不服,使用门槛有些高。 不过随着GPT的爆火,现在AI智能工具已经遍布到各行各业了,随着时间的推移,国内的AI工具也已经“百花盛放”了…...
弹性云服务器是什么,为何如此受欢迎
云计算作为当下炙手可热的技术领域,已然成为现代企业不可或缺的核心能力。云服务器作为云计算的基石之一,在这个数字化时代发挥着至关重要的作用。而弹性云服务器,作为云服务器的一种演进形式,更是备受瞩目。 弹性云服务器&#…...
Docker部署RabbitMQ与简单使用
官网地址: Messaging that just works — RabbitMQ 我的Docker博客:Docker-CSDN博客 1.结构 其中包含几个概念: **publisher**:生产者,也就是发送消息的一方 **consumer**:消费者,也就是消费消息的一方 …...
2024年黄石市建设优质工程评价认定申报条件、流程及材料合集
2024年黄石市建设优质工程评价认定申报条件、流程及材料合集如下,黄石市的企业单位可以了解一下,有疑问名字找我哦。 第一章总则 第一条为贯彻落实《中华人民共和国建筑法》、《安全生产法》、《建设工程质量管理条例》、《建设工程安全生产管理条例》…...
偏微分方程算法之混合边界条件下的差分法
目录 一、研究目标 二、理论推导 三、算例实现 四、结论 一、研究目标 我们在前几节中介绍了Poisson方程的边值问题,接下来对椭圆型偏微分方程的混合边值问题进行探讨,研究对象为: 其中,为矩形区域,为上的连续函数…...
apollo资料整理
Application X: Application X Apollo: Apollo 自动驾驶开放平台 Cyber RT API tutorial — Cyber RT Documents documentation Cyber RT API tutorial — Cyber RT Documents documentation GitHub - daohu527/dig-into-apollo: Apollo notes (Apollo学习笔记) - Apollo l…...
森林消防新利器:高扬程水泵的革新与应用/恒峰智慧科技
随着全球气候变化的加剧,森林火灾的频发已成为威胁生态安全的重要问题。在森林消防工作中,高效、快速的水源供给设备显得尤为重要。近年来,高扬程水泵的广泛应用,为森林消防工作带来了新的希望与突破。 一、高扬程水泵的技术优势 …...
Microsoft Universal Print 与 SAP 集成教程
引言 从 SAP 环境打印是许多客户的要求。例如数据列表打印、批量打印或标签打印。此类生产和批量打印方案通常使用专用硬件、驱动程序和打印解决方案来解决。 Microsoft Universal Print 是一种基于云的打印解决方案,它允许组织以集中化的方式管理打印机和打印机驱…...
VBA在Excel中字母、数字的相互转化
VBA在Excel中字母、数字的相互转化 字母转数字的方法 数字转字母的方法 众所周知,Excel表中的行以数字展示,列用字母展示,如下图: 编程时,很多时候需要将列的字母转变为数字使用,如cells(num1,num2).value等,不知大家是怎么将字母转化为数字的,Excel是否有其他方式…...
【C语言】——联合体与枚举
【C语言】——联合体与枚举 一、联合体1.1、联合体类型的声明1.2、联合体的特点1.3、相同成员的结构体和联合体对比1.4、联合体的大小计算1.5、联合体的应用举例 二、枚举2.1、枚举类型的声明2.2、枚举类型的优点 一、联合体 1.1、联合体类型的声明 联合体也叫做共用体 与…...
java线上问题排查之内存分析(三)
java线上问题排查之内存分析 使用top命令 top命令显示的结果列表中,会看到%MEM这一列,这里可以看到你的进程可能对内存的使用率特别高。以查看正在运行的进程和系统负载信息,包括cpu负载、内存使用、各个进程所占系统资源等。 2.用jstat命令…...
中电金信:金Gien乐道 | 4月要闻速览,精彩再回顾
中国电子党组副书记、总经理李立功一行调研中电金信 4月10日,中国电子党组副书记、总经理李立功一行赴中电金信进行调研,深入听取了中电金信经营发展情况、研发工作及“源启”行业数字底座平台的汇报,并参观了公司展厅和科技研发场所…...
Java将文件目录转成树结构
在实际开发中经常会遇到返回树形结构的场景,特别是在处理文件系统或者是文件管理系统中。下面就介绍一下怎么将文件路径转成需要的树形结构。 在Java中,将List<String>转换成树状结构,需要定义一个树节点类(TreeNode&#…...
硬件工程师必读:10条职业发展黄金法则!
在快速发展的科技时代,硬件工程师作为推动技术创新和产业升级的重要力量,其职业发展之路既充满挑战也蕴含无限机遇。为了在这条道路上稳步前行,我们首先需要了解硬件产品的研发流程。 在这个过程中,公司内的每个岗位都发挥着不可或…...
Redis是什么? 日常运维 Redis 需要注意什么 ? 怎么降低Redis 内存使用 节省内存?
你的项目或许已经使用 Redis 很长时间了,但在使用过程中,你可能还会或多或少地遇到以下问题: 我的 Redis 内存为什么增长这么快?为什么我的 Redis 操作延迟变大了?如何降低 Redis 故障发生的频率?日常运维…...
【Android项目】“追茶到底”项目介绍
没有多的介绍,这里只是展示我的项目效果,后面会给出具体的代码实现。 一、用户模块 1、注册(第一次登陆的话需要先注册账号) 2、登陆(具有记住最近登录用户功能) 二、点单模块 1、展示饮品列表 2、双向联动…...
机试:进制转换问题
十进制转任意进制 简单回忆一下十进制我们是怎么转换成二进制的(短除法): 我们会将十进制数不断的进行除2操作,并且记录下每一次的余数(这个余数就是我们最终求的二进制数的组成部分)。 以下以12D举例&a…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...
比较数据迁移后MySQL数据库和ClickHouse数据仓库中的表
设计一个MySQL数据库和Clickhouse数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
