深入理解MyBatis——查询流程

MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路,然后探究MyBatis的是如何实现查询的。

MyBatis执行流程

利用MyBatis实现一次查询

        InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");  
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();  
        //创建SqlSessionFactory
        SqlSessionFactory factory = builder.build(inputStream);  

        //从SqlSession工厂 SqlSessionFactory中创建一个SqlSession,进行数据库操作  
        SqlSession sqlSession = factory.openSession();  

        //使用SqlSession查询  
        Map<String,Object> params = new HashMap<String,Object>();  

        params.put("xxx",xxx);  
        //执行查询
        List<Object> result = sqlSession.selectList("mapper.sqlId"params);
  1. SqlSession sqlSession = factory.openSession():开启一个sqlSession,它表示和数据库交互的会话,完成必要数据库增删改查功能。
  2. 执行sqlSession.selectList:返回查询结果List,Mybatis和数据库进行交互的流程都在此方法中。

那么探究MyBtis就由此进入吧!

selectList(String statement, Object parameter)方法源码:

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

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    //根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement  
      MappedStatement ms = configuration.getMappedStatement(statement);
      //查找好以后交给executor.query方法执行下面的流程
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >  
  select   
    *  
    from table   
</select>  

说明:根据Statement获得StatementId(如上面配置文件的Id为selectByMinSalary),通过StatementId找出配置文件mapper.xml中对应的MappedStatement,并且将其交给executor的query方法来执行。

Executor的实现类BaseExecutor.query方法源码

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //动态地生成需要执行的SQL语句,用BoundSql对象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    //设置一级缓存的key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  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();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

//queryFromDatabase方法
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;
  }


//SimpleExecutor(BaseExecutor子类)类的doQuery()
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
    //获得配置对象configuration
      Configuration configuration = ms.getConfiguration();
      //创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //StatementHandler根据configuration创建Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //交给StatementHandler.query执行
      //成对数据库的查询,最终返回List结果集。
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

说明:Executor的功能和作用是
1.创建BoundSql;
2.创建缓存
3.将BoundSql传递给StatementHandler,由StatementHandler来完成对数据库的查询,以及返回List。

StatementHandler

接着我们进入prepareStatement方法设置参数:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    //对statement设置参数,就是对?占位符赋值
    handler.parameterize(stmt);
    return stmt;
  }

进入parameterize方法:

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

此时我们知道,是通过使用ParameterHandler对象来完成对Statement的设值 ,将一个一个参数填充到”?”中。

此时Statement对象构建完成了,是一个可用的SQL语句了。

StatementHandler.query()方法:

//PreParedStatementHandler类来实现query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//将statement转为PreparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //通过使用ResultHandler来处理ResultSet来返回List
    return resultSetHandler.<E> handleResultSets(ps);
  }

说明:StatementHandler通过ResultHandler的handleResultSets方法,实现将ResultSet结果集转换成List 结果集。

ResultHandler

ResultHandler.handleResultSets用来将ResultSet结果集转换成List

源码如下

  //
  // HANDLE RESULT SETS
  //DefaultResultSetHandler的handleResultSets方法

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
  //这里是List
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    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.getResulSets();
    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);
  }

总结

1.开启一个sqlSession

2.sqlSession根据StatementId得到一个MapperStatement,并交给Executor来执行查询

3.Executor得到MapperStatement后,会生成一个boundsql,并且创建一个代表缓存的cacheKey,如果对于缓存中有数据,则直接返回;如果缓存中没有,就需要boundsql传递且借助StatementHandler去数据库中获得数据,并返回结果集List,之后在加入缓存。

4.StatementHandle**r获得boundsql以后,借助**ParameterHandler 来讲参数设置进Statement并返回一个PreparedStatement对象,此时执行查找返回结果集ResultSet。StatementHandler此时再借助ResultHandler.handleResultSets用来将ResultSet结果集转换成List 。

借用大神的一张图来概括上述流程:

这里写图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章