這篇文章主要從MapperProxy類出發,來縱向分析下Mybatis執行sql的流程。java
MapperProxyFactory類只是MapperProxy的工廠類,因此主要看下MapperProxy就行了。MapperProxy是用jdk動態代理來實現對Mapper的代理,所以先看下他的invoke方法。不出意外,這個方法裏就有咱們最關心的代碼,是如何執行的查詢。sql
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args);//Object定義的方法,則透傳給Mapper } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args);//java8接口的默認方法和靜態方法,代理方法有所不一樣。 } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method);//先從緩存中取,沒有則根據method新建MapperMethod類 return mapperMethod.execute(sqlSession, args);//執行sql }
能夠看到真正執行Sql的是MapperMethod的execute方法。接下來看下。api
MapperMethod的execute方法主要就是判斷sql語句是update,insert,delete仍是select,來執行不一樣的邏輯。update,insert,delete的邏輯基本相同,都是執行sql語句。select較爲複雜,須要將返回結果映射爲java bean。返回多個結果時還要分爲集合,map,Cursor三種狀況。所以,咱們就只看select,而且只考慮將結果集保存在集合的狀況。實如今executeForMany方法中。緩存
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args);//獲取參數Map if (method.hasRowBounds()) {//邏輯分頁,通常不多用 RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } if (!method.getReturnType().isAssignableFrom(result.getClass())) {//若結果類型不符合方法的返回類型,則轉化爲對應的結合類型 if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }
method這個屬性是MethodSignature類,是MapperMethod的內部靜態類。主要封裝了下處理Method的方法。convertArgsToSqlCommandParam這個方法就是當有多個參數時,將此次方法調用的參數和參數名保存在一個map中。session
@Select("select * from product where id > #{id} and price > #{price}") ArrayList<Product> findAll(@Param("id") long id, @Param("price") double price);
例如上面這個方法,代理類在執行這個方法時,執行到Object param = method.convertArgsToSqlCommandParam(args)這句時,若入參爲1,20。則param的值爲Map,鍵值對爲:
{
"id":"1",
"price":"20",
"param1":"1",
"price":"1"
}
有了這個Map,則方便將Sql中的#{id},#{price}替換爲"1"和"20"。
command這個屬性是SqlCommand類,是MapperMethod的內部靜態類。主要保存了sql數據,封裝了一些工具方法。 getName方法,則是獲得"${Mapper包路徑}.${Mapper類名}.${執行方法名}",例如,若包名是mapper,上面的方法則是"mapper.ProductMapper.findAll"。這個字符串有什麼用呢?
上一篇說過addMapper的過程當中,主要存儲下了三份數據,其中sql信息存儲在MappedStatement類中,而全部的MappedStatement存儲在mappedStatements這個Map中,而他的key也是這個字符串。
因此此時,咱們能夠找到這個方法對應的MappedStatement。即獲得addMapper時解析Sql的相關數據。 能夠看到最終執行Sql是SqlSession的selectList方法。mybatis默認使用的SqlSession是DefaultSqlSession類,接下來看下。mybatis
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement);//經過key找到對應的MappedStatement。 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
能夠看到,ms即是剛剛說到的MappedStatement。而最終執行查詢的是executor。executor是DefaultSqlSession的一個屬性,是在構造方法中傳來的。DefaultSqlSession是在哪裏構造的呢?就是在第一篇中demo中調用到的,SqlSessionFactory的OpenSession方法。mybatis默認的SqlSessionFactory是DefaultSqlSessionFactory。app
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment();//保存了transactionFactory和DataSource final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit);//一個session對應一個Executor,一級緩存存在Executor裏,這也就是爲何一級緩存是Session級別的。 } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
能夠看到主要分了三步:事務工廠實例化事務對象tx,實例化執行器executor,傳入剛剛的tx,實例化DefaultSqlSession,傳入剛剛的executor。
事務類主要考慮JdbcTransaction,這個類其實就是稍微封裝了下Jdbc的commit,rollback,setAutoCommit等操做。
接下來就到了我們要找的executor了。Executor是個接口,mybatis封裝了三種實現類,BatchExecutor,ReuseExecutor,SimpleExecutor。configuration.newExecutor(tx, execType),這句代碼就是根據類型來實例化不一樣的Executor。以及後續要說的緩存和插件擴展。
繼續回到剛剛的DefaultSqlSession類,裏面主要用到了Executor的query方法。接下來看下Executor吧。工具
在Executor中,咱們能看到裝飾者模式和模板模式的影子。
BaseExecutor抽象類,實現了query,commit,rollback等基本方法。但doQuery等是抽象方法,由實現類BatchExecutor,ReuseExecutor,SimpleExecutor去具體實現。這種模板模式,耦合代碼較少,值得學習。
CachingExecutor是個裝飾類,爲類賦予緩存功能。不須要爲BatchExecutor,ReuseExecutor,SimpleExecutor寫三個對應的緩存類。
具體的查詢代碼在BatchExecutor,ReuseExecutor,SimpleExecutor的doQuery方法中,咱們就來看下最簡單的SimpleExecutor的doQuery方法吧。學習
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//實例化StatementHandler stmt = prepareStatement(handler, ms.getStatementLog());//獲得jdbc中的 Statement,並進行sql參數綁定、設置超時時間、異常處理等。 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
StatementHandler的結構也和Executor差很少,也是用了模板模式。SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler這三個實現類分別對應了jdbc的三種Statement,他們的區別能夠查看jdbc文檔。咱們就主要看PreparedStatementHandler。fetch
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement;//將statement強制轉換爲jdbc中的PreparedStatement ps.execute();//執行sql return resultSetHandler.<E> handleResultSets(ps);//將jdbc的ResultSets轉化爲須要的結果類型 }
能夠看到最終執行的仍是jdbc的PreparedStatement.execute()。而jdbc最終將結果保存在ResultSets中,須要resultSetHandler將其解析爲最終類型。這個過程下篇文章要講,涉及到addMapper過程當中保存的ResultMap。
sql執行的縱向過程以下圖
固然每一個類還有其餘的代碼,之後會對緩存,插件,日誌等進行分析。有的細節也並無去討論,主要是由於這些代碼並不算難,但又比較繁瑣。例如sql參數綁定,已經將參數保存在了map中,剩下就是字符串匹配而後用jdbc的api進行參數綁定。