Mybatis源碼淺析
前言
最近準備看一看mybatis的源碼,雖說使用了很久,但是裏面的一些細節還是不算很了解,今天整理一個簡單的文檔。我們首先需要理解一件事,mybatis的底層使用的還是jdbc,所以如果對jdbc很熟悉的話,源碼看起來就會很輕松;如果對jdbc不了解的話,建議先熟悉一下再使用mybatis
結構
首先奉上一張圖,這張圖能夠簡單的概述mybatis的結構,裏面針對緩存等其他特性沒有描述,後續深入了解後補上
這裏涵蓋了mybatis一次查詢功能的主要類,如果有畫錯的地方,還請及時指出。
再附上一小段測試代碼,方便解釋上面的類圖,只要跟著debug走一次就會豁然開朗
//重量級對象,最好保證單例SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(Resources.getResourceAsReader("mybatis-config.xml")); //每次執行sql需要開啟一個session回話,用完之後及時回收;考慮性能可以使用連接池,註意重復使用時數據沖突 SqlSession sqlSession = null; try { sqlSession = factory.openSession(); WxMenu menu= sqlSession.selectOne("xxx.yyy", 2); System.out.println(menu); }catch (Exception e){ e.printStackTrace(); }finally { if(sqlSession != null){ sqlSession.close(); } }
說明:上面的xxx.yyy分別代表mapper.xml中的namespace和sql的id屬性值,在mybatis解析xml的時候加入緩存,用於唯一標識一個sql,大家測試的時候根據需要進行修改
流程
1)首先創建SqlSessionFactoryBuilder對象,通過名字就知道這個對象用於創建SqlSessionFactory。XMLConfigBuilder是用於讀取並解析xml配置文件,並將配置信息存儲在Configuration對象中,然後再通過Configuration對象構建SqlSessionFactory
2)SqlSessionFactory對象是一個重量級的對象,可以理解為與數據庫連接的管理工廠,所有的會話(session)都從這裏獲取,所以最好做成單例的對象。(配置文件的內容都是從官網粘貼的,官網:http://www.mybatis.org/mybatis-3/zh/getting-started.html)
3)接下來就要從SqlSessionFactory獲取session對象,每和數據庫交互一次則需要開啟一次會話,也就是SqlSession對象,包括事務控制(Transaction)、statement管理(MappedStatement,一個sql對應一個statement對象)、分頁(RowBounds)、配置信息(Configuration)等對象都包含在其中
4)上面的一切都是準備工作,真正負責執行sql的是Executor。Executor的創建很有意思,默認情況下會用到SimpleExecutor和CachingExecutor。這兩個類都實現了Executor,但是CachingExecutor做了一個小小的包裝。這裏貼上幾行代碼:
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor, autoCommit); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
首先創建SimpleExecutor對象,然後再以此對象構建CachingExecutor(加粗部分)
5)當我們調用sqlseesion的查詢方法時,其實調用的是executor的query()方法,最終調用doQuery方法進行查詢。這裏面有些緩存的操作,會涉及到lock,有興趣的童鞋可以仔細看下。我們這裏只觀察查詢數據庫的情況。這裏我們看一下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(this, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
在這裏獲取到Configuration對象之後,再根據其他入參生成StatementHandler對象,仔細看下代碼發現返回的是實現類PreparedStatementHandler(因為MappedStatement默認的statementType就是PREPARED)。PreparedStatementHandler也許大家比較陌生,但是熟悉jdbc的人一定知道PreparedStatement。回想一下jdbc執行的流程,獲取connection對象,然後創建statement對象,執行statement.execute(sql),返回resultset對象,然後對結果進行處理。其實當你進入PreparedStatementHandler.query方法之後,你會發現和jdbc是一樣的。我們看下這個query方法的代碼:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
怎麽樣,和jdbc沒什麽差別吧。有的人肯定會問connection在哪裏獲取的,sql參數綁定在哪裏處理的?看下上面的doQuery()方法中的prepareStatement()方法就知道啦,裏面使用了parameterHandler做參數的綁定,具體代碼就不在累述。
6)PreparedStatementHandler執行完之後,需要對結果處理,這裏使用的是resultSetHandler,對結果處理完畢之後返回即可。
總結
當然我們這裏只是簡單看了一下mybatis的查詢操作,其實mybatis還有其他特性我們沒有分析。不過閱讀源碼沒有必要一行一行的分析,挑選主要的類和方法,其他的高級特性可以按需再看。跟著debug走一次,心裏有一個整體的流程會事半功倍的,還有多看註釋(老外會在類和方法體上寫很多註釋)。mybatis的整體思想就是幫助我們構建sql,執行sql,然後再將結果映射到我們的model上,相當於是對jdbc的高級封裝。其中的很多類的命名也很友好,比如PreparedStatementHandler其實就是PreparedStatement的的控制器,做了PreparedStatement該做的事。
上面的分析如果有出入,還望及時指出,互相學習,共勉互勵!
Mybatis源碼淺析