Contents

SSM系列(五)---Mybatis SQL 执行流程分析

本文主要通过源码详细分析了 Mybatis 框架中 SQL 语句的执行流程,包括加载解析核心配置文件,创建SqlSessionFactory对象,创建SqlSession对象,执行 SQL 操作。

更多文章欢迎访问我的个人博客–>幻境云图

1. Mybatis工作流程

1.1 概述

1.读取mybatis全局配置文件:将定义好的mybatis全局配置文件进行读取,并包装成为一个InputStream对象 2.解析配置文件:由SqlSessionFactoryBuilder类的bulid方法驱动,对包装好的XML文件进行解析。很容易看到,其具体的解析任务是交给XMLConfigBuilder对象完成. 3.创建SqlSessionFactory对象 4.创建SqlSession的对象 5.执行SQL操作

Mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器BaseExecutor、一个是缓存执行器CachingExecutor。 Mybatis底层封装了 Mapped Statement对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过 Mapped Statement在执行sql前将输入的java对象映射至sql中, 输入参数映射就是jdbc编程中对preparedStatement设置参数。 Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过 Mapped Statement在执行sql后将输出结果映射至java对象中, 输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

1.2 实例代码

    @Test
    public void testMybaits() throws IOException {
        // 1. mybatis核心配置文件 以流的形式加载进来
        String resources = "mybatis-config.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resources);
        // 2. 解析配置文件 根据配置文件创建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 3. 用SqlSessionFactory创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
         // 直接执行SQL操作或者获取mapper对象都在操作
         User user = sqlSession.selectOne("com.illusory.i.shiro.mapper.UserMapper.findUserByName", "张三");
         System.out.println(user);
        // 4. SqlSession获取mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 5. 执行CRUD操作
          User userByName = mapper.findUserByName("username");
    }

2.原理分析

2.1 读取mybatis全局配置文件

将定义好的mybatis全局配置文件进行读取,并包装称为一个InputStream对象。

        // 1. mybatis核心配置文件 以流的形式加载进来
        String resources = "mybatis-config.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resources);

Resources.class是 Mybatis 提供的一个加载资源文件的工具类。

  • getResourceAsStream(String resource)
//Resources类
/*
   * Returns a resource on the classpath as a Stream object
   *
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
  }
  • getResourceAsStream()
  /*
   * Returns a resource on the classpath as a Stream object
   *
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
      throw new IOException("Could not find resource " + resource);
    }
    return in;
  }

获取到自身的 ClassLoader 对象,然后交给 ClassLoade r(lang包下的)来加载:

  • getResourceAsStream()
//ClassLoaderWrapper 
/*
   * Get a resource from the classpath, starting with a specific class loader
   *
   * @param resource    - the resource to find
   * @param classLoader - the first class loader to try
   * @return the stream or null
   */
  public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    return getResourceAsStream(resource, getClassLoaders(classLoader));
  }
  
    /*
     * Try to get a resource from a group of classloaders
     *
     * @param resource    - the resource to get
     * @param classLoader - the classloaders to examine
     * @return the resource or null
     */
    InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
      for (ClassLoader cl : classLoader) {
        if (null != cl) {
  
          // try to find the resource as passed
          InputStream returnValue = cl.getResourceAsStream(resource);
  
          // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
          if (null == returnValue) {
            returnValue = cl.getResourceAsStream("/" + resource);
          }
  
          if (null != returnValue) {
            return returnValue;
          }
        }
      }
      return null;
    }

值的注意的是,它返回了一个InputStream对象。

2.2 解析配置文件

由SqlSessionFactoryBuilder类的bulid方法驱动,对包装好的XML文件进行解析。很容易看到,其具体的解析任务是交给XMLConfigBuilder对象完成.

  • SqlSessionFactory.build()
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  • SqlSessionFactoryBuilder.build()
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

首先通过 Document 对象来解析,然后返回 InputStream 对象,然后交给 XMLConfigBuilder 构造成org.apache.ibatis.session.Configuration 对象,

2.3 创建方法构造成SqlSessionFactory对象

将前面解析配置文件构造出来的Configuration对象交给SqlSessionFactoryBuilder.build()方法构造成SqlSessionFactory。

  • build方法如下:
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

最终返回的是DefaultSqlSessionFactory对象

2.4 创建SqlSession

SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。

        // 3. 用SqlSessionFactory创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
  • DefaultSqlSessionFactory.openSession()
  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

最终也是返回的一个DefaultSqlSession对象。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //根据Configuration的Environment属性来创建事务工厂
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //通过事务工厂创建事务,默认level=null autoCommit=false
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建执行器 真正执行sql语句的对象
      final Executor executor = configuration.newExecutor(tx, execType);
      //根据执行器返回对象 SqlSess
      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();
    }
  }

构建步骤:

Environment–>TransactionFactory+autoCommit+tx-level–>Transaction+ExecType–>Executor+Configuration+autoCommit–>SqlSession

其中,Environment是Configuration中的属性。

2.5 执行SQL操作

SQL语句的执行才是MyBatis的重要职责,该过程就是通过封装JDBC进行操作,然后使用Java反射技术完成JavaBean对象到数据库参数之间的相互转换, 这种映射关系就是有TypeHandler对象来完成的,在获取数据表对应的元数据时,会保存该表所有列的数据库类型,大致逻辑如下所示:

  User user = sqlSession.selectOne("com.illusory.i.shiro.mapper.UserMapper.findUserByName", "张三");
        System.out.println(user);

调用selectOne方法进行SQL查询,selectOne方法最后调用的是selectList,在selectList中,会查询 configuration中存储的MappedStatement对象,mapper文件中一个sql语句的配置对应一个MappedStatement对象,然后调用执行器进行查询操作。

  • DefaultSqlSession.selectOne();
  @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.<T>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;
    }
  }
  • DefaultSqlSession.selectList();
  @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) {
    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();
    }
  }

执行器在query操作中,优先会查询缓存是否命中,命中则直接返回,否则从数据库中查询。

  • CachingExecutor.query()
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
   @Override
   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
       throws SQLException {
     Cache cache = ms.getCache();
     if (cache != null) {
       flushCacheIfRequired(ms);
       if (ms.isUseCache() && resultHandler == null) {
         ensureNoOutParams(ms, boundSql);
         @SuppressWarnings("unchecked")
         List<E> list = (List<E>) tcm.getObject(cache, key);
         if (list == null) {
           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
           tcm.putObject(cache, key, list); // issue #578 and #116
         }
         return list;
       }
     }
     //BaseExecutor.query()
     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
   }
  • BaseExecutor.query()
 @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;
  }
  • BaseExecutor.queryFromDatabase()
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
        /**
         * 先往localCache中插入一个占位对象,这个地方
         */
    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;
  }

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

最后的doQuery由SimpleExecutor代理来完成,该方法中有2个子流程,一个是SQL参数的设置,另一个是SQL查询操作和结果集的封装。

  • SimpleExecutor.doQuery()方法如下:
  @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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
       /* 子流程1: SQL查询参数的设置 */
      stmt = prepareStatement(handler, ms.getStatementLog());
         /* 子流程2: SQL查询操作和结果集封装 */
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

子流程1 SQL查询参数的设置

首先获取数据库connection连接,然后准备statement,然后就设置SQL查询中的参数值。打开一个connection连接,在使用完后不会close, 而是存储下来,当下次需要打开连接时就直接返回。

// SimpleExecutor类
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    /* 获取Connection连接 */
    Connection connection = getConnection(statementLog);

    /* 准备Statement */
    stmt = handler.prepare(connection, transaction.getTimeout());

    /* 设置SQL查询中的参数值 */
    handler.parameterize(stmt);
    return stmt;
}

子流程2 SQL查询结果集的封装

// SimpleExecutor类
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行查询操作
    ps.execute();
    // 执行结果集封装
    return resultSetHandler.<E> handleResultSets(ps);
}

// DefaultReseltSetHandler类
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    /**
     * 获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等。
     * 这些信息都存储在了ResultSetWrapper中了
     */
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

ResultSetWrapper 是 ResultSet 的包装类,调用 getFirstResultSet 方法获取第一个 ResultSet,同时获取数据库的 MetaData 数据, 包括数据表列名、列的类型、类序号等,这些信息都存储在 ResultSetWrapper 类中了。然后调用handleResultSet 方法来来进行结果集的封装。

// DefaultResultSetHandler类
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        if (parentMapping != null) {
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
            if (resultHandler == null) {
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                multipleResults.add(defaultResultHandler.getResultList());
            } else {
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } finally {
        // issue #228 (close resultsets)
        closeResultSet(rsw.getResultSet());
    }
}

这里调用handleRowValues方法进行结果值的设置

// DefaultResultSetHandler类
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
        ensureNoRowBounds();
        checkResultHandler();
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        // 封装数据
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
        throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
        Object rowValue = getRowValue(rsw, discriminatedResultMap);
        storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
}

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // createResultObject为新创建的对象,数据表对应的类
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            // 这里把数据填充进去,metaObject中包含了resultObject信息
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
        }
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
    }
    return rowValue;
}

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (autoMapping.size() > 0) {
        // 这里进行for循环调用,因为user表中总共有7列,所以也就调用7次
        for (UnMappedColumnAutoMapping mapping : autoMapping) {
            // 这里将esultSet中查询结果转换为对应的实际类型
            final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
            if (value != null) {
                foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
                // gcode issue #377, call setter on nulls (value is not 'found')
                metaObject.setValue(mapping.property, value);
            }
        }
    }
    return foundValues;
}

mapping.typeHandler.getResult会获取查询结果值的实际类型,比如我们user表中id字段为int类型,那么它就对应Java中的Integer类型, 然后通过调用statement.getInt(“id”)来获取其int值,其类型为Integer。metaObject.setValue方法会把获取到的Integer值设置到Java类中的对应字段。

// MetaObject类
public void setValue(String name, Object value) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            if (value == null && prop.getChildren() != null) {
                // don't instantiate child path if value is null
                return;
            } else {
                metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
            }
        }
        metaValue.setValue(prop.getChildren(), value);
    } else {
        objectWrapper.set(prop, value);
    }
}

metaValue.setValue方法最后会调用到Java类中对应数据域的set方法,这样也就完成了SQL查询结果集的Java类封装过程

3. MyBatis缓存

MyBatis提供查询缓存,用于减轻数据库压力,提高性能。MyBatis提供了一级缓存和二级缓存。

3.1 一级缓存

一级缓存是 SqlSession 级别的缓存,每个 SqlSession 对象都有一个哈希表用于缓存数据,不同 SqlSession 对象之间缓存不共享。 同一个 SqlSession 对象对象执行2遍相同的 SQL 查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库查询了, 直接返回缓存结果即可。MyBatis 默认开启一级缓存的。 简单说就是SQL语句作为key,查询结果作为value,根据key去查找value,如果查询语句相同就能直接返回value。

3.2 二级缓存

二级缓存是 mapper 级别的缓存,二级缓存是跨 SqlSession 的,多个 SqlSession 对象可以共享同一个二级缓存。不同的 SqlSession 对象执行两次相同的 SQL 语句, 第一次会将查询结果进行缓存,第二次查询直接返回二级缓存中的结果即可。MyBatis 默认不开启二级缓存的,可以在配置文件中使用如下配置来开启二级缓存:

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

​ 当SQL语句进行更新操作(删除/添加/更新)时,会清空对应的缓存,保证缓存中存储的都是最新的数据。

4. 参考

https://www.cnblogs.com/dongying/p/4142476.html

http://www.mybatis.org/mybatis-3/zh/getting-started.html