Mybatis源码-三种SqlSession的区别

news2024/11/16 4:52:49

三个SqlSession
DefaultSqlSession与SqlSessionManager 与SqlSessionTemplate 是我常见的3种sqlsesion
在这里插入图片描述
从类图可以看出他们三个都实现了了SqlSession,也就是他们都可以表示一个会话。与其他不同的是SqlSessionManager实现了SqlSessionFactory

这三种sqlsession的区别是啥?他们的应用场景是啥呢?

这一切我们从DefaultSqlSession开始说起。

DefaultSqlSession
DefaultSqlSession 是SqlSession的默认实现。

当我们单独使用Mybatis时,我们通常使用DefaultSqlSession 来执行SQL,操作数据库。

 String resource = "mybatis-config.xml";
          // 读取配置文件
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 构建sqlSessionFactory
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          // 获取sqlSession
          SqlSession sqlSession = sqlSessionFactory.openSession();
          try {
             User user = sqlSession.selectOne("MyMapper.selectUser", 1);
             System.out.println(user);
          } finally {
             sqlSession.close();
    	}



但是DefaultSqlSession存在两个不足。

我们需要自己手动关闭sqlsesion,我们知道,人总是不可靠的。忘关sqlsession 是有很大概率发生的
线程安全问题:DefaultSqlSession是线程不安全的Sqlsession 。也就是说DefaultSqlSession不能是单例,
如何解决这两个问题?

自动关闭Session问题:

我可以自己做一个切面,专门处理session关闭问题
Mybatis为我们提供了升级版的DefaultSqlSession, SqlSessionManager可以解决这个问题
线程安全问题:

既然不能共用,很自然的,我们每次使用DefaultSqlSession的时候都从SqlSessionFactory当中获取一个就可以了啊。
但是我们仍然想使用单例版的Sqlsession怎么办?别慌,Mybatis为我们提供了升级版的DefaultSqlSession ,SqlSessionManager可以解决这个问题。
SqlSessionManager
个人认为,与其说SqlSessionManager是DefaultSqlSession 的升级版,不如说SqlSessionManager是DefaultSqlSession代理版(或者封装版)

为什么这样说?来看看SqlSessionManager能干吗

1.获取DefaultSqlSession的能力

	String resource = "mybatis-config.xml";
    // 读取配置文件
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionManager sqlSessionManager =SqlSessionManager.newInstance(inputStream);
    //获取一个SqlSession
    SqlSession session = sqlSessionManager.openSession();
    
    //SqlSessionManager 类的openSession
      public SqlSession openSession() {
        return sqlSessionFactory.openSession();
      }

从这个角度看,其实他跟SqlSession没有什么区别。他只是封装了SqlSession ,具体工作还是交给SqlSession去做的.
sqlSessionManager.openSession() = sqlSessionFactory.openSession()

得到的Sqlsession自然也是DefaultSqlSession

2.解决DefaultSqlSession的不足
2.1解决自动关闭问题
为了解决自动关闭问题,SqlSessionManager使用了代理技术来实现了自动关闭问题。

使用JDK动态代理技术,动态生成代理对象sqlSessionProxy ,并用内部类SqlSessionInterceptor来对SqlSession的执行方法进行增强。

private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    //使用JDK代理技术,生成一个代理对象
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

我们以insert为例来看看

sqlSessionManager.insert()

public int insert(String statement) {
    return sqlSessionProxy.insert(statement);
}
执行insert()方法时,sqlSessionManager内部是调用sqlsessionProxy代理对象的insert方法。之后执行增强器的SqlSessionInterceptor#invoke方法。

    @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
          if (sqlSession != null) {// 当使用线程本地变量
            try {
              return method.invoke(sqlSession, args);
            } catch (Throwable t) {
              throw ExceptionUtil.unwrapThrowable(t);
            }
          } else {//不使用线程本地变量。
              //从sqlSessionFactory获取一个DefaultSqlSession
            final SqlSession autoSqlSession = openSession();
            try {
              final Object result = method.invoke(autoSqlSession, args);
              autoSqlSession.commit();//提交
              return result;
            } catch (Throwable t) {
              autoSqlSession.rollback();//回滚
              throw ExceptionUtil.unwrapThrowable(t);
            } finally {
              autoSqlSession.close();//关闭sqlsession
            }
          }
        }
      }

nvoke方法内部,调用openSession() 从sqlSessionFactory中获取一个DefaultSqlSession,执行对应的方法,并在finally中执行关闭sqlsession

最后的执行时序

在这里插入图片描述
sqlSessionManager.insert() 的背后依然是DefaultSqlSession.insert 。并且帮助我们close 了DefaultSqlSession。

开发人员再也不必担心,忘记关闭DefaultSqlSession 了。

在执行sqlSessionManager的sqlsession方法时, 其本质也是每次都创建DefaultSqlSession ,不正是线程安全的吗?

单例是sqlSessionManager ;但真正执行的是DefaultSqlSession

2.1解决线程安全问题。
解决线程安全问题,sqlSessionManager 还有另一个方式,那就是使用线程本地变量,不同于每次执行CURD操作都重新获取一个DefaultSqlSession 。 线程本地变量这种方式是一个线程内使用同一个请求,这就大大节省了创建DefaultSqlSession 的时间,并且是线程安全的。

使用:

sqlSessionManager.startManagedSession();//绑定Session到线程本地变量
sqlSessionManager.insert()

原理:

private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

public void startManagedSession() {
    this.localSqlSession.set(openSession());
}

当startManagedSession()开始线程本地变量时,会从sqlSessionFactory获取一个session 放入到线程本地localSqlSession中,绑定到当前线程。

当我们执行sqlSessionManager.insert方法时,执行到增强器的invoke方法时,会从localSqlSession获取绑定到当前线程的sqlsession

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //从线程本地变量里获取
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }

小结:我们可以看出,SqlSessionManager通过动态代理技术+线程本地变量,升级了DefaultSqlSession的使用

qlSessionTemplate
SqlSessionTemplate 是Mybatis与Spring 整合时的线程安全sqlsession .

来看其构造方法

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

会发现他和SqlSessionManager 类似,SqlSessionTemplate 也使用了JDK动态代理技术来实现。SqlSessionTemplate 也有一个内部类增强器SqlSessionInterceptor。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //获取连接
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
      //执行sqlsession 目标方法
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

我们也从自动关闭与线程安全两个角度来看看SqlSessionTemplate

.解决线程安全问题
不同于SqlSessionManager 自己管理session的方式,SqlSessionTemplate 把session的管理外包出去了

SqlSessionTemplate 把获取sqlsession的工作交给了SqlSessionUtils去做了。

 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    //从事务同步器中获取Sqlsession。
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

SqlSessionUtils 会先尝试从TransactionSynchronizationManager事务同步器中获取sqlsesion,获取不到再从工厂内获取。

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);//从事务同步器中获取sqlsession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
    session = sessionFactory.openSession(executorType);
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}

TransactionSynchronizationManager 本身也是一个线程本地变量管理器。

从这一点来看,他和SqlSessionManager 是一样的。只是管理的方式不同,一个自己管,一个外包

2.解决自动关闭问题
同SqlSessionManager一样,在执行完session后,也会帮助close.
不同的是,

session 如果是由TransactionSynchronizationManager管理的,则只会更新引用计数器,让Spring在托管事务结束时调用close回调,关闭session。
session不是由TransactionSynchronizationManager 管理的,则直接关闭session

 public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
     //计数减1
      holder.released();
    } else {
    //关闭session
      session.close();
    }
  }

总结

DefaultSqlSession 与 SqlSessionManager 是Mybatis 默认提供的两个sqlsesion;SqlSessionTemplate是Mybatis与Spring整合时用的sqlsesion

DefaultSqlSession 是单例线程不安全的,SqlSessionManager与SqlSessionTemplate 是单例线程安全的

SqlSessionManager与SqlSessionTemplate 都对通过动态代理技术对DefaultSqlSession 自动关闭问题进行了优化

SqlSessionManager是自己管理sqlsession,SqlSessionTemplate外包给TransactionSynchronizationManager管理sqlsession。

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

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

相关文章

Windows 找不到文件‘chrome‘。请确定文件名是否正确后,再试一次

爱像时间&#xff0c;永恒不变而又短暂&#xff1b;爱像流水&#xff0c;浩瀚壮阔却又普普通通。 Windows 找不到文件chrome。请确定文件名是否正确后&#xff0c;再试一次 如果 Windows 提示找不到文件 "chrome"&#xff0c;可能是由于以下几种原因导致的&#xff1…

vmware中windows操作系统虚拟机安装

1.win10中安装 1.1 虚拟机向导 文件-新建虚拟机 典型-下一步 稍后安装操作系统-下一步 window10 64x -下一步 修改虚拟机名称及位置-下一步 默认60g,至少大于40g-将虚拟磁盘拆分成多个文件夹-下一步 点击完成 1.2 编辑虚拟机设置 移除打印机 设置虚拟机&#xff0c;加入iso映…

DAY4,Qt(事件处理机制的使用,Qt中实现服务器的原理)

1.Qt中实现服务器的原理&#xff1b; ---chatser.h---头文件 #ifndef CHATSER_H #define CHATSER_H#include <QWidget> #include <QTcpServer> //服务器类 #include <QTcpSocket> //套接字类 #include <QMessageBox> //消息对话类 #include <Q…

机器学习深度学习——模型选择、欠拟合和过拟合

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——多层感知机的简洁实现 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有…

HHDESK便捷功能介绍三

1 连接便捷显示 工作中&#xff0c;往往需要设置很多资源连接。而过多的连接设&#xff0c;往往很容易混淆。 在HHDESK中&#xff0c;当鼠标点击连接时&#xff0c;会在下方显示本连接的参数&#xff0c;方便用户查看。 2 日志查看 实际工作中&#xff0c;查看日志是一件很…

干货 | 5个经典的模拟电路解析,电子人必看!

干货 | 5个经典的模拟电路解析&#xff0c;电子人必看&#xff01; 作为一个电子人&#xff0c;我们平时需要和不同的电路接触&#xff0c;但有一些电路图是经典的&#xff0c;值得我们永远记住。一、自举电路 此电路用在各种ADC之前的采样电路&#xff0c;可以让ADC实现轨到轨…

lc154.寻找旋转排序数组中的最小值

最小元素的位置以旋转次数为索引的位置&#xff0c;但是没有告诉旋转次数&#xff0c;换一种思路 当遇到arr[index] > arr[index1]时&#xff0c;index1为最小元素的位置。首位位置独立比较。但是这种方法还是遍历数组 观察两组数的中间值与首尾的值&#xff0c;又由于数组…

变现:利用 chatgpt + midjourney 制作微信表情包

1、利用gpt生成提示词&#xff0c;当然也可以直接翻译 生成基础提示词&#xff0c; 比如&#xff1a; an anime image with a white kawaii character in it, in the style of light green and brown, minimalist detail, animated gifs, cranberrycore, 1860–1969, babyco…

企业做数字化转型,请先避开这5个坑!

前言&#xff1a; “多种薪酬结构并存”的中国企业&#xff0c;在面临线下算薪、线上算税、表格数据整理易出错的问题时&#xff0c;有没有高效的解决办法&#xff1f;在考勤记录需要结合纸质版假条核对时&#xff0c;怎样减少人事的工作量&#xff1f;企业在积累了海量业务数…

【探讨】Java POI 处理 Excel 中的名称管理器

前言 最近遇到了一些导表的问题。原本的导表工具导不了使用名称管理器的Excel。 首先我们有两个Sheet。B1用的是名称管理器中的AAA, 而B2用的对应的公式。 第二个sheet&#xff0c;名为Test2: 这是一段简化的代码&#xff1a; public class Main {public static void mai…

Rust ESP32C3开发

Rust ESP32C3开发 系统开发逐步使用Rust语言&#xff0c;在嵌入式领域Rust也逐步完善&#xff0c;本着学习Rust和ESP32的目的&#xff0c;搭建了ESP32C3的环境&#xff0c;过程中遇到了不少问题&#xff0c;予以记录。 ESP-IDF开发ESP32 这一部分可跳过&#xff0c;是使用C开…

浏览器中的Markdown编辑器StackEdit

目前博客的 Pageviews 大约是之前的 10%&#xff0c;而 Uniques 则大约是 15% 左右。看来很多同学已经彻底迷路了 大家可以关注CSDN&#xff0c;地址&#xff1a; https://blog.csdn.net/wbsu2004 微信公众号也可以关注起来 什么是 StackEdit &#xff1f; StackEdit 是基于 P…

pyspark 笔记 cast 转换列的类型

1 不借助 pyspark.sql.types from pyspark.sql.functions import coldata [("Alice", "28"), ("Bob", "22"), ("Charlie", "30")] columns ["name", "age_str"] df spark.createDataFram…

如何使用vscode连接远程服务器

1、安装remote-ssh 在应用商店搜索remote-ssh&#xff0c;安装remote-ssh 2、安装完成后会出现远程资源管理器 3、点击远程资源管理器 --ssh的➕号&#xff0c;在输出框内输入要连接的服务器ip及账户名 如&#xff1a;ssh 账户名ip地址 4、输入后回车保存 5、保存后刷新一下 6…

SOLIDWORKS磁力配合工具

在我们平常的工作中&#xff0c;很多人都会面临大型装配体相互配合的问题。有很多用户&#xff0c;由于设计的产品体积很大&#xff0c;零件数量非常多。即使将设备分成不同的部件进行组装&#xff0c;不同的部件之间进行配合也非常困难。因为这种时候软件运行的速度会变得非常…

【UE5 多人联机教程】06-显示玩家名称

效果 可以看到玩家输入各自的名称&#xff0c;会显示到自己控制的角色头上。但是目前有一个BUG就是&#xff0c;当客户端加入游戏时会多创建一个服务端的角色。 步骤 1. 打开“BP_ThirdPersonCharacter”&#xff0c;添加一个控件组件&#xff0c;用于显示玩家名称 作为网格体…

国产内存惹人爱,光威的价格战太凶猛,海外品牌已无力招架

现阶段&#xff0c;真的很适合升级内存条和SSD&#xff01;当然了&#xff0c;我说的是国产的品牌&#xff0c;经过这几年的发展&#xff0c;国产内存和SSD的表现都有了质的飞跃&#xff0c;像是光威之类的品牌&#xff0c;更是成功在玩家群体中获得了良好的口碑&#xff0c;而…

数据库的介绍

无处不在的数据库 信息化社会&#xff0c;无处不在的就是数据。 编程语言可以归纳为&#xff0c;数据的计算一类。 那数据的存储呢? 数据库就是存储数据的库&#xff0c;那么它是如何组织数据并存储的呢? 数据库如何存储数据 我们先来看看&#xff0c;生活中&#xff0c;…

通过社区参与解锁早期增长:Maven 远程医疗平台概览

Maven通过用户导向的渐进式验证&#xff0c;找到了一个被忽视的巨大女性医疗服务市场&#xff0c;作为女性医疗保健的先行者&#xff0c;已服务超过1500万用户&#xff0c;目前估值已达$14亿。本文将深入探索Maven实现产品市场匹配的三个阶段&#xff0c;从如何验证初始的市场机…

Junit5 + YAML 轻松实现参数化和数据驱动(一)

登录&#xff1a;不同的用户名&#xff0c;不同的密码&#xff0c;不同的组合都需要做登录场景的测试&#xff0c;正常的排列组合下可能会产生多个用例 搜索&#xff1a;不同的搜索条件产生不同的搜索结果&#xff0c;搜索也是常见的测试项&#xff0c;单个搜索参数或者多种搜…