Mybatis源碼解析,一步一步從淺入深(七):執行查詢

一,前言html

  咱們在文章:Mybatis源碼解析,一步一步從淺入深(二):按步驟解析源碼的最後一步說到執行查詢的關鍵代碼:sql

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

  selelectOne方法有兩個參數:數據庫

  第一個參數是:com.zcz.learnmybatis.dao.UserDao.findUserById緩存

  第二個參數是:1(Integer類型,就是咱們傳入的參數id:1,咱們是指望經過這個id查詢到咱們想要的結果)mybatis

  由於接下來的代碼比較複雜,並且容易迷路。那麼咱們就定下來本片文章的目的:app

  1,sql語句是何時,在哪裏執行的?post

  2,咱們傳入參數id是怎麼參與執行的?ui

  爲了更情緒的分析這兩個問題的答案,我將分析的過程分爲三步:this

  1,在SqlSession中的執行過程spa

  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幾篇文章要寫。鑑於做者能力和時間,就大概的展現一下這個過程吧。

 


 遠程不易,轉載請聲明出處:http://www.javashuo.com/article/p-fpdkspdt-dr.html 

相關文章
相關標籤/搜索