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方法的执行)。下面是获取当前线程同一数据源连接的缓存,由于不是本文重点,具体如何设置进去的就不展开了。

不好意思,以上结论是错误的!!!!设置事务手动提交: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。

 @Override
  public 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接口底层已经为我们实现了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/590637.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024五一数学建模C题完整论文讲解(含完整python代码及几十个特征表、处理表、结果表)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024五一数学建模C题煤矿深部开采冲击地压危险预测完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 C题论文…

【Docker学习】docker version查看版本信息

就像很多应用一样&#xff0c;docker也使用version来查看版本信息。但因为docker包含有不少独立组件&#xff0c;version的作用范围会更广一些。 用法1&#xff1a; docker --version 描述&#xff1a; 输出安装的Docker CLI 的版本号。关于Docker CLI&#xff0c;请访问。 实操…

数字电路-5路呼叫显示电路和8路抢答器电路

本内容涉及两个电路&#xff0c;分别为5路呼叫显示电路和8路抢答器电路&#xff0c;包含Multisim仿真原文件&#xff0c;为掌握FPGA做个铺垫。紫色文字是超链接&#xff0c;点击自动跳转至相关博文。持续更新&#xff0c;原创不易&#xff01; 目录&#xff1a; 一、5路呼叫显…

如何免费体验 gpt2-chatbot

如何免费体验 gpt2-chatbot 就在五一假期期间&#xff0c;一个神秘模型在没有任何官方文件的情况下突然发布。发布后不到 12 小时就立即引起人工智能爱好者和专家们的关注。这个名为“gpt2-chatbot”的神秘新模型凭借其令人印象深刻的能力轰动全球。有人猜测它可能是 OpenAI 的…

Python爬取豆瓣电影Top250数据

任务 爬取豆瓣电影top250中的影片名称、影片海报、年份、地区、类型、评分、评价人数、总体评价&#xff0c;并输出到douban_top250.xlsx文件中 环境 Python 3.8 requests bs4 openpyxl 源码 # 创建一个新的Excel工作簿 workbook openpyxl.Workbook() # 获取默认的工作表…

新版security demo(二)前端

写这篇博客&#xff0c;刚好换了台电脑&#xff0c;那就借着这个demo复习下VUE环境的搭建。 一、前端项目搭建 1、安装node 官网下载安装即可。 2、安装脚手架 npm install -g vue-cli 使用脚手架搭建一个demo前端项目 vue init webpack 项目名称 3、安装依赖 这里安装…

OpenCV(三)—— 车牌筛选

本篇文章要介绍如何对从候选车牌中选出最终进行字符识别的车牌。 无论是通过 Sobel 还是 HSV 计算出的候选车牌都可能不止一个&#xff0c;需要对它们进行评分&#xff0c;选出最终要进行识别的车牌。这个过程中会用到两个理论知识&#xff1a;支持向量机和 HOG 特征。 1、支…

vivado Aurora 8B/10B IP核(9)- CRC、 Aurora 8B/10B内核的时钟接口端口

CRC 模块提供 16 位或 32 位 CRC&#xff0c;用于用户数据。 Aurora 8B/10B 内核的时钟接口端口 从相邻收发器四边形的时钟Xilinx 实现工具可以根据需要对南北路由和引脚交换到收发器时钟输入进行必要的调整&#xff0c;以将时钟从一个四线到另一个。 重要信息&#xff1a;共…

25计算机考研院校数据分析 | 哈尔滨工业大学

哈尔滨工业大学&#xff08;Harbin Institute of Technology&#xff09;&#xff0c;简称哈工大&#xff0c; 校本部位于黑龙江省哈尔滨市&#xff0c;是由工业和信息化部直属的全国重点大学&#xff0c;位列国家“双一流”、“985工程”、“211工程”&#xff0c;九校联盟 、…

【Java EE】Mybatis之XML详解

文章目录 &#x1f38d;配置数据库连接和MyBatis&#x1f340;写持久层代码&#x1f338;添加mapper接口&#x1f338;添加UserInfoXMLMapper.xml&#x1f338;单元测试 &#x1f332;CRUD&#x1f338;增(Insert)&#x1f338;删(Delete)&#x1f338;改(Update)&#x1f338;…

【MIT6.S081】Lab6: Copy-on-Write Fork for xv6(详细解答版)

实验内容网址&#xff1a;https://xv6.dgs.zone/labs/requirements/lab6.html 本实验的代码分支&#xff1a;https://gitee.com/dragonlalala/xv6-labs-2020/tree// Implement copy-on write 关键点: 内存引用计数、usertrap()、页表 思路: Copy on write 是为了优化在fork()时…

Benewake(北醒) 短距 TF-Luna 8m

北醒TF-Luna激光雷达在测距方面具有以下特点: 高精度测距:北醒TF-Luna激光雷达采用激光束对目标进行扫描和测量,可以获取目标的高精度位置信息。其工作原理保证了测距的稳定性和准确性。 小视场角:该雷达具有较小的视场角(FOV),这意味着它可以更精确地锁定和测量特定区域…

Go协程的底层原理(图文详解)

为什么要有协程 什么是进程 操作系统“程序”的最小单位进程用来占用内存空间进程相当于厂房&#xff0c;占用工厂空间 什么是线程 进程如果比作厂房&#xff0c;线程就是厂房里面的生产线&#xff1a; 每个进程可以有多个线程线程使用系统分配给进程的内存&#xff0c;线…

深入理解网络原理1

文章目录 前言一、网络初识1.1 IP地址1.2 端口号1.3 协议1.4 五元组1.5 协议分层 二、TCP/IP五层协议三、封装和分用四、客户端vs服务端4.1 交互模式4.2 常见的客户端服务端模型4.3 TCP和UDP差别 前言 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享…

网络安全审计

一、什么叫网络安全审计 网络安全审计是按照一定的安全策略&#xff0c;利用记录、系统活动和用户活动等信息&#xff0c;检查、审查和检验操作时间的环境及活动&#xff0c;从而发现系统漏洞、入侵行为或改善系统性能的过程&#xff0c;它是提高系统安全性的重要手段。 系统…

linux下载压缩包

比如我要下载的压缩包地址为&#xff1a; http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/cocostuff-10k-v1.1.zip 1.创建文件夹并切换到这个目录下 2.用wget获取压缩包 压缩包下好了 3.解压 如果是 tar.gz包解压 tar -zxvf 也可以解压到具体的目录…

Java的逻辑控制和方法的使用介绍

前言 程序的逻辑结构一共有三种&#xff1a;顺序结构、分支结构和循环结构。顺序结构就是按代码的顺序来执行相应的指令。这里主要讲述Java的分支结构和循环结构&#xff0c;由于和C语言是有相似性的&#xff0c;所以这里只会提及不同点和注意要点~~ 注意在C语言中&#xff0c;…

Docker部署nginx并启用https加密连接

前言 在当今互联网时代&#xff0c;安全性成为越来越重要的议题。随着网络攻击日益猖獗&#xff0c;保护数据和通信的安全性变得至关重要。在这种背景下&#xff0c;对于在 Docker 中运行 Nginx 是否需要使用 HTTPS 这一问题&#xff0c;我们需要考虑到网络安全的重要性以及 H…

如何低成本创建个人网站?

目录 前言 网站源代码 虚拟主机或服务器 域名注册或免费二级域名 域名解析 上传源代码压缩包 添加刚刚的域名 成功搭建 失败的解决方案 结语 前言 很多小白都非常想拥有自己的网站&#xff0c;但很多人虽然有了自己的源代码但苦于不知道怎么将其变成所有人都能够访…

YOLO自研模块:多尺度轻量化卷积模块

目录 一、原理 二、代码 三、配置文件 一、原理 不同大小的卷积核,提取目标特征的特征尺度不同,所以通过使用不同大小卷积核的卷积来提取特征就可以保证获取到目标的多尺度特征。 借鉴YOLOv8中,将通道数进行划分的操作,在卷积的输入过程中为了减小参数量,将输入通道数…