Sample code
The previous article said that for MyBatis, insert, update, and delete are a group, because for MyBatis It is said that they are all update; select is a group, because for MyBatis it is select.
This article studies the implementation process of select. The sample code is:
1 public void testSelectOne() { 2 System.out.println(mailDao.selectMailById(8)); 3 }
The implementation of the selectMailById method is:
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 }
We know that the select provided by MyBatis has two methods, selectList and selectOne, but this article only analyzes and only needs to analyze the selectOne method, the reason will be explained later.
selectOne method flow
First look at the selectOne method flow of SqlSession, the method is located In 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 }
This is why I said that the selectOne and selectList methods only need to analyze the selectList method, becauseIn MyBatis, all selectOne operations will eventually be converted into selectList operations. It is nothing more than judging the number of result sets after the operation is completed. If the number of result sets exceeds one, an error will be reported.
Then look at the code implementation of selectList in line 3. The method is also located in 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 }
I won’t talk about getting MappedStatement in line 3. Let’s follow the query method implementation of Executor in line 4. A decorator mode is used here to add caching function to SimpleExecutor. The code is located in 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 }
The code in line 2 obtains BoundSql. The content in BoundSql has been mentioned above, and will be summarized at the end.
The code in line 3 builds the cache Key based on the input parameters.
The code in line 4 executes the query operation. Take a look at the code implementation. The code is also located in 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 }
There is no configuration and reference to Cache, so the judgment on line 4 is not executed and the code on line 17 is executed. The code is located in BaseExecutor, the parent class of SimpleExecutor. The source code is implemented as:
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 }
The code on line 16 is executed here. The queryFromDatabase method is implemented as:
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 }
The code goes to line 5 and finally the duQuery method is executed. The implementation is:
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 }
See that the code in lines 4 to 6 is the same as the previous update, so I won’t go into details. Friends who have an impression of handler should remember it. It is PreparedStatementHandler. The next part will analyze the difference from update and how the query method of PreparedStatementHandler is implemented.
Query method implementation of PreparedStatementHandler
##Follow the query method of PreparedStatementHandler to the end. Its final implementation is:
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 }
See that line 3 performs the query operation, and the code in line 4 processes the result set, converting the result set to List, handleResultSets The method is implemented as:
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 }
Summarize this method.
Line 7 of the code, obtains the ResultSet through the getResultSet method of PreparedStatement, and wraps the ResultSet into a ResultSetWrapper. In addition to the ResultSet, the ResultSetWrapper also defines each piece of data returned by the database. Each row and column name, the JDBC type corresponding to the column, and the type of Java Class corresponding to the column. In addition, the most important thing is that it also contains TypeHandlerRegister (type processor, all parameters are set through TypeHandler).
##The 9th line of code obtains the ResultMap defined in the