MyBatis源码分析之SqlSession

2023-11-05

SqlSession接口以及门面模式

SqlSession是一个接口类型,它的接口方法包含了如下几个方面的功能

  1. 增删改查的操作
    selectOne,selectList,delete,update,insert等操作
  2. 事务的管理操作
    commit rollback flushstatements close
  3. 获取当前的Connection对象
    getConnection
  4. 获取XNL解析出的配置信息
    getConfiguration
  5. 清空缓存的操作
    clearCache
  6. 实现接口的动态代理
    getMapper

之所以将这么多不同的类型的接口方法定义在一个统一的接口中是为了方便用户使用,通过一个SqlSession’对象就可以完成几乎所有的需求。
当然,该接口的角色是一类似于服务员,这些方法并没有具体实现,而是在用户请求时分派给相应的子系统完成。
这样的设计称为门面模式,SqlSession是门面类。
下面我们看看Mybatis具体实现的SqlSession类,探究门面类下包含了多少个子系统,SqlSession又是如何同它们交互的。

DefaultSqlSession类

我们先看下它的字段以及构造方法

public class DefaultSqlSession implements SqlSession {
  // xml配置对象
  private final Configuration configuration;
  // executor用来执行增删改查操作的对象
  private final Executor executor;
  // 事务管理中的是否自动提交
  private final boolean autoCommit;
  // 用来标记当前会话是否有更新操作,如果有则设为true,表示缓存中可能有脏数据
  private boolean dirty;
  //游标列表
  private List<Cursor<?>> cursorList;
	// 两个构造方法,主要是给私有字段赋值
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
  }
}

关于增删改查操作的实现

首先是selectOne(查询一条记录)的实现。statement是sql语句的id,通过这个id我们才能知道需要执行哪一条语句,parameter是用于查询的参数,这个参数可以不传,MyBatis实现了方法的重载。
可以发现,selectOne实际上调用的是selectList方法,用来返回查询的对象集合,如果记录数等于一就返回,大于一就抛出异常,否则就返回null。

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }
 @Override
  public <T> T selectOne(String statement) {
    return this.selectOne(statement, null);
  }

下面看一下查询的核心方法selectList。
可以发现selectList可以接受四个参数,并且除了statement不能为空以外,其它三个参数都可以不传,并进行了相应的方法重载。
只需要把重点关注到最后一个selectList的实现。

 @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
  }

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  	/* statement表示sql语句的id
	*  parameter是用来查询的参数
	* rowBounds是控制分页的参数
	* handler是用来处理结果集函数接口
	*/ 
    try {
    // 通过id从configuration中查找到了Statement对象,它其中封装了这条sql语句的相关信息,比如具体的Sql语句是什么,缓存等等
      MappedStatement ms = configuration.getMappedStatement(statement);
     // 实际的查询是通过executor的query方法查找的
     //这个wrapCollection是干什么的呢?
     /* 它是用来判断当前传递的参数是不是一个Collection,并会进行相应的处理
     * 现在我们并不需要知道query里的具体逻辑是什么,只需要知道我们查询时实际调用的是executor的query方法
	*/
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

下面是select的三种重载形式,它事实上调用的也是selectList方法,区别是它没有返回值,且必须传入ResultHandler类

@Override
  public void select(String statement, Object parameter, ResultHandler handler) {
    select(statement, parameter, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, ResultHandler handler) {
    select(statement, null, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    selectList(statement, parameter, rowBounds, handler);
  }

查询Cursor调用的是executor的queryCursor方法

 @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

update相较于select较为简单,就是query的update方法来更新参数,它只需要sql的id和用来更新的参数
值得注意的是,一但sqlSession执行了update方法,dirty就会被设置为true,用来标记缓存中可能有脏数据

public int update(String statement) {
    return update(statement, null);
  }

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

insert和delet本质也是数据的更新,所以调用的也是executor的update方法。

 @Override
  public int insert(String statement) {
    return insert(statement, null);
  }

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
   @Override
  public int delete(String statement) {
    return update(statement, null);
  }

  @Override
  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }

总结一下
所有的select操作依赖于selectList和selectCursor,而它们又依赖于executor的query和queryCursor方法
所有的delet,insert,update操作都依赖于update,而它又依赖于executor的query方法。

关于事务管理的实现

/*
* 该方法判断是否应该提交事务或者回滚事务,分析可知,在不是自动提交事务的情况下,只有dirty为true时才需要回滚或者提交
* dirty只有在执行更新操作以后才会设置为true,并且在commit或者rollback之后重新置为false
* 而commit和rollback同样是调用了executor的相应接口
*/
private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }
@Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void rollback() {
    rollback(false);
  }

  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

可以发现getConnection和clearCache同样依赖于executor

@Override
  public Connection getConnection() {
    try {
      return executor.getTransaction().getConnection();
    } catch (SQLException e) {
      throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
    }
  }

  @Override
  public void clearCache() {
    executor.clearLocalCache();
  }

下面两个是和Configuration的相关对象

 @Override
  public Configuration getConfiguration() {
    return configuration;
  }

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

最后总结分析一下DefaultSqlSession的几个字段
executor 负责增删改查,事务管理,清理缓存和获取连接对象的操作
dirty 判断当前是否可能存在脏数据,对事务管理起作用
autoCommit 是否自动提交事务
configuration xml配置对象,可以通过它完成mapper接口类的动态代理,以及通过sql的id查找相关的MappedStatement对象
cursorList 用来保存游标变量的数组

SqlSessionManager

SqlSessionManager同样实现了SqlSession接口,但一般用不到,它主要的功能是对现有的SqlSession对象做代理,增强它的方法。

接下来的内容

在下一篇博文中,我会分析SqlSession中用到的Executor对象,关注具体的查询逻辑。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MyBatis源码分析之SqlSession 的相关文章

随机推荐

  • Docker日志查看命令

    docker容器列表查看 docker ps 日志查看语法 docker logs OPTIONS CONTAINER OPTIONS说明 f 跟踪日志输出 since 显示某个开始时间的所有日志 t 显示时间戳 tail 仅列出最新N条容
  • 2022十三届蓝桥杯国赛题解

    特此声明 本文仅为参考文档 标准答案请参考官方文档 试题A 该题是一道背包dp题 我的思路是定义三维dp 第一维表示第i个数 第二维表示前i个数的总和为j 第三维表示前i个数 总和为j 第i个数为z的方案数 首先观察这个题的性质 要求互不相
  • 十五、使用Selector(多路复用器)实现Netty中Reactor主从模型

    导论 前面几篇文章我们分别从 一 C10K问题经典问答 二 java nio ByteBuffer用法小结 三 Channel 通道 四 Selector选择器 五 Centos Linux安装nc 六 windows环境下netcat的安
  • 医疗场景下CV应用的总结与思考

    目录 背景 项目总体思路与框架 技术难题及解决思路 Data模块 Modeling模块 Deployment模块 可优化方向 标注流程 基于GAN的眼底图像生成模型 隐私计算 联邦学习 即时标注平台 自动化机器学习 背景 随着新生儿眼底筛查
  • 【Flask】Flask 会话

    Flask Sessions 会话 由于HTTP是无状态的 因此无法纪录客户一连串的动作 必须有一种机制使服务器能认得客户 这就引入了 会话 概念 服务器发给客户一个会话ID 当客户再访问服务器时就带着这个ID 服务器就凭着这个唯一的ID来
  • 毕业实习:Vue+Springboot+MySQL实现的订餐系统

    为期4天的毕业实习的收获 第一部分 vue框架的准备工作 测试vue cli创建工程 要以管理员身份运行cmd 1 在F盘下新建一个F shixuntestvue code文件 2 cd F 3 cd shixuntestvue code
  • python爬虫+数据分析完整流程--豆瓣电影分类排行榜

    整体思路 利用requests BeautifulSoup爬虫豆瓣电影分类排行榜数据 数据输出到本地csv文件 构建mongodb数据库 将数据存放入mongodb 利用pandas matplotlib画图分析数据 1 利用Beautif
  • Nginx安全性配置

    隐藏版本号 http server location http server tokens off 访问黑白名单设置 http server location limit except 白名单 只允许192 168 1 0 24网段的主机访
  • POJO/DTO/DO/EO/VO/BO/PO/AO的含义和使用

    关于POJO DTO DO EO VO BO PO AO 本文讨论 POJO DTO DO EO VO BO PO AO 的定义 另外讨论了这些xO在controller service dao mapper层里的使用规范 另外还稍微讨论了
  • 2023年第十四届蓝桥杯第一题阶乘求和的python另类解法

    蓝桥杯2023 求和 1 2 3 202320232023 的后9位数字 模除 乘方 n 202320232023 s s1 0 temp 10 9 for i in range 1 n 1 m 1 for k in range 1 i 1
  • 【Git系列】IDEA集成Git

    IDEA集成Git 1 idea配置git 2 idea添加暂存区和提交 创建文件 将整个项目添加到暂存区 提交到本地仓库 查看控制台 显示提交的信息 修改文件 再次提交 3 idea拉取和推送 拉取远程仓库 推送远程仓库 4 idea克隆
  • Qt出现“undefined reference to vtable for”原因总结 (转my gallery)

    由于Qt本身实现的机制所限 我们在使用Qt制作某些软件程序的时候 会遇到各种各样这样那样的问题 而且很多是很难 或者根本找不到原因的 即使解决了问题 如果有人问你为什么 你只能回答 不知道 今天我在这里列举的问题也是再编写Qt程序时 总是遇
  • 中文文本分类-朴素贝叶斯

    原创作品 出自 晓风残月xj 博客 欢迎转载 转载时请务必注明出处 http blog csdn net xiaofengcanyuexj 由于各种原因 可能存在诸多不足 欢迎斧正 最近在想怎么利用数据挖掘的方法进行评论自动审核 分类为垃圾
  • linux下启动tomcat服务

    目前有个java mysql项目需部署到 麒麟系统 一番折腾后总算成功部署上去了 其操作和windows有本质区别 需要使用终端命令行 现将常用运维操作整理如下 Linux下tomcat服务的启动 关闭与错误跟踪 使用PuTTy远程连接到服
  • VSCode 连不上远程服务器问题及解决办法集合

    楼主由于突然 VSCode 连不上服务器 因此从网上搜到了很多解决办法 楼主觉得可以将这些方法集中起来 为遇到这种连接问题的人们作为参考 该资料将持续更新 也欢迎各位大神留言提供更多解决办法 目录 1 Error Establishing
  • Games101,作业7(模板分析)

    该博客只分析较难理解的函数和用途 以及程序运行方式 简单的函数不再赘述 首先 我们将从程序运行的流程来理解代码框架 之后再按照代码文件来分析整个框架的构造思路 程序流程 main函数进入 构造Scene类 构造场景 Scene scene
  • Git基础操作:git只删除远程文件

    比如我使用idea进行开发 不小心将 idea目录提交到远程仓库了 即使后增加了 gitignore忽略此目录 但是之前已经提交到仓库的文件是不管用的 按照下面命令即可只删除远程仓库的文件夹 git rm r cached idea cac
  • 消息队列RabbitMQ核心:简单(Hello World)模式、队列(Work Queues)模式、发布确认模式

    文章目录 一 简单模式 Hello World 代码实现 二 队列模式 Work Queues 轮训分发消息 代码实现 消息应答 概述 RabbitMQ持久化 不公平分发 三 发布确认模式 原理概述 发布确认策略 单个确认发布 批量确认发布
  • 微信小程序获取当前的地理位置wx.getLocation接口快速通过审核的解决办法

    某鱼社区团购商城系统小程序提交审核接口一直没通过 分析了原因多半是没提交图片 某鱼社区团购商城系统小程序会用到以下三个接口 其中wx getLocation接口经常发现不通过 这问题我也遇上过提交了两次才通过 某鱼社区团购商城系统小程序这三
  • MyBatis源码分析之SqlSession

    SqlSession接口以及门面模式 SqlSession是一个接口类型 它的接口方法包含了如下几个方面的功能 增删改查的操作 selectOne selectList delete update insert等操作 事务的管理操作 com