zoukankan      html  css  js  c++  java
  • Mybatis源码解析,一步一步从浅入深(七):执行查询

    一,前言

      我们在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码的最后一步说到执行查询的关键代码:

    result = sqlSession.selectOne(command.getName(), param);

      selelectOne方法有两个参数:

      第一个参数是:com.zcz.learnmybatis.dao.UserDao.findUserById

      第二个参数是:1(Integer类型,就是我们传入的参数id:1,我们是期望通过这个id查询到我们想要的结果)

      因为接下来的代码比较复杂,而且容易迷路。那么我们就定下来本片文章的目的:

      1,sql语句是什么时候,在哪里执行的?

      2,我们传入参数id是怎么参与执行的?

      为了更情绪的分析这两个问题的答案,我将分析的过程分为三步:

      1,在SqlSession中的执行过程

      2,在Excutor中的执行过程

      3,在Statement中的执行过程

    二,在代码的执行过程中分析问题

      1,代码在SqlSession中的执行过程

        在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中,我们已经知道了,使用的sqlSession是DefaultSqlSession,那显而易见,要首先看一下selectOne的源码了:

     1 DefaultSqlSession implements SqlSession
     2 public <T> T selectOne(String statement, Object parameter) {
     3     // Popular vote was to return null on 0 results and throw exception on too many.
     4     // 执行查询
     5     List<T> list = this.<T>selectList(statement, parameter);
     6     if (list.size() == 1) {
     7     //如果返回的list的大小是1,则返回第一个元素
     8       return list.get(0);
     9     } else if (list.size() > 1) {
    10     //如果大于1,则抛出异常
    11       throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    12     } else {
    13     // 如果小于1,则返回null
    14       return null;
    15     }
    16   }

      到这里可以明白一件事情,原来selectOne是调用selectList执行查询的,只不过是取了返回值的第一个元素。  

      我们传入的参数id,就是第5行代码的第二个参数,继续分析第5行的源代码:

    DefaultSqlSession implements SqlSession
    public <E> List<E> selectList(String statement, Object parameter) {
        // 调用了另外一个三个参数的重载方法,
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }

        继续跟踪:

     1  DefaultSqlSession implements SqlSession
     2  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
     3     try {
     4       //从configuration中取出解析userDao-mapping.xml文件是生成的MappedStatement
     5       MappedStatement ms = configuration.getMappedStatement(statement);
     6       // 这里的executor是CachingExecutor
     7       List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
     8       return result;
     9     } catch (Exception e) {
    10       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    11     } finally {
    12       ErrorContext.instance().reset();
    13     }
    14   }

      看源代码的第5行,还记得在文章:Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析中,mapper文件中的每一个select,insert,update,delete标签,都会解析成一个MappedStatement类的实例对象吗?而且这个实例对象是存放在configuration中的。然后执行第7行代码,这里的executor是CachingExecutor实例对象。到这里SqlSession中的代码流程就结束了,我们进入Executor中的执行过程。

      2,代码在在Excutor中的执行过程

        看看源代码:

    1   CachingExecutor implements Executor
    2   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    3     //取出sql语句
    4     BoundSql boundSql = ms.getBoundSql(parameterObject);
    5     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    6     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    7   }

      同样的,查询参数id也是在这个方法的第二个参数,而且在取出sql语句的方法中,对查询参数进行了检查。继续跟踪这个方法中的第6行代码:

     1    CachingExecutor implements Executor
     2    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
     3       throws SQLException {
     4     //使用缓存  
     5     Cache cache = ms.getCache();
     6     if (cache != null) {
     7       flushCacheIfRequired(ms);
     8       if (ms.isUseCache() && resultHandler == null) {
     9         ensureNoOutParams(ms, parameterObject, boundSql);
    10         @SuppressWarnings("unchecked")
    11         List<E> list = (List<E>) tcm.getObject(cache, key);
    12         if (list == null) {
    13           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    14           tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
    15         }
    16         return list;
    17       }
    18     }
    19     
    20     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    21   }

      继续跟踪第20行代码:

     1 BaseExecutor implements Executor
     2   @SuppressWarnings("unchecked")
     3   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     4     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
     5     if (closed) throw new ExecutorException("Executor was closed.");
     6     if (queryStack == 0 && ms.isFlushCacheRequired()) {
     7       clearLocalCache();
     8     }
     9     List<E> list;
    10     try {
    11       queryStack++;
    12       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    13       if (list != null) {
    14         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    15       } else {
    16         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    17       }
    18     } finally {
    19       queryStack--;
    20     }
    21     if (queryStack == 0) {
    22       for (DeferredLoad deferredLoad : deferredLoads) {
    23         deferredLoad.load();
    24       }
    25       deferredLoads.clear(); // issue #601
    26       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    27         clearLocalCache(); // issue #482
    28       }
    29     }
    30     return list;
    31   }

      继续跟踪第16行代码:   

     1  BaseExecutor implements Executor
     2    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     3     List<E> list;
     4     localCache.putObject(key, EXECUTION_PLACEHOLDER);
     5     try {
     6       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
     7     } finally {
     8       localCache.removeObject(key);
     9     }
    10     localCache.putObject(key, list);
    11     if (ms.getStatementType() == StatementType.CALLABLE) {
    12       localOutputParameterCache.putObject(key, parameter);
    13     }
    14     return list;
    15   }

      继续跟踪第6行代码:

     1   SimpleExecutor extends BaseExecutor 
     2    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     3     Statement stmt = null;
     4     try {
     5       Configuration configuration = ms.getConfiguration();
     6       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     7       //准备预处理语句
     8       stmt = prepareStatement(handler, ms.getStatementLog());
     9       return handler.<E>query(stmt, resultHandler);
    10     } finally {
    11       closeStatement(stmt);
    12     }
    13   }

      看到第3行了吗?这里声明了一个Statement,是不是很熟悉?是的,这就是我们在使用原始的JDBC进行查询的时候,用到的,那么我们的问题是不是在这里就有了答案了呢?这里先留一个标记。

      代码执行到这里之后呢,Executor部分的流程就结束了,接下来是在Statement中的执行过程。

      3,代码在Statement中的执行过程

        继续看源码:

     1  //第一段源码
     2   RoutingStatementHandler implements StatementHandler
     3    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     4     return delegate.<E>query(statement, resultHandler);
     5   }
     6   //第二段源码
     7   PreparedStatementHandler extends BaseStatementHandler
     8    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     9     PreparedStatement ps = (PreparedStatement) statement;
    10     ps.execute();
    11     return resultSetHandler.<E> handleResultSets(ps);
    12   }

      在源代码的第10行,是不是也很熟悉?同样也是使用了JDBC进行的查询。似乎这一段的源代码很简单,但其实不是的resultSetHandler中也是做了一部分工作的,这里就不详细描述了。代码到这里就结束了,似乎我们没有看到文章开头的两个问题的答案。看来应该就是在上面标记的地方了。

      4,问题的答案

        关键代码:stmt = prepareStatement(handler, ms.getStatementLog());

        prepareStatement源码:

     1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
     2     Statement stmt;
     3     //从事务中获取数据库连接
     4     Connection connection = getConnection(statementLog);
     5     // 获取Statement
     6     stmt = handler.prepare(connection);
     7     // 为Statement设置查询参数
     8     handler.parameterize(stmt);
     9     return stmt;
    10   }
     1 public Statement prepare(Connection connection) throws SQLException {
     2     ErrorContext.instance().sql(boundSql.getSql());
     3     Statement statement = null;
     4     try {
     5       //初始化Statement
     6       statement = instantiateStatement(connection);
     7       //设置查询超时时间
     8       setStatementTimeout(statement);
     9       setFetchSize(statement);
    10       return statement;
    11     } catch (SQLException e) {
    12       closeStatement(statement);
    13       throw e;
    14     } catch (Exception e) {
    15       closeStatement(statement);
    16       throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    17     }
    18   }
     1 protected Statement instantiateStatement(Connection connection) throws SQLException {
     2     String sql = boundSql.getSql();
     3     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
     4       String[] keyColumnNames = mappedStatement.getKeyColumns();
     5       if (keyColumnNames == null) {
     6         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
     7       } else {
     8         return connection.prepareStatement(sql, keyColumnNames);
     9       }
    10     } else if (mappedStatement.getResultSetType() != null) {
    11       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    12     } else {
    13         //从数据库连接中获取statement
    14       return connection.prepareStatement(sql);
    15     }
    16   }

      看到第14行,哈哈这里就是我们要的答案了。这样一来我们的两个问题,就都有答案了。

      因为执行查询的这个过程比较复杂,如果真的要详细的全部解释清楚的话,估计还得10几篇文章要写。鉴于作者能力和时间,就大概的展示一下这个过程吧。


     远程不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9712446.html 

  • 相关阅读:
    可扩展设计的三个维度
    今天用批处理脚本遇到的两个问题
    响应式编程学习记录
    ThreadLocal使用注意
    JDK8 函数式接口
    Java多线程相关的常用接口
    java异步编程
    java多线程同步器
    paramiko获取远程主机的环境变量
    python为不同的对象如何分配内存的小知识
  • 原文地址:https://www.cnblogs.com/zhangchengzi/p/9712446.html
Copyright © 2011-2022 走看看