首頁  >  文章  >  Java  >  select源碼分析及小結

select源碼分析及小結

巴扎黑
巴扎黑原創
2017-06-26 09:32:121578瀏覽

範例程式碼

之前的文章說過,對於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行程式碼,取得該標籤按道理應該只能定義一個resultMap屬性,但這裡卻取得的是一個List,不是很清楚。

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

第12行~第18行的程式碼,將ResultSetWrapper中的值依照ResultMap,轉換成Java對象,先儲存在multipleResults中,這是一個List

第20行~第33行的程式碼,是用來處理 ;,每次執行MyBatis操作的時候先取得對應的MappedStatement,MappedStatement持有的一些重要屬性有:#####################接著是BoundSql,BoundSql中最重要儲存的就是目前要執行的SQL語句,其餘還有要設定的參數資訊與參數對象,BoundSql持有的屬性有:#######

最后是ParameterMapping,ParameterMapping是待设置的参数映射,存储了待设置的参数的相关信息,ParameterMapping持有的属性有:

 

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   @Override13   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<BatchResult> doFlushStatements(boolean isRollback) 5       throws SQLException; 6  7 protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) 8       throws SQLException; 9 10 protected abstract <E> Cursor<E> 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<Interceptor> interceptors = new ArrayList<Interceptor>(); 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<Interceptor> 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的时候会根据用户配置先尝试操作缓存。

在MyBatis中还有很多地方使用到了装饰器模式,例如StatementHandler、Cache。

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 fail10           // throw an SQLException instead of a Runtime11           checkConnection();12         }13         return method.invoke(realConnection, args);14       } catch (Throwable t) {15         throw ExceptionUtil.unwrapThrowable(t);16       }17     }18 }

这层代理的作用主要是为了让Connection使用完毕之后从栈中弹出来。

MyBatis中的插件也是使用代理模式实现的,这个在后面会说到。

以上是select源碼分析及小結的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn