Mybatis之Sqlsession、Connection和Transaction三者间的关系

news2024/11/27 11:58:18

前言

最近在看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接口底层已经为我们实现了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ARP防火墙能够为网络安全贡献什么样的力量

ARP防火墙&#xff08;Address Resolution Protocol Firewall&#xff09;作为网络安全的一环&#xff0c;起到保护网络免受ARP欺骗攻击的关键作用。今天德迅云安全给您介绍ARP防火墙的相关方面&#xff0c;帮助您深入了解和认识这一关键的安全措施。 网络安全对于现代社会的信…

Python量化择时的技术指标函数

Python量化择时的技术指标函数 技术指标通过对原始数据&#xff08;开盘价、收盘价、最低价、最高价、成交量、成交金额、成交笔数&#xff09;的处理&#xff0c;来反映出市场的某一方面深层的内涵&#xff0c;这些内涵是很难通过原始数据直接看出来的。技术指标能客观地反映…

力扣763. 划分字母区间

Problem: 763. 划分字母区间 文章目录 题目描述思路复杂度Code 题目描述 思路 1.创建一个名为 last 的数组&#xff0c;用于存储每个字母在字符串 s 中最后出现的位置。然后&#xff0c;获取字符串 s 的长度 len。 2.计算每个字母的最后位置&#xff1a;遍历字符串 s&#xff0…

springboot整合mybatis配置多数据源(mysql/oracle)

目录 前言导入依赖坐标创建mysql/oracle数据源配置类MySQLDataSourceConfigOracleDataSourceConfig application.yml配置文件配置mysql/oracle数据源编写Mapper接口编写Book实体类编写测试类 前言 springboot整合mybatis配置多数据源&#xff0c;可以都是mysql数据源&#xff…

R语言数据探索与分析-运用时间序列预测模型对成都市API进行预测分析

一、研究背景 “绿水青山就是金山银山&#xff0c;要让绿水青山变成金山银山”让人们深刻的意识到环境的重要性。与此同时&#xff0c;由于现代生活水平的不断提高&#xff0c;所带来的环境污染也不断增多&#xff0c;空气以及环境的污染带来了越来越多的疾病&#xff0c;深刻…

SVM单类异常值检测

SVM是一种广泛使用的分类器&#xff0c;通常用于二分类或多分类问题。然而&#xff0c;在异常点检测的场景中&#xff0c;我们通常会将数据视为一个类别&#xff08;即正常数据点&#xff09;&#xff0c;并尝试找到那些与正常数据点显著不同的点&#xff08;即异常点&#xff…

OS考研chapter3内存管理

目录 一、基础知识点补充 1.内存、内存地址概念与联系 2.按byte编址 vs 按字编码 二、进程运行的基本原理 1.指令的工作原理 2.逻辑地址 vs 物理地址 3.从写程序到程序运行 &#xff08;1&#xff09;编辑源代码 &#xff08;2&#xff09;编译 &#xff08;3&#xf…

深入浅出学习Pytorch—Pytorch简介与2024年最新安装(GPU)

深入浅出学习Pytorch—Pytorch简介 学习原因&#xff1a;Pytorch日益增长的发展速度与深度学习时代的迫切需要 Pytorch模型训练 pytorch实现模型训练包括以下的几个方面&#xff08;学习路线&#xff09; 数据&#xff1a;数据预处理与数据增强模型&#xff1a;如何构建模型模…

Java Jackson-jr 库是干什么用的

Jackson-jr 是一个轻量级的Java JSON 处理库。这个库被设计用来替代 Jackson 的复杂性。对比 Jackson 的复杂 API&#xff0c;Jackson-jr 的启动速度更快&#xff0c;包大小更小。 虽然Jackson databind&#xff08;如ObjectMapper&#xff09;是通用数据绑定的良好选择&#…

Linux变量的认识及环境变量配置详解

文章目录 1、变量的划分2、局部变量3、全局变量4、环境变量4.1、概述4.2、配置临时环境变量4.3、配置永久环境变量4.3.1、用户级配置文件1&#xff09;配置方法一&#xff1a;~/.bashrc文件2&#xff09;配置方法二&#xff1a;~/.profile文件3&#xff09;配置方法三&#xff…

git学习指南

文章目录 一.版本控制1.认识版本控制2.版本控制功能3.集中式版本控制4.分布式版本控制 二.Git的环境安装搭建1.Git的安装2.Git配置分类3.Git配置选项 三.Git初始化本地仓库1. git init/git clone-获取Git仓库2. 本地仓库文件的划分3. git status-检测文件的状态4. git add-文件…

什么?300TB SSD要来了?

SK海力士在韩国首尔的一场新闻发布会上宣布&#xff0c;其正在研发一款前所未有的300TB容量的固态硬盘&#xff08;SSD&#xff09;。这款硬盘的预告是该公司一系列旨在推动数据中心和设备端AI能力发展的产品与技术组合的一部分。SK海力士引用市场研究预测&#xff0c;全球在AI…

前端-React项目初始化

大家好我是苏麟 , 今天聊聊前端依赖 Ant Desgin Pro 快速初始化项目 . Ant Desgin Pro 官网 : 开始使用 - Ant Design Pro 初始化项目 找到文档->快速上手 脚手架命令 : # 使用 npm npm i ant-design/pro-cli -g创建项目命令 : pro create 项目名称 选择简单还是全量 : …

课时114:sed命令_进阶实践_高阶用法1

2.2.3 高阶用法1 学习目标 这一节&#xff0c;我们从 基础知识、缓存实践、小结 三个方面来学习。 基础知识 简介 对于sed命令来说&#xff0c;除了我们经常使用的模式空间之外&#xff0c;它还支持一个叫暂存空间(Hold Space)的模式,所谓的暂存空间&#xff0c;也就是说&a…

【软件工程】详细设计

目录 前言详细设计算法设计工具——判定表 前言 软件工程生命周期分为八个阶段&#xff1a; 问题定义—>可行性研究—>需求分析 —>概要设计—>详细设计—>编码与单元测试 —>综合测试—>软件维护 这节我们讲的是软件开发流程中的一个阶段&#xff0c;需求…

如何批量复制多个文件到多个目录中(提取匹配法)

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 具体操作 1、情景再现 我这里创建了3个数字命名的文件夹和一些带有数字命名的图片文件。 &#xff08;这里仅做演示作用&#xff0c;实际操作的数量肯定巨…

webpack与vite

webpack 使用步骤&#xff1a; 初始化项目 pnpm init -y安装依赖webpack、webpack-cli在项目中创建src目录&#xff0c;然后编写代码&#xff08;index.js&#xff09;执行pnpm weboack来对代码进行打包&#xff08;打包后观察dist文件夹&#xff09; 配置古文件&#xff08;w…

快速构建vscode pytest 开发测试环境

如果不想用 heavy 的pycharm vscode 也是1个很好的选择 安装python SDK pacman -S python [gatemanmanjaro-x13 tmp]$ pacman -Q python python 3.11.8-1安装Vscode 很多中方法 yay -S visual-studio-code-bin [gatemanmanjaro-x13 tmp]$ pacman -Q | grep -i visual visua…

HTML_CSS学习:列表相关属性

一、列表相关属性 相关代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>列表相关属性</title><style>ul{/*列表符号*//*list-style-type: decimal;*//*list-style-type…

使用OpenCV绘制两幅图验证DSC和IoU以及BCELoss的计算程序

1.创作灵感 很多小伙伴在玩深度学习模型的时候,需要计算Groudtruth和predict图的dsc、IOU以及BCELoss。这两个关键的指标的程序有很多种写法,今天使用OpenCV绘制两张已知分布的图像,计算其dsc、IOU以及BCELoss。 2、图像如图所示 在一个100100的区域内,红色框范围为预测…