範例程式碼
之前的文章說過,對於MyBatis來說insert、update、delete是一組的,因為對於MyBatis來說它們都是update;select是一組的,因為對MyBatis來說它就是select。
本文研究一下select的實作流程,範例程式碼為:
1 public void testSelectOne() { 2 System.out.println(mailDao.selectMailById(8)); 3 }
selectMailById方法的實作為:
1 public Mail selectMailById(long id) {2 SqlSession ss = ssf.openSession();3 try {4 return ss.selectOne(NAME_SPACE + "selectMailById", id);5 } finally {6 ss.close();7 }8 }
我們知道MyBatis提供的select有selectList和selectOne兩個方法,但本文只分析且只需要分析selectOne方法,原因後面說。
selectOne方法流程
先看SqlSession的selectOne方法流程,方法位於DefaultSqlSession中:
1 public <T> T selectOne(String statement, Object parameter) { 2 // Popular vote was to return null on 0 results and throw exception on too many. 3 List<T> list = this.<T>selectList(statement, parameter); 4 if (list.size() == 1) { 5 return list.get(0); 6 } else if (list.size() > 1) { 7 throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); 8 } else { 9 return null;10 }11 }
這裡就是為什麼我說selectOne與selectList兩個方法只需要分析selectList方法就可以了的原因,因為在MyBatis中所有selectOne操作最後都會轉換為selectList運算,無非就是操作完畢之後判斷一下結果集的個數,如果結果集數超過一個就報錯。
接著看下第3行的selectList的程式碼實現,方法同樣位於DefaultSqlSession:
1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 2 try { 3 MappedStatement ms = configuration.getMappedStatement(statement); 4 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 5 } catch (Exception e) { 6 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); 7 } finally { 8 ErrorContext.instance().reset(); 9 }10 }
#第3行取得MappedStatement就不說了,跟一下第4行Executor的query方法實現,這裡使用了一個裝飾器模式,給SimpleExecutor加上了快取功能,程式碼位於CachingExecutor中:
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {2 BoundSql boundSql = ms.getBoundSql(parameterObject);3 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);4 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);5 }
第2行的程式碼取得BoundSql,BoundSql中的內容上文已經說過了,最後也會有總結。
第3行的程式碼根據輸入參數建立快取Key。
第4行的程式碼執行查詢操作,看下程式碼實現,程式碼同樣位於CachingExecutor:
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2 throws SQLException { 3 Cache cache = ms.getCache(); 4 if (cache != null) { 5 flushCacheIfRequired(ms); 6 if (ms.isUseCache() && resultHandler == null) { 7 ensureNoOutParams(ms, parameterObject, boundSql); 8 @SuppressWarnings("unchecked") 9 List<E> list = (List<E>) tcm.getObject(cache, key);10 if (list == null) {11 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);12 tcm.putObject(cache, key, list); // issue #578 and #11613 }14 return list;15 }16 }17 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);18 }
#這裡並沒有配置且引用Cache,因此不執行第4行的判斷,執行第17行的程式碼,程式碼位於SimpleExecutor的父類別BaseExecutor中,原始碼實作為:
1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 3 if (closed) { 4 throw new ExecutorException("Executor was closed."); 5 } 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 // issue #60126 deferredLoads.clear();27 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {28 // issue #48229 clearLocalCache();30 }31 }32 return list;33 }
這裡執行第16行的程式碼,queryFromDatabase方法實作為:
1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 List<E> list; 3 localCache.putObject(key, EXECUTION_PLACEHOLDER); 4 try { 5 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); 6 } finally { 7 localCache.removeObject(key); 8 } 9 localCache.putObject(key, list);10 if (ms.getStatementType() == StatementType.CALLABLE) {11 localOutputParameterCache.putObject(key, parameter);12 }13 return list;14 }
程式碼走到第5行,最後執行duQuery方法,方法的實作為:
1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 2 Statement stmt = null; 3 try { 4 Configuration configuration = ms.getConfiguration(); 5 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 6 stmt = prepareStatement(handler, ms.getStatementLog()); 7 return handler.<E>query(stmt, resultHandler); 8 } finally { 9 closeStatement(stmt);10 }11 }
看到第4行~第6行的程式碼都跟前文update是一樣的,就不說了,handler有印象的朋友應該記得是PreparedStatementHandler,下一部分就分析一下和update的區別,PreparedStatementHandler的query方法是如何實現的。
PreparedStatementHandler的query方法實作
跟PreparedStatementHandler的query方法跟到底,其最終實現為:
1 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {2 PreparedStatement ps = (PreparedStatement) statement;3 ps.execute();4 return resultSetHandler.<E> handleResultSets(ps);5 }
看到第3行執行查詢操作,第4行的程式碼處理結果集,將結果集轉換為List,handleResultSets方法實作為:
1 public List<Object> handleResultSets(Statement stmt) throws SQLException { 2 ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); 3 4 final List<Object> multipleResults = new ArrayList<Object>(); 5 6 int resultSetCount = 0; 7 ResultSetWrapper rsw = getFirstResultSet(stmt); 8 9 List<ResultMap> resultMaps = mappedStatement.getResultMaps();10 int resultMapCount = resultMaps.size();11 validateResultMapsCount(rsw, resultMapCount);12 while (rsw != null && resultMapCount > resultSetCount) {13 ResultMap resultMap = resultMaps.get(resultSetCount);14 handleResultSet(rsw, resultMap, multipleResults, null);15 rsw = getNextResultSet(stmt);16 cleanUpAfterHandlingResultSet();17 resultSetCount++;18 }19 20 String[] resultSets = mappedStatement.getResultSets();21 if (resultSets != null) {22 while (rsw != null && resultSetCount < resultSets.length) {23 ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);24 if (parentMapping != null) {25 String nestedResultMapId = parentMapping.getNestedResultMapId();26 ResultMap resultMap = configuration.getResultMap(nestedResultMapId);27 handleResultSet(rsw, resultMap, null, parentMapping);28 }29 rsw = getNextResultSet(stmt);30 cleanUpAfterHandlingResultSet();31 resultSetCount++;32 }33 }34 35 return collapseSingleResultList(multipleResults);36 }
總結一下這個方法。
第7行程式碼,透過PreparedStatement的getResultSet方法取得ResultSet,並將ResultSet包裝成ResultSetWrapper,ResultSetWrapper除了包含了ResultSet之外,依序定義了資料庫傳回的每個資料的每個行列名稱、列對應的JDBC類型、列對應的Java Class的類型,除此之外最主要的是還包含了TypeHandlerRegister(類型處理器,所有的參數都是透過TypeHandler進行設定的)。
第9行程式碼,取得該
以上是select源碼分析及小結的詳細內容。更多資訊請關注PHP中文網其他相關文章!