當前位置:
首頁 > 知識 > select源碼分析及小結

select源碼分析及小結

示例代碼

之前的文章說過,對於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 selectOne(String statement, Object parameter) {
2 // Popular vote was to return null on 0 results and throw exception on too many.
3 List list = this.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 List 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 List 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 List 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 list = (List) tcm.getObject(cache, key);
10 if (list == null) {
11 list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
12 tcm.putObject(cache, key, list); // issue #578 and #116
13 }
14 return list;
15 }
16 }
17 return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
18 }

這裡並沒有配置且引用Cache,因此不執行第4行的判斷,執行第17行的代碼,代碼位於SimpleExecutor的父類BaseExecutor中,源碼實現為:

1 public List 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 list;
10 try {
11 queryStack++;
12 list = resultHandler == null ? (List) 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 #601
26 deferredLoads.clear;
27 if (configuration.getLocalCacheScope == LocalCacheScope.STATEMENT) {
28 // issue #482
29 clearLocalCache;
30 }
31 }
32 return list;
33 }

這裡執行第16行的代碼,queryFromDatabase方法實現為:

1 private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
2 List 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 List 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.query(stmt, resultHandler);
8 } finally {
9 closeStatement(stmt);
10 }
11 }

看到第4行~第6行的代碼都和前文update是一樣的,就不說了,handler有印象的朋友應該記得是PreparedStatementHandler,下一部分就分析一下和update的區別,PreparedStatementHandler的query方法是如何實現的。

PreparedStatementHandler的query方法實現

跟一下PreparedStatementHandler的query方法跟到底,其最終實現為:

1 public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
2 PreparedStatement ps = (PreparedStatement) statement;
3 ps.execute;
4 return resultSetHandler. handleResultSets(ps);
5 }

看到第3行執行查詢操作,第4行的代碼處理結果集,將結果集轉換為List,handleResultSets方法實現為:

1 public List handleResultSets(Statement stmt) throws SQLException {
2 ErrorContext.instance.activity("handling results").object(mappedStatement.getId);
3
4 final List multipleResults = new ArrayList;
5
6 int resultSetCount = 0;
7 ResultSetWrapper rsw = getFirstResultSet(stmt);
8
9 List 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行代碼,獲取該標籤按道理應該只能定義一個resultMap屬性,但是這裡卻獲取的是一個List,不是很清楚。

第11行代碼,做了一個校驗,即如果select出來有結果返回,但是沒有ResultMap或者ResultType與之對應的話,拋出異常,道理很簡單,沒有這2者之一,MyBatis並不知道將返迴轉成什麼樣子。

第12行~第18行的代碼,將ResultSetWrapper中的值根據ResultMap,轉成Java對象,先存儲在multipleResults中,這是一個List

第20行~第33行的代碼,是用於處理,每次執行MyBatis操作的時候先獲取對應的MappedStatement,MappedStatement持有的一些重要屬性有:

select源碼分析及小結

接著是BoundSql,BoundSql中最重要存儲的就是當前要執行的SQL語句,其餘還有要設置的參數信息與參數對象,BoundSql持有的屬性有:

select源碼分析及小結

最後是ParameterMapping,ParameterMapping是待設置的參數映射,存儲了待設置的參數的相關信息,ParameterMapping持有的屬性有:

select源碼分析及小結

MyBatis中使用到的設計模式

下面來總結一下MyBatis中使用到的設計模式,有些設計模式可能在到目前位置的文章中沒有體現,但是在之後的【MyBatis源碼分析】系列文章中也會體現,這裡一併先列舉出來:

1、建造者模式

代碼示例為SqlSessionFactoryBuilder,代碼片段:

1 public SqlSessionFactory build(Reader reader) {
2 return build(reader, null, null);
3 }
4
5 public SqlSessionFactory build(Reader reader, String environment) {
6 return build(reader, environment, null);
7 }
8
9 public SqlSessionFactory build(Reader reader, Properties properties) {
10 return build(reader, null, properties);
11 }
12
13 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
14 try {
15 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
16 return build(parser.parse);
17 } catch (Exception e) {
18 throw ExceptionFactory.wrapException("Error building SqlSession.", e);
19 } finally {
20 ErrorContext.instance.reset;
21 try {
22 reader.close;
23 } catch (IOException e) {
24 // Intentionally ignore. Prefer previous error.
25 }
26 }
27 }

