學習 MyBatis 的一點小總結 —— 底層源碼初步分析

在過去程序員使用 JDBC 連接數據庫,總會帶來諸多不便。MyBatis 是一款優秀的持久層框架,可以替代 JDBC 幫助我們更好的進行開發。要了解 MyBatis 的實現原理,首先我們要明白 MyBatis 的大致操作步驟。
在這裏插入圖片描述
數據庫源告訴我們連接哪個數據庫,獲得要執行的SQL語句,再進行操作,這點者缺一不可。接下來要看的就是這三點在底層如何實現。


MyBatis 如何獲取數據庫源?

使用 Mybatis 第一步肯定是要寫好配置文件。官方給出的指導文檔告訴我們,XML 配置文件中包含了對 MyBatis 系統的核心設置,包括獲取數據庫連接實例的數據源。

<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <!-- 我們要獲取的數據庫源信息在這裏 -->
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
</configuration>

想要連接數據庫,必然要獲得數據源信息。既然上述配置文件有數據庫源信息,那我們只要進行解析就好了。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

由代碼可見,我們要的是一個 SqlSessionFactory 實例,SqlSessionFactory 裏面就有我們所需的數據庫源信息。通過new SqlSessionFactoryBuilder().build(inputStream) 返回SqlSessionFactory 實例,進入 build 方法。
在這裏插入圖片描述
build 方法返回值就是是 SqlSessionFactory,注意到有 return build(parser.parse()),關注點在 parser.parse(),進入 parse 方法。
在這裏插入圖片描述
parse 方法返回 Configuration 。parsed 是一個布爾類型成員變量,默認值是 false,作判斷的目的是爲了防止多線程情況下該方法被二次調用。這個方法返回一個 Configuration 類型的實例,Configuration 是 BaseBuilder 類的一個成員變量,Configuration 其實保存了配置文件所有的信息,只是現在還是一張白紙,需要再操作一番。進入 parseConfiguration 方法。
在這裏插入圖片描述
上一張圖的 parse.evalNode 方法將配置文件中 configuration 標籤下的內容進行解析,封裝到一個對象,這個對象作爲參數傳入 parseConfiguration 方法中。在 parseConfiguration 方法我們見到了很多熟悉的字樣,諸如 properties、typeAliases 之類的配置信息,但我們的目的是要拿到數據庫源信息,因此我們把目標放在包裹了數據庫源信息的 environments 標籤上,進入 environmentsElement 方法。
在這裏插入圖片描述
作爲參數的 context 對象與之前一樣,封裝了 environments 標籤中的內容,我們還需要進一步解析 dataSource 標籤,關注 DataSourceFactory dsFactory = dataSourceElement(child.evalNode(“dataSource”)) 這段代碼,進入dataSourceElement 方法。
在這裏插入圖片描述
到這裏 context 對象只有 dataSource 裏的內容了。發現 type 的值爲 POOLED(默認值),props 保存最終的數據庫配置信息。DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance() 這一段代碼,進入 resolveClass 方法,最終再跳轉 resolveAlias 方法中。
在這裏插入圖片描述
注意 value = (Class) typeAliases.get(key),typeAliases 實際上是一個 HashMap,將 POOLED 作爲 key 得到了保存的對應的 Class 類型。回到 dataSourceElement 方法。
在這裏插入圖片描述
得到返回的 Class 返回值,利用反射 newInstance 創建對應的 DataSourceFactory 對象,set 方法保存 props ,回到 environmentsElement 方法。
在這裏插入圖片描述
繼續執行後面的方法,最終數據庫源信息封裝到一個 Environment 類型的實例,這個實例又通過 set 方法保存到了 configuration 。configuration 已經處理就緒,被 parse 方法返回。回到之前的 build 方法,將 configuration 作爲參數傳入至另一個重載的 build 方法。
在這裏插入圖片描述
SqlSessionFactory 本身是一個接口,DefaultSqlSessionFactory 則是實現了 SqlSessionFactory 的實現類,保存好 configuration 之後返回,就得到了我們開頭需要的 SqlSessionFactory 實例。


MyBatis 如何獲取 sql 語句?

與獲取數據庫源類似,只要解析 Mapper 配置文件中的對應標籤,就可以獲得對應的 sql 語句。之前我們講過,SqlSessionFactory 中的 configuration 屬性保存數據庫源信息,事實上這個 configuration 屬性將整個配置文件的信息都給封裝成一個類來保存了。解析的前半部分與之前一樣,分歧點在之前提到的 parseConfiguration 方法,其中在 environmentsElement 方法下面還有一個 mapperElement 方法。
在這裏插入圖片描述
配置文件中 mappers 標籤加載mapper文件的方式共有四種:resource、url、class、package。代碼中的 if-else 語句塊分別判斷四種不同的加載方式,可見 package 的優先級最高。parent 是配置文件中 mappers 標籤中的信息,通過外層的循環一個一個讀取多個 Mapper 文件。這裏使用的方式是 resource ,所以會執行光標所在行的代碼塊,進入 mapperParser.parse() 方法。
在這裏插入圖片描述
我們要的是 mapper 標籤的內容,因此我們關注 configurationElement(parser.evalNode("/mapper")) 這一句,進入 configurationElement 方法。
在這裏插入圖片描述
context 就是我們解析整個 Mapper 文件 mapper 標籤中的內容,既然現在得到了內容,那隻需再找到對應的標籤就能獲得sql語句了。注意 buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)),我們看到了熟悉的 select、insert、update、delete,這些標籤裏就有我們寫 sql 語句。進入 buildStatementFromContext 方法。
在這裏插入圖片描述
list 保存了我們在 Mapper 文件中寫的所有含有 sql 語句的標籤元素,用一個循環遍歷 list 的每一個元素,分別將每一個元素的信息保存到 statementParser 中。進入 parseStatementNode 方法。
在這裏插入圖片描述
在這裏插入圖片描述
這個方法代碼內容很多,僅摘出節選,裏面定義了很多局部變量,這些變量用來保存sql語句標籤(例如)的參數信息(例如緩存 useCache)。再把所有參數傳到 addMappedStatement 中。進入 addMappedStatement 方法。
在這裏插入圖片描述
MappedStatement statement = statementBuilder.build(),使用 build 方法得到 MappedStatement 實例,這個類封裝了每一個含有sql語句標籤中所有的信息,再是 configuration.addMappedStatement(statement),保存到 configuration 中。


MyBatis 如何執行 sql 語句?

既然有了 SqlSessionFactory,我們可以從中獲得 SqlSession 的實例。開啓 session 的語句是 SqlSession session = sessionFactory.openSession(),進入 openSession 方法。
在這裏插入圖片描述
最終會執行 openSessionFromDataSource 方法。在之前 environment 已經有了數據庫源信息,調用 configuration.newExecutor 方法。
在這裏插入圖片描述
Executor 叫做執行器,Mybatis 一共有三種執行器,用一個枚舉類 ExecutorType 保存,分別是 SIMPLE,REUSE,BATCH,默認就是 SIMPLE。if-else 語句判斷對應的類型,創建不同的執行器。在代碼末端處有個 if 判斷語句,如果 cacheEnabled 爲 true,則會創建緩存執行器,默認是爲 true,即默認開啓一級緩存。

回到 openSessionFromDataSource 方法,最終返回一個 DefaultSqlSession 實例。得到 session 我們就可以執行 sql 語句了。SqlSession 提供了在數據庫執行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執行已映射的 SQL 語句,以 selectOne 方法爲例,進入該方法後發現,最終會調用到 selectList 方法。
在這裏插入圖片描述
configuration.getMappedStatement(statement) 得到了我們之前保存的 MappedStatement 對象,再調用 executor.query 方法,調用 query 方法之前會執行 wrapCollection 方法,保存 sql 語句中用戶傳入的參數。進入 query 方法。
在這裏插入圖片描述
boundSql 裏面就有我們要執行的 sql 語句,CacheKey 是用來開啓緩存的。執行父類 BaseExecutor 中的 createCacheKey 方法,通過 id,offsetid,limited,sql 組成一個唯一的 key,調用下一個 query 方法。
在這裏插入圖片描述
Cache cache = ms.getCache() 是二級緩存,二級緩存爲空,直接調用 query 方法。
在這裏插入圖片描述
list = resultHandler == null ? (List) localCache.getObject(key) : null 傳入 key 值在本地查詢,如果有返回證明 key 已經緩存到本地,直接從本地緩存獲取結果。否則 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql),去數據庫查詢。
在這裏插入圖片描述
localCache.putObject(key, EXECUTION_PLACEHOLDER) 首先將 key 緩存至本地,下一次查詢就能找到這個 key 了。進入 doQuery 方法。
在這裏插入圖片描述
stmt = prepareStatement(handler, ms.getStatementLog()),得到一個 Statement。進入 prepareStatement 方法。
在這裏插入圖片描述
我們看到了一個熟悉的 Connection 對象,這個就是原生 JDBC 的實例對象。回到 doQuery 方法,進入 handler.query(stmt, resultHandler) 方法。
在這裏插入圖片描述
statement 強轉型爲 PreparedStatement 類型,這下我們又得到了 PreparedStatement 的類型實例了,調用 execute 方法,這個方法也是屬於原生 JDBC。執行完成後 return resultSetHandler.handleCursorResultSets(ps),進入 handleCursorResultSets 方法。
在這裏插入圖片描述
ResultSetWrapper rsw = getFirstResultSet(stmt),看到 getFirstResultSet 方法中的 ResultSet rs = stmt.getResultSet(),在這裏我們得到了 ResultSet 實例對象,最終 return rs != null ? new ResultSetWrapper(rs, configuration) : null,返回最終結果集。


MyBatis 如何實現不同類型數據之間的轉換?

進入上一張圖中 ResultSetWrapper 中可以看到,其中包含三個成員變量 columnNames、classNames、jdbcTypes,三者都是 ArrayList 集合。看一下構造方法。
在這裏插入圖片描述
final ResultSetMetaData metaData = rs.getMetaData(),metaData 就是數據庫相關的數據,getColumnCount 統計有多少個字段,循環加入到 columnNames、jdbcTypes、classNames。columnNames 保存的就是實體類中的屬性名,jdbcTypes 保存的是字段在數據庫中的數據類型,classNames 保存的是字段在 Java 中的數據類型,比如 Java 的 String 與數據庫 VARCHAR,MyBatis 充當一箇中介完成轉換,真正實現 ORM 的核心思想。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章