mybatis源码阅读之--整体执行流程

autumnlight      2022-05-07     485

关键词:

Mybatis执行流程分析

Mybatis执行SQL语句可以使用两种方式:

  1. 使用SqlSession执行update/delete/insert/select操作
  2. 使用SqlSession获得对应的Mapper,然后调用mapper的相应方法执行语句

其中第二种方式获取Mapper的流程在前面已经解析过,请查看文章Mybatis源码阅读之--Mapper执行流程
其实这个方法最后的MapperMthod也是调用SqlSession的相应方法执行增删该的操作,这边文章主要介绍SqlSession执行Sql的流程。

此处以最为复杂的select举例说明--SqlSession的默认实现类为DefaultSqlSession:

  1. selectOne 处理返回单条数据的逻辑
// DefaultSqlSession类中
  public <T> T selectOne(String statement, Object parameter) {
    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;
    }
  }

调用selectList()返回一个List,如果返回的数据大于一条,那么抛出ToooManyResultsException,否则,如果List的大小为1,返回第一个数据,否则返回null
2. selectList 处理返回多条数据的逻辑

// DefaultSqlSession类中
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

我们看到,先获得String类型指定的statemnet获得MappedStatement ms,然后调用executer.query方法处理sql语句
Executor有多个实现,SimpleExecutor、BatchExecutor、CachingExecutor,其中BatchExecutor是执行批处理操作的,而CachingExecetor对Executor进行了一层包装完成二级缓存的功能(详见文章Mybatis源码阅读之--二级缓存实现原理分析)。

这里我们主要分析SimpleExecutor

public class SimpleExecutor extends BaseExecutor {
    
}

SimpleExecutor继承自BaseExecutor,后者实现了query方法

// SimpleExecutor
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 获得缓存键
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

创建CacheKey,然后根据CacheKey执行查询操作

// BaseExecutor类
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 由于嵌套查询,这里会查询多次
    // 第一个查询,并且当前的语句需要刷新缓存,则进行缓存的刷新
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) { // 缓存中拿到了,处理输出参数
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else { // 缓存中没有拿到,则从数据库中拿
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) { // 最外层的查询已经结束
      // 所有非延迟的嵌套查询也已经查完了,那么就可以把嵌套查询的结果放入到需要的对象中
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

其中有一些一级缓存的处理,这里不再详细分析,欲了解详情,请参考文章Mybatis源码阅读之--本地(一级)缓存实现原理分析

这里就从queryFromDatabase方法进行分析,一级缓存没有拿到数据会执行从数据库中获取数据的操作。

// BaseExecutor类
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 这里为什么要先占位呢?
    // 回答:嵌套的延迟加载有可能用的是同一个对象,这里说明已经开始查了,
    // 但是由于处理嵌套的查询,此查询还没有查完,再次执行嵌套查询,且查询的是相同的东西,那么就不用再查了
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 删除占位
      localCache.removeObject(key);
    }
    // 将数据放入到缓存中
    localCache.putObject(key, list);
    // 对于callable的statement来说,出参也需要缓存,而出参也是放在了入参中
    // 因此这里缓存了入参
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

此方法去除一级缓存的处理之后就一句话

list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

可以发现BaseExecutor并没有实现doQuery方法,而是将doQuery交给具体的子类进行实现(SimpleExecutor和BatchExecutor对于doQuery的方法实现有所不同),
这里使用了模板方法模式,BaseExecutor只对一级缓存进行处理,具体从数据库中查询数据的操作交给子类去处理。

目光再次回到SimpleExecutor的doQuery方法

// SimpleExecutor
  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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

此方法的主要过程如下:

  1. 创建一个StatementHandler
  2. 创建Statement对象
  3. 调用StatementHandler的query方法完成查询的操作

先看第一步,如何创建一个StatementHander

// Configuration类
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

创建了一个RoutingStatementHandler,并将插件应用到statementHandler上。此处设计到Mybatis插件的实现原理,不再赘述,详情参见Mybatis源码阅读之--插件实现原理分析

看下RoutingStatementHandler的创建过程

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
}

这里又一次使用到了装饰者模式,根据Statement的类型不同,创建不同的StatementHandler,如果此语句类型为statement,则创建一个SimpleStatementHandler,若是一个prepared,则创建一个PreparedStatementHandler,若是Callable,则创建一个CallableStatementHandler。

由于篇幅问题,这里只分析用到最多的PreparedStatementHandler。

而RoutingStatementHandler的其他方法都是调用了delegate的相应方法。

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

我们再次回到SimpleExecutor中,创建完了StatementHandler之后,进行Statement的创建。

// SimpleExecutor
  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;
  }

首先获得一个连接,然后调用StatementHandler.prepare方法创建Statement

// BaseSatementHandler
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 初始化(创建)statement
      statement = instantiateStatement(connection);
      // 设置语句执行超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 设置返回数据的大小
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

该方法先调用子类的instantiateStatement方法,完成Statement的初始化
然后在设置statement的超时时间以及fetchSize。依然是模板方法模式。

// PreparedStatementHandler
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();

    // 创建statement需要四个参数:
    // 1. sql
    // 2. resultSetType 参见ResultSetType枚举
    // 3. columnNames
    // 4. autoGeneratedKeys
    // 5. concurrencyLevel
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

通过connection创建PreparedStatement。

至此Statement的创建完成。

再次回到SimpleExecutor中,进行参数的设置:

// SimpleExecutor
  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;
  }

使用StatementHandler进行参数的初始化

// PreparedStatementHandler
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

这里调用了ParameterHandler.setParameters方法进行初始化参数,ParameterHandler的默认实现类为DefaultParameterHandler

而ParameterHandler是在StatementHanler创建的时候进行创建的

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // ....其他操作
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
// Configuration类
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

使用LanguageDriver创建ParameterHandler,然后处理插件。

DefaultParameter处理参数的代码就不解释了,有兴趣自己可以看一看。

思绪拉回到SimpleExecutor的doQuery方法:

  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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

目前已经分析到最后一步,handler.query()。

// PreparedStatementHandler
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

执行ps.execute()的方法进行数据的查询,然后调用resultSetHandler.handleResultStes方法处理结果集,将结果集转换为List,并返回。

至此,整体流程已经分析完毕。

总结:
Mybatis整体操作流程主要为以下几点:

  1. sqlSessiong.query
  2. executor.query
  3. statementHandler.prepareStatement准备statement
  4. parameterHanler.setParameter设置参数
  5. resultSetHandler.handleResultSets处理结果集

最后来一张时序图来有个直观的感受

技术图片

mybatis源码-解析配置文件之解析的流程

1.简介在之前的文章《mybatis初步使用(IDEA的Maven项目,超详细)》中,讲解了mybatis的初步使用,并总结了以下mybatis的执行流程:通过Resources工具类读取mybatis-config.xml,存入Reader;SqlSessionFactoryBuilder使用上一步获得的reader创建SqlSes... 查看详情

okhttp完全解析之整体调用流程

...码很多巧妙的实现,非常值得学习。建议下载一个OkHttp的源码,使用Idea或者SourceInsight阅读,跟随本文一起阅读。OKHttp的全局概览:分析一个复杂的框架,可以先分析简单的调用流程然后分析全局的类之间的关系OkHttp的 查看详情

mybatis系统性详解(学习笔记)(代码片段)

目录mybatis知识传统JDBC不足mybatis基础mybatis核心应用配置与原理解析mybatis核心概念整体认识mybatis源码包mybatis基本流程类调用mybatis流程记录mybatis处理流程图mybatis*之sessionmybatis之mappermybatis之sqlmybatis之executormybatis之Cache一级缓存二... 查看详情

mybatis源码阅读之--本地(一级)缓存实现原理分析

前言:Mybatis为了提升性能,内置了本地缓存(也可以称之为一级缓存),在mybatis-config.xml中可以设置localCacheScope中可以配置本地缓存的作用域,包含两个值session和statement,其中session选项表示本地缓存在整个session都有效,而state... 查看详情

mybatis之执行流程(代码片段)

目录一、使用二、实现原理原生的jdbc执行步骤组件ConfigurationSqlSessionFactorySqlSessionExecutorStatementHandlerParameterHandlerResultSetHandlerTypeHandlerMappedStatementSqlSourceBoundSql三、具体流程1、生成sqlSessionFactory1.1、解析配置文 查看详情

mybatis源码解析之执行sql语句

作者:郑志杰mybatis操作数据库的过程//第一步:读取mybatis-config.xml配置文件InputStreaminputStream=Resources.getResourceAsStream("mybatis-config.xml");//第二步:构建SqlSessionFactory(框架初始化)SqlSessionFactorysqlSessionFactory=newSqlS 查看详情

mybatis底层源码分析-mybatis执行流程

1MyBatis流程图2MyBatis源码分析2.1Configuration.xml该配置文件是MyBatis的全局配置文件,在这个文件中可以配置诸多项目。常用的内容是别名设置,拦截器设置等。2.1.1Properties(属性)将数据库连接参数单独配置在db.properties中,放在类... 查看详情

手把手带你阅读mybatis源码执行篇

前言上一篇文章提到了MyBatis是如何构建配置类的,也说了MyBatis在运行过程中主要分为两个阶段,第一是构建,第二就是执行,所以这篇文章会带大家来了解一下MyBatis是如何从构建完毕,到执行我们的第一条SQL语句的。之后这部... 查看详情

面试必问|聊聊mybatis执行流程?(代码片段)

...,越来越多的公司摒弃了Hibernate,而选择拥抱了MyBatis。而且,很多大厂在面试的时候喜欢问MyBatis底层的原理和源码实现。总之,MyBatis几乎成为了Java开发人员必须深入掌握的框架技术,今天,我们就一起... 查看详情

吃透mybatis源码-mybatis执行流程

...星好评⭐⭐⭐⭐⭐感谢。前言上一篇文章我们分析了一下Mybatis的 查看详情

手把手带你阅读mybatis源码缓存篇

前言大家好,这一篇文章是MyBatis系列的最后一篇文章,前面两篇文章:手把手带你阅读Mybatis源码(一)构造篇 和 手把手带你阅读Mybatis源码(二)执行篇,主要说明了MyBatis是如何将我们的xml配置文件构建为其内部的Confi... 查看详情

freeswitch源码阅读之sofia模块

  sofia模块在freeswitch中的位置非常重要,所有的sip通话都和它有关,那么我们就看一下该模块的执行流程。一、实现的功能:1.sip注册;2.呼叫;3.Presence;4.SLA,等。 二、主要的方法,有三个,分别为: [cpp] viewplain&n... 查看详情

mybatis源码解析mybatis执行sql的流程分析(代码片段)

本章着重介绍MyBatis执行Sql的流程,关于在执行过程中缓存、动态SQl生成等细节不在本章中体现还是以之前的查询作为例子:publicclassApppublicstaticvoidmain(String[]args)Stringresource="mybatis-config.xml";Readerreader;try//将XML配置... 查看详情

flink整体执行流程(代码片段)

以Flink源码中自带的WordCount为例,执行的入口从用户程序的execute()函数入手,execute()的源码如下:1publicJobExecutionResultexecute(StringjobName)throwsException2StreamGraphstreamGraph=getStreamGraph();3streamGraph.setJobName(jobName) 查看详情

mybatis的架构原理与6大核心流程

深入详解Mybatis的架构原理与6大核心流程注:本文转自:mikechen:MyBatis是Java生态中非常著名的一款ORM框架,目前在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。 如果你想要进入一线大厂,能够熟练使用MyBatis... 查看详情

polardb-x源码解读系列:dml之insertignore流程(代码片段)

在上一篇源码阅读中,我们介绍了INSERT的执行流程。而INSERTIGNORE与INSERT不同,需要对插入值判断是否有UniqueKey的冲突,并忽略有冲突的插入值。因此本文将进一步介绍PolarDB-X中INSERTIGNORE的执行流程,其根据插入的... 查看详情

读zepto源码之代码结构

虽然最近工作中没有怎么用zepto,但是据说zepto的源码比较简单,而且网上的资料也比较多,所以我就挑了zepto下手,希望能为以后阅读其他框架的源码打下基础吧。源码版本本文阅读的源码为 zepto1.2.0阅读zepto之前需要了解jav... 查看详情

读zepto源码之代码结构

虽然最近工作中没有怎么用zepto,但是据说zepto的源码比较简单,而且网上的资料也比较多,所以我就挑了zepto下手,希望能为以后阅读其他框架的源码打下基础吧。源码版本本文阅读的源码为 zepto1.2.0阅读zepto之前需要了解jav... 查看详情