Mybatis系列之SQL的执行流程

在正式说明之前,我们拿最原生的代码,也就是读取指定目录下的config.xml文件的形式,显式创建会话的形式来讲解源码,为什么这么做呢?因为不管是SpringMVC、SpringBoot集成Mybatis还是MgbatisPlus它们都是对Mybatis的一层又一层的封装,但是底层原理不变,比如SqlSession,这个会话对象是一直都存在的,所以我们从原生代码开始,也就是下面的第一段代码


在下面的文章中,说明了一下Mybatis的架构设计,在最后也说明了Mybatis的一个运行流程,所以在这里,对这下面这篇文档的流程来讲解Mybatis系列之架构设计原理_阿小冰的博客-CSDN博客Mybatis系列之架构设计原理https://blog.csdn.net/qq_38377525/article/details/123642061


加载配置并初始化配置信息源码剖析

//1、读取指定目录下的Mybatis主配置文件
Inputstream inputstream = Resources.getResourceAsStream("mybatis-config.xml");
//2、初始化配置文件
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

上述代码中的步骤2,是主要代码,是拿到主配置文件的路径之后,直接初始化,这一步是下面CURD所有操作的前提,我们着重分析一下这个build(),走进源码看一下

SqlSessionFactoryBuilder类中定义了一个build方法的调用

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}

我们点进去看一下具体实现,具体实现也在这个类中,源码如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //mybatis中专门解析mybatis配置文件的一个类
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //调用重载方法,返回值是一个配置Configuration对象
      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.
      }
    }
  }

这里我们需要注意一下,Mybatis在初始化的时候,会自动把Mybatis的配置信息全部加载到内存里,使用Configuration实例来维护,这个类在org.apache.ibatis.session包下,带着大家看一下上述代码最后return的build方法中的入参是怎么分解的

首先,点击进入parse()这个方法,来到XMLConfigBuilder类,这个类是专门处理XML文件的,点击进来之后首先看到的是如下源码,是用来做分析前的一个校验准备的

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //拿到配置文件,获取根节点configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

然后我们进入parseConfiguration方法,也在当前类中,源码如下:

  private void parseConfiguration(XNode root) {
    try {
      //解析settings标签
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //解析properties标签
      propertiesElement(root.evalNode("properties"));
      //加载自定义的VFS实现类
      loadCustomVfs(settings);
      //解析tupeAliases标签
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析plugins标签
      pluginElement(root.evalNode("plugins"));
      //解析objectFactory标签
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析objectWrapperFactory标签
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析reflectionFactory标签
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      //将settings传给Configuration
      settingsElement(settings);
      //解析environments标签
      environmentsElement(root.evalNode("environments"));
      //解析databaseIdProvider标签
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析typeHandlers标签
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

这个源码主要是对主配置文件做标签解析,获取对应的配置值,解析的这些key,在Configuration类中也都存在,为的就是解析之后转换成Configuration对象,在Configuration类中包含一个mappedStatements缓存,主要是保存一个完整的SQL,这里需要讲解一下这个MappedStatement类

在上述代码中,解析的最后一步是对mappers进行解析,这个是我们写sql的xml文件中最外层的一个标签,获取它,主要是为了获取它作用域下的所有标签,其实点进去,它就是再去解析<select>/<update>/<insert>/<delete>等标签,生成一个又一个的MappedStatement对象,看下面的sql定义

<select id="getUserById" resultType="com.cb.entity.User">
     select * from user where id=#{id}
</select>

在Configuration类中,用一个Map来接收这些MappedStatement,Key其实就是namespace+id,value=MappedStatement对象,我们来看一下MappedStatement对象的属性

public final class MappedStatement {

  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
  //以下代码省略...
}

看着这些属性是不是很熟悉?其实对应的就是我们写自定义sql时候各个CURD标签中的属性:resultMap、id等等,然后对我们上面写的<select>进行解析,获取一个MappedStatement对象,初始化到Configuration中

到这里,xml解析完成之后,主配置文件、sql维护的xml都已经解析完毕了,得到了一个最终的Configuration对象,拿到这个对象就要回到上面的build方法重载,上面的SqlSessionFactoryBuilder定义了很多build的重载方法,初始化到这里,就要用到下面的重载方法,完成初始化

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

处理操作请求

其实就是执行SQL,然后讲一下SQL是怎么执行的

最主要的还是SqlSession,它是一个接口类,主要是实现类是DefaultSqlSession,还有一个已经被弃用了,就不做赘述了,SqlSession它是Mybatis用于和数据库交互的最父级类(最顶级类)一般情况下,它会结合ThreadLocal绑定使用,一个会话使用一个SqlSession,会话结束就释放资源close

我们看一下DefaultSqlSession类:

public class DefaultSqlSession implements SqlSession {
  //配置类
  private Configuration configuration;
  //执行器
  private Executor executor;
  //以下代码省略...
}

Configuration类已经没啥可说的了,我们来看执行器Executor:

执行器Executor

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <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 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();

  void setExecutorWrapper(Executor executor);

}

执行器主要的三个实现类:

1、BatchExecutor:重用语句并执行批量更新

2、ReuseExecutor:重用预处理语句-prepared statements

3、SimpleExecutor:默认的普通执行器

然后我们获取sqlSession实例

SqlSession sqlSession = factory.openSession();
List<User> list =sqlSession.selectList("com.cb.mapper.UserMapper.getUserList");

我们看一下openSession()主要做了哪些工作,首先DefaultSqlSessionFactory中有很多openSession的重载方法,但都调用了openSessionFromDataSource方法,只是入参不一样,看一下openSessionFromDataSource源码逻辑

/*
 * execType:执行器类型
 * level:事务隔离级别
 * autoCommit:是否开启事务
 */  
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    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);
      //返回SqlSession实例
      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();
    }
  }

因为上面声明的openSession要执行的是查询列表的操作,所以我们看一下openSession提供的selectList方法的实现逻辑

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //获取到具体的一个MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //调用执行器中的query方法来进行查询,rowBounds用来分页,wrapCollection用来封装参数
      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();
    }
  }

目前SQL到这里是已经拼接为一个可以直接拿到数据库执行的sql语句了,所以接下来我们剖析一下执行器是怎么执行sql的

我们进入executor.query()

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //封装sql
    BoundSql boundSql = ms.getBoundSql(parameter);
    //获取缓存
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //执行查询-需进一步讲解
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

接下来的query()代码,在之前的一篇mybatis的缓存文章中有写到Mybatis系列之一级/二级缓存_阿小冰的博客-CSDN博客中有讲解,这里我们再来一遍:

  @SuppressWarnings("unchecked")
  @Override
  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;
  }




  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);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

我们再对doQuery方法进一步讲解,我们在点击这个方法的时候,找到默认的执行器类-SimpleExecutor,看源码:

  @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、传入参数创建StatementHanlder对象来执行查询
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //2、创建jdbc中的statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      //3、交给StatementHandler来处理逻辑
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

上述代码的步骤3,需要进一步再讲解一下,看他是怎么创建的

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //从连接池获得连接,这个方法最后会调用openConnection方法,可以自己研究一下哈
    Connection connection = getConnection(statementLog);
    //建立连接会话
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

这个逻辑比较长,最后也只是创建了一个StatementHandler对象,然后把入参之类的传给它,它来完成对数据库的查询,最终返回一个List结果集

以上的执行器的代码很关键,所以需要总结一下:

1、执行器根据传递的参数,完成SQL的动态解析,生成一个BoundSql对象,让StatementHandler获取并使用

2、结合一级二级缓存,为查询创建了缓存,提高了查询效率

3、创建JDBC的Statement连接对象,然后传递给StatementHandler对象,它去执行并返回结果

所以,到这里我们就需要讲一下StatementHandler对象的作用

StatementHandler

它主要有两个功能

1、我们在写SQL的时候,会用到#{},这个在Mybatis分析之后会转换为?,举例:select * from user where id = #{id},转换之后是 select * from user where id = ?,StatementHandler会通过paramterize方法对这些占位符?进行参数填充

2、StatementHandler通过List query方法(当然还有其他方法,我上面的例子就是查询集合)来执行Statement,然后将Statement对象返回的resultSet封装成对应的List,最后,通过工厂和反射等机制,获取到我们resultMap指定返回的数据类型,将Statement返回的集合转换为对应的数据类型并返回给客户端

具体代码就不展示了,了解主要机制就可以了

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值