重載了大量的build方法,可以根據參數的不同構建出不同的SqlSessionFactory。

2、抽象工廠模式

代碼示例為TransactionFactory,代碼片段為:

1 public class JdbcTransactionFactory implements TransactionFactory {
2
3 @Override
4 public void setProperties(Properties props) {
5 }
6
7 @Override
8 public Transaction newTransaction(Connection conn) {
9 return new JdbcTransaction(conn);
10 }
11
12 @Override
13 public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
14 return new JdbcTransaction(ds, level, autoCommit);
15 }
16 }

抽象出事物工廠,不同的事物類型實現不同的事物工廠,像這裡就是Jdbc事物工廠,通過Jdbc事物工廠去返回事物介面的具體實現。

其它的像DataSourceFactory也是抽象工廠模式的實現。

3、模板模式

代碼示例為BaseExecutor,代碼片段:

1 protected abstract int doUpdate(MappedStatement ms, Object parameter)
2 throws SQLException;
3
4 protected abstract List doFlushStatements(boolean isRollback)
5 throws SQLException;
6
7 protected abstract List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
8 throws SQLException;
9
10 protected abstract Cursor doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
11 throws SQLException;

BaseExecutor封裝好方法流程,子類例如SimpleExecutor去實現。

4、責任鏈模式

代碼示例為InterceptorChain,代碼片段為:

1 public class InterceptorChain {
2
3 private final List interceptors = new ArrayList;
4
5 public Object pluginAll(Object target) {
6 for (Interceptor interceptor : interceptors) {
7 target = interceptor.plugin(target);
8 }
9 return target;
10 }
11
12 public void addInterceptor(Interceptor interceptor) {
13 interceptors.add(interceptor);
14 }
15
16 public List getInterceptors {
17 return Collections.unmodifiableList(interceptors);
18 }
19
20 }

可以根據需要添加自己的Interceptor,最終按照定義的Interceptor的順序逐一嵌套執行。

5、裝飾器模式

代碼示例為CachingExecutor,代碼片段為:

1 public class CachingExecutor implements Executor {
2
3 private Executor delegate;
4 private TransactionalCacheManager tcm = new TransactionalCacheManager;
5
6 public CachingExecutor(Executor delegate) {
7 this.delegate = delegate;
8 delegate.setExecutorWrapper(this);
9 }
10
11 ...
12 }

給Executor添加上了緩存的功能,update與query的時候會根據用戶配置先嘗試操作緩存。

6、代理模式

代碼示例為PooledConnection,代碼片段為:

1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
2 String methodName = method.getName;
3 if (CLOSE.hashCode == methodName.hashCode && CLOSE.equals(methodName)) {
4 dataSource.pushConnection(this);
5 return null;
6 } else {
7 try {
8 if (!Object.class.equals(method.getDeclaringClass)) {
9 // issue #579 toString should never fail
10 // throw an SQLException instead of a Runtime
11 checkConnection;
12 }
13 return method.invoke(realConnection, args);
14 } catch (Throwable t) {
15 throw ExceptionUtil.unwrapThrowable(t);
16 }
17 }
18 }

這層代理的作用主要是為了讓Connection使用完畢之後從棧中彈出來。

MyBatis中的插件也是使用代理模式實現的,這個在後面會說到。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 科技優家 的精彩文章:

ubuntu下配置Apache+mod
SQL Server Alwayson可用性副本會話期間的可能故障
尚學堂高琪Java300集視頻精華筆記
Javascript檢測值

TAG:科技優家 |

您可能感興趣

Storm源碼分析之Trident源碼分析
Prometheus原理和源碼分析
ThreadLocal源碼分析
JsBridge 源碼分析
sys.path源碼分析
spring源碼分析——spring大綱
Vue 源碼分析之 Observer
Flutter圖片緩存 Image.network源碼分析
LinkedList源碼分析
TinyHttpd源碼分析
HashMap源碼分析
Thread源碼剖析
PopupWindow源碼分析
基於Redis實現分散式鎖-Redisson使用及源碼分析
AtomicInteger 源碼解析
kafka 源碼分析 3 : Producer
PyalgoTrade源碼閱讀完結篇
React Native BackHandler exitApp 源碼分析
分散式共享 Session之SpringSession 源碼細節
android 結合源碼深入剖析AsyncTask機制原理