mybatis核心源码深度剖析核心执行器executor和缓存原理

赵广陆      2022-05-21     391

关键词:

目录


1 JDBC回顾

  1. 回顾JDBC执行过程
    添加jar包 -> 获得连接 -> 预编译SQL -> 执行SQL,读取结果 -> 关闭事务
public static void main(String[] args) throws Exception 
  // 1、注册驱动
  DriverManager.registerDriver(new com.mysql.jdbc.Driver());
  // 2、建立连接
  Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/test?
useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT", "root", "root");
  // 3、编写sql,进行预编译
  String sql = " select * from tb_brand;";
  PreparedStatement ps = con.prepareStatement(sql);
  // 4、执行查询,得到结果集
  ResultSet rs = ps.executeQuery();
  while (rs.next()) 
    int bid = rs.getInt("bid");
    String bname = rs.getString("bname");
   System.out.println("====> bid=" + bid + "\tbname=" + bname);
 
  //5、关闭事务
  rs.close();
  ps.close();
  con.close();

  1. MyBatis对JDBC封装的执行过程

2 MyBatis的核心执行组件介绍

在Mybatis中,SqlSession对数据库的操作,将委托给执行器Executor来完成都将委托给执行器
Executor来完成;
Mybatis执行过程,主要的执行模块是: SqlSession->Executor->StatementHandler->数据库
四个核心组件:
① 动态代理 MapperProxy
② SQL会话 SqlSession
③ 执行器 Executor
④ JDBC处理器 StatementHandler

2.1 SqlSession

SqlSession采用了门面模式方便来让我们使用。他不能跨线程使用,一级缓存生命周期和它一致;
基本功能:增删改查基本的操作功能;
辅助功能:提交、关闭会话;
门面模式:提供一个统一的门面接口API,使得系统更加容易使用。

2.2 Executor

基本功能:改、查、维护缓存;
辅助功能:提交、关闭执行器、批处理刷新;
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作
委托给 StatementHandler完成。

2.3 StatementHandler

经过执行器处理后交给了StatementHandler(声明处理器);
主要作用就是:参数处理、结果处理;
StatementHandler 首先通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过
java.sql.Statement 对象执行 SQL 语句并得到结果集,最后通过 ResultSetHandler 完成结果
集的映射,得到结果对象并返回。

3 Executor执行器分析

3.1 JDBC中的执行器

JDBC有三种执行器分别是Statement(简单执行器)、PreparedStatement(预处理执行器)、
CallableStatement(存储过程执行器)
Statement:基本功能:执行静态SQL
PreparedStatement:设置预编译,防止SQL注入
CallableStatement:设置出参、读取参数(用于执行存储过程)

3.2 Mybatis执行器

Executor继承结构分析

Mybatis给我们提供了三种执行器,分别是 :
SimpleExecutor(简单执行器)、
ResuseExecutor(可重用执行器)、
BathExecutor(批处理执行器)
这三个执行器继承了一个BaseExecutor(基础执行器),而这个基础执行器实现了Executor接口,其中
简单执行器是默认的执行器。
其实还有一种执行器CachingExecutor(二级缓存执行器)你开启二级缓存则会实例化它,在 BaseExecutor
的基础上,实现二级缓存功能。 (注意: BaseExecutor 的本地缓存,就是一级缓存。)

3.2.1 Executor接口

org.apache.ibatis.executor.Executor ,执行器接口。
主要定义了以下内容:
读和写操作相关的方法
事务相关的方法
缓存相关的方法
设置延迟加载的方法
设置包装的 Executor 对象的方法

public interface Executor 
  // 空 ResultHandler 对象的枚举
  ResultHandler NO_RESULT_HANDLER = null;
  // 更新 or 插入 or 删除,由传入的 MappedStatement 的 SQL 所决定
  int update(MappedStatement ms, Object parameter) throws SQLException;
  // 查询,带 ResultHandler + CacheKey + BoundSql
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws
SQLException;
  // 查询,带 ResultHandler
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException;
  // 查询,返回值为 Cursor
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds
rowBounds) throws SQLException;
  // 刷入批处理语句
  List<BatchResult> flushStatements() throws SQLException;
  // 提交事务
  void commit(boolean required) throws SQLException;
  // 回滚事务
  void rollback(boolean required) throws SQLException;
  // 创建 CacheKey 对象
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, BoundSql boundSql);
  // 判断是否缓存
  boolean isCached(MappedStatement ms, CacheKey key);
  // 清除本地缓存
  void clearLocalCache();
  // 延迟加载
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property,
CacheKey key, Class<?> targetType);
  // 获得事务
  Transaction getTransaction();
  // 关闭事务
  void close(boolean forceRollback);
  // 判断事务是否关闭
  boolean isClosed();
  // 设置包装的 Executor 对象
  void setExecutorWrapper(Executor executor);

3.2.2 BaseExecutor(基础执行器)

基础执行器:维护一级缓存,是simple、reuse、batch这三个执行器的父类。主要逻辑是维护缓 存,其他实现交给子类。 org.apache.ibatis.executor.BaseExecutor 实现 Executor 接口,提 供骨架方法,从而使子类只要实现指定的几个抽象方法即可。

/**
* 事务对象
*/
protected Transaction transaction;
/**
* 包装的 Executor 对象
*/
protected Executor wrapper;
/**
* DeferredLoad( 延迟加载 ) 队列
*/
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
/**
* 本地缓存,即一级缓存
*/
protected PerpetualCache localCache;
/**
* 本地输出类型的参数的缓存
*/
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
/**
* 记录嵌套查询的层级
*/
protected int queryStack;
/**
* 是否关闭
*/
private boolean closed;
// ************************
/**
clearLocalCache() 方法,清理一级(本地)缓存
*/
@Override
public void clearLocalCache() 
  if (!closed) 
    // 清理 localCache
    localCache.clear();
    // 清理 localOutputParameterCache
    localOutputParameterCache.clear();
 

// createCacheKey(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, BoundSql boundSql) 方法,创建 CacheKey 对象
// isCached(MappedStatement ms, CacheKey key) 方法,判断一级缓存是否存在
// query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException 
  // <1> 获得 BoundSql 对象
  BoundSql boundSql = ms.getBoundSql(parameter);
  // <2> 创建 CacheKey 对象
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  // <3> 查询
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);

// update方法
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException 
  ErrorContext.instance().resource(ms.getResource()).activity("executing an
update").object(ms.getId());
  // <1> 已经关闭,则抛出 ExecutorException 异常
  if (closed) 
    throw new ExecutorException("Executor was closed.");
 
  // <2> 清空本地缓存
  clearLocalCache();
  // <3> 执行写操作
  return doUpdate(ms, parameter);

3.2.3 SimpleExecutor(简单执行器)

每次读或写操作都会创建一个新的预处理器(PrepareStatement);
每次执行的的SQL都会进行一次预编译;
执行完成后,关闭该 Statement 对象。
org.apache.ibatis.executor.SimpleExecutor ,继承 BaseExecutor 抽象类,简单的 Executor 实现
类。

// 查询
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException 
  Statement stmt = null;
  try 
    Configuration configuration = ms.getConfiguration();
    // <1> 创建 StatementHandler 对象
    StatementHandler handler = configuration.newStatementHandler(wrapper,
ms, parameter, rowBounds, resultHandler, boundSql);
    // <2> 初始化 StatementHandler 对象
    stmt = prepareStatement(handler, ms.getStatementLog());
    // <3> 执行 StatementHandler ,进行读操作
    return handler.query(stmt, resultHandler);
  finally 
 // <4> 关闭 StatementHandler 对象
    closeStatement(stmt);
 

private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException 
  Statement stmt;
  // <2.1> 获得 Connection 对象
  Connection connection = getConnection(statementLog);
  // <2.2> 创建 Statement 或 PrepareStatement 对象
  stmt = handler.prepare(connection, transaction.getTimeout());
  // <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
  handler.parameterize(stmt);
  return stmt;

// 更新
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException 
  Statement stmt = null;
  try 
    Configuration configuration = ms.getConfiguration();
    // 创建 StatementHandler 对象
    StatementHandler handler = configuration.newStatementHandler(this, ms,
parameter, RowBounds.DEFAULT, null, null);
    // 初始化 StatementHandler 对象
    stmt = prepareStatement(handler, ms.getStatementLog());
    // <3> 执行 StatementHandler ,进行写操作
    return handler.update(stmt);
  finally 
    // 关闭 StatementHandler 对象
    closeStatement(stmt);
 

3.2.4 ReuseExecutor(可重用执行器)

每次开始读或写操作,以sql作为key,查找Statement对象优先从缓存中获取对应的 Statement
对象。如果不存在,才进行创建。( 只要是相同的SQL只会进行一次预处理)
执行完成后,不关闭该 Statement 对象,而是放置于Map<String, Statement>内,供下一次使
用。
其它的,和 SimpleExecutor 是一致的。
原理:

//可重用的执行器内部用了一个map,用来缓存SQL语句对应的Statement对象
//Key是我们的SQL语句,value是我们的Statement对象
private final Map<String, Statement> statementMap = new HashMap<String,
Statement>();

org.apache.ibatis.executor.ReuseExecutor ,继承 BaseExecutor 抽象类,可重用的 Executor 实
现类。

// doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException 
  Configuration configuration = ms.getConfiguration();
  // 创建 StatementHandler 对象
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms,
parameter, rowBounds, resultHandler, boundSql);
  // 初始化 StatementHandler 对象
  Statement stmt = prepareStatement(handler, ms.getStatementLog());
  // 执行 StatementHandler ,进行读操作
  return handler.query(stmt, resultHandler);

// doUpdate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException 
  Configuration configuration = ms.getConfiguration();
  // 创建 StatementHandler 对象
  StatementHandler handler = configuration.newStatementHandler(this, ms,
parameter, RowBounds.DEFAULT, null, null);
  // 初始化 StatementHandler 对象
  Statement stmt = prepareStatement(handler, ms.getStatementLog());
  // 执行 StatementHandler ,进行写操作
  return handler.update(stmt);

private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException 
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  // 存在
  if (hasStatementFor(sql)) 
    // <1.1> 从缓存中获得 Statement 或 PrepareStatement 对象
    stmt = getStatement(sql);
    // <1.2> 设置事务超时时间
    applyTransactionTimeout(stmt);
  // 不存在
  else 
    // <2.1> 获得 Connection 对象
    Connection connection = getConnection(statementLog);
    // <2.2> 创建 Statement 或 PrepareStatement 对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    // <2.3> 添加到缓存中
    putStatement(sql, stmt);
 
  // <2> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
  handler.parameterize(stmt);
  return stmt;

// 判断是否存在对应的 Statement 对象
private boolean hasStatementFor(String sql) 
  try 
    return statementMap.keySet().contains(sql) &&
!statementMap.get(sql).getConnection().isClosed();
  catch (SQLException e) 
    return false;
 

注意区别

注意:
ReuseExecutor 考虑到重用性,但是 Statement 最终还是需要有地方关闭。答案就在
#doFlushStatements(boolean isRollback) 方法中。而 BaseExecutor 在关闭 #close() 方法
中,最终也会调用该方法,从而完成关闭缓存的 Statement 对象们
BaseExecutor 在提交或者回滚事务方法中,最终也会调用该方法,也能完成关闭缓存的 Statement
对象们。

3.2.5 BatchExecutor(批处理执行器)

批处理只对增删改SQL有效(没有select,JDBC批处理不支持select);
将所有增删改sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓
存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行
executeBatch()批处理的;
执行sql需要满足三个条件才能使用同一个Statement(使用同一个Statement是为了压缩体积、
减少SQL预处理)
1.sql相同
2.同一个MappedStatement(sql标签的配置都在这里面)
3.执行的顺序必须是连续的
org.apache.ibatis.executor.BatchExecutor ,继承 BaseExecutor 抽象类,批量执行的 Executor
实现类。

public class BatchExecutor extends BaseExecutor 
/**
  * Statement 数组
  */
  private final List<Statement> statementList = new ArrayList<>();
  /**
  * BatchResult 数组
  *
  * 每一个 BatchResult 元素,对应一个 @link #statementList 的 Statement 元素
  */
  private final List<BatchResult> batchResultList = new ArrayList<>();
  /**
  * 当前 SQL
  */
  private String currentSql;
  /**
  * 当前 MappedStatement 对象
  */
  private MappedStatement currentStatement;

  //doUpdate
  @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws
SQLException 
    final Configuration configuration = ms.getConfiguration();
    // <1> 创建 StatementHandler 对象
    final StatementHandler handler = configuration.newStatementHandler(this,
ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    // <2> 如果匹配最后一次 currentSql 和 currentStatement ,则聚合到 BatchResultif (sql.equals(currentSql) && ms.equals(currentStatement)) 
      // <2.1> 获得最后一次的 Statement 对象
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      // <2.2> 设置事务超时时间
      applyTransactionTimeout(stmt);
      // <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
      handler.parameterize(stmt);
      // <2.4> 获得最后一次的 BatchResult 对象,并添加参数到其中
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    // <3> 如果不匹配最后一次 currentSql 和 currentStatement ,则新建 BatchResult
对象
    else 
   // <3.1> 获得 Connection
      Connection connection = getConnection(ms.getStatementLog());
      // <3.2> 创建 Statement 或 PrepareStatement 对象
      stmt = handler.prepare(connection, transaction.getTimeout());
      // <3.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
      handler.parameterize(stmt);   //fix Issues 322
      // <3.4> 重新设置 currentSql 和 currentStatement
      currentSql = sql;
      currentStatement = ms;
      // <3.5> 添加 Statement 到 statementList 中
      statementListmybatis核心源码深度剖析工作机制和实现原理

目录1MyBatis源码分析导入1.1为什么要看MyBatis框架的源码1.2如何深入学习MyBatis源码1.3源码分析的5大原则2MyBatis架构体系深入剖析2.1MyBatis的整体架构体系2.2MyBatis的工作机制和实现原理2.2.1接口层2.2.1.1获取SqlSession流程分析2.2.1.2SqlSess... 查看详情

mybatis:mybatis核心组件介绍原理解析和源码解读

Mybatis核心成员Configuration    MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中SqlSession      作为MyBatis工作的主要顶层API,表示和数据库交互时的会... 查看详情

nacos源码系列——第二章(nacos核心源码主线剖析下)(代码片段)

上章节我这边带着大家看了下Nacos的源码,针对上节课做个总结:Nacos服务注册过程深度剖析Nacos注册表如何防止多节点读写并发冲突Nacos高并发支撑异步队列与内存队列剖析Nacos心跳机制(讲了一半)那么本节课我... 查看详情

唯一插件化replugin源码及原理深度剖析--初始化之框架核心(代码片段)

上一篇:唯一插件化RePlugin源码及原理深度剖析–工程职责提示:请不要忽略代码注释,由于通畅上下逻辑思维,不太重要的部分跳转代码不会全部进去一行行的看,但是会将注释写出来,所以请务必不要... 查看详情

深度挖掘rocketmq底层源码「底层源码挖掘系列」透彻剖析贯穿rocketmq的消费者端的运行核心的流程(上篇)(代码片段)

...层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行核心的流程上篇:分析对应总体消费流程的判断和校验以及限流控制和回调等处理流程分析下篇:分析基于上篇的总体流程的底层的消息通讯以及拉去处理数据传输... 查看详情

mybatis源码分析-----核心调度对象statmenthandler

...章,我们知道:mybatis的插件开发,主要是集中在Executor(执行器),ParameterHandler(参数处理器),ResultSetHandler(结果集处理器),StatementHandler(语句处理器)。  我们知道了mybatis对外暴露的API(SqlSession)的操作,其实是其持有Execu... 查看详情

深度挖掘rocketmq底层源码「底层源码挖掘系列」透彻剖析贯穿rocketmq的消费者端的运行核心的流程(pull模式-下)(代码片段)

...层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行核心的流程(Pull模式-上)】pullBlockIfNotFound方法通过该方法获取该MessageQueue队列下面从offset位置开始的消息内容,其中maxNums=32即表示获取的最大消息个数ÿ... 查看详情

easyexcel专题深度解析读流程核心源码(代码片段)

...t()方法初始化ExcelReaderSheetBuilder3)根据excel类型选择对应的执行器executor的实现类XlsxAnalyser4)使用OPCPa 查看详情

easyexcel专题深度解析读流程核心源码(代码片段)

...t()方法初始化ExcelReaderSheetBuilder3)根据excel类型选择对应的执行器executor的实现类XlsxAnalyser4)使用OPCPackage将Excel文件转换为Xml5 查看详情

easyexcel专题深度解析读流程核心源码(代码片段)

...t()方法初始化ExcelReaderSheetBuilder3)根据excel类型选择对应的执行器executor的实现类XlsxAnalyser4)使用OPCPackage将Excel文件转换为Xml5 查看详情

mybatis源码分析四xml解析与核心对象的构建(代码片段)

四、XML解析与核心对象的构建到此为止,已经把MyBatis核心的代理以及与JDBC的交互逻辑梳理完成,下面来看看,配置文件以及mapper.xml的加载和SqlSessionFactory的创建。InputStreaminputStream=Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");... 查看详情

二期0005线程池原理剖析&锁的深度化

...线程池中,线程池的处理流程如下:1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程... 查看详情

spring源码核心剖析(代码片段)

SpringAOP作为Spring最核心的能力之一,其重要性不言而喻。然后需要知道的是AOP并不只是Spring特有的功能,而是一种思想,一种通用的功能。作者:京东科技韩国凯前言SpringAOP作为Spring最核心的能力之一,其重要性不言而喻。然后... 查看详情

mybatis源码分析三mybatis的核心对象及其作用

三、MyBatis的核心对象及其作用本文2.3w字,详细介绍了MyBatis的核心对象和作用以及MyBatis运行流程,是如何通过动态代理创建实现类的。文章目录三、MyBatis的核心对象及其作用数据存储类对象ConfigurationMappedStatementBoundSql操作类对... 查看详情

高并发通过threadpoolexecutor类的源码深度解析线程池执行任务的核心流程(代码片段)

...们通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程,小伙伴们最好是打开IDEA,按照冰河说的步骤,调试下ThreadPoolExecutor类的源码,这样会理解的更加深刻,好了,开始今天的主题。核心逻... 查看详情

通过threadpoolexecutor类解析线程池执行任务的核心流程

摘要:ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态。本文分享自华为云社区《​​【高并发】通过ThreadPoolExecutor类的源码深度解析... 查看详情

深入浅出spring原理及实战「原理分析专题」不看源码就带你剖析ioc容器核心流程以及运作原理

这是史上最全面的Spring的核心流程以及运作原理的分析指南🍃【Spring核心专题】「IOC容器篇」不看繁琐的源码就带你浏览Spring的核心流程以及运作原理🍃【Spring核心专题】「AOP容器篇」不看繁琐的源码就带你浏览Spring的... 查看详情

mybatis源码分析三mybatis的核心对象及其作用(代码片段)

三、MyBatis的核心对象及其作用本文2.3w字,详细介绍了MyBatis的核心对象和作用以及MyBatis运行流程,是如何通过动态代理创建实现类的。文章目录三、MyBatis的核心对象及其作用数据存储类对象ConfigurationMappedStatementBoundSql操... 查看详情