1. 程式人生 > >mybatis 的原始碼分析

mybatis 的原始碼分析

*SqlSessions 由 SqlSessionFactory 例項建立的。SqlSessionFactory 對 象 包 含 創 建 SqlSession 實 例 的 所 有 方 法 。 而 SqlSessionFactory 本 身 是 由 
SqlSessionFactoryBuilder 建立。*

那麼我們先從 SqlSessionFactoryBuilder 開始 
SqlSessionFactoryBuilder:

SqlSessionFactory 的建立,需要使用 SqlSessionFactoryBuilder 物件的 build()方法。由於SqlSessionFactoryBuilder 物件在建立完工廠物件後,就完成了其歷史使命,即可被銷燬。所以,一般會將該 SqlSessionFactoryBuilder 物件建立為一個方法內的區域性物件,方法結束,物件銷燬。其被過載的 build()方法較多: 
SqlSessionFactoryBuilder 有五個 build()方法,每一種都允許你從不同的資源中建立一個 SqlSession 例項。

SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)

第一種方法是最常用的,它使用了一個參照了 XML 文件或特定的 mybatis-config.xml 檔案的 Reader 例項。 可選的引數是 environment 和 properties。 environment 決定載入哪種環境,包括資料來源和事務管理器。比如:

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
        ...
    <dataSource type="POOLED"
> ... </environment> <environment id="production"> <transactionManager type="MANAGED"> ... <dataSource type="JNDI"> ... </environment> </environments>

如果你呼叫了 一個使用 environment 引數 方 式的 build 方法, 那麼 MyBatis 將會使用 configuration 物件來配置這個 environment。 當然, 如果你指定了一個不合法的 environment, 你會得到錯誤提示。 如果你呼叫了其中之一沒有 environment 引數的 build 方法, 那麼就使用 預設的 environment(在上面的示例中就會指定為 default=”development”)。

如果你呼叫了使用 properties 例項的方法,那麼 MyBatis 就會載入那些 properties(屬性 配置檔案) ,並你在你配置中可使用它們。那些屬性可以用${propName}語法形式多次用在 配置檔案中。

回想一下,屬性可以從 mybatis-config.xml 中被引用,或者直接指定它。因此理解優先 級是很重要的。我們在文件前面已經提及它了,但是這裡要再次重申:

如果一個屬性存在於這些位置,那麼 MyBatis 將會按找下面的順序來載入它們:

在 properties 元素體中指定的屬性首先被讀取, 
從 properties 元素的類路徑 resource 或 url 指定的屬性第二個被讀取, 可以覆蓋已經 指定的重複屬性, 
作為方法參 數傳遞 的屬性最 後被讀 取,可以 覆蓋已 經從 properties 元 素體和 resource/url 屬性中載入的任意重複屬性。 
因此,最高優先順序的屬性是通過方法引數傳遞的,之後是 resource/url 屬性指定的,最 後是在 properties 元素體中指定的屬性。

總結一下,前四個方法很大程度上是相同的,但是由於可以覆蓋,就允許你可選地指定 environment 和/或 properties。 這裡給出一個從 mybatis-config.xml 檔案建立 SqlSessionFactory 的示例:

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

注意這裡我們使用了 Resources 工具類,這個類在 org.mybatis.io 包中。Resources 類正如其名,會幫助你從類路徑下,檔案系統或一個 web URL 載入資原始檔。看一下這個類的 原始碼或者通過你的 IDE 來檢視,就會看到一整套有用的方法。這裡給出一個簡表:

URL getResourceURL(String resource)
URL getResourceURL(ClassLoader loader, String resource)
InputStream getResourceAsStream(String resource)
InputStream getResourceAsStream(ClassLoader loader, String resource)
Properties getResourceAsProperties(String resource)
Properties getResourceAsProperties(ClassLoader loader, String resource)
Reader getResourceAsReader(String resource)
Reader getResourceAsReader(ClassLoader loader, String resource)
File getResourceAsFile(String resource)
File getResourceAsFile(ClassLoader loader, String resource)
InputStream getUrlAsStream(String urlString)
Reader getUrlAsReader(String urlString)
Properties getUrlAsProperties(String urlString)
Class classForName(String className)

最後一個 build 方法使用了一個 Configuration 例項。configuration 類包含你可能需要了 解 SqlSessionFactory 例項的所有內容。Configuration 類對於配置的自查很有用,包含查詢和 操作 SQL 對映(不推薦使用,因為應用正接收請求) 。configuration 類有所有配置的開關, 這些你已經瞭解了,只在 Java API 中露出來。這裡有一個簡單的示例,如何手動配置 configuration 例項,然後將它傳遞給 build()方法來建立 SqlSessionFactory。

DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);

Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);

SqlSessionFactory已建立,可以用來建立 SqlSession 例項。 
SqlSessionFactory:

SqlSessionFactory 介面物件是一個重量級物件(系統開銷大的物件),是執行緒安全的,所 以一個應用只需要一個該物件即可。建立SqlSession 需要使用 SqlSessionFactory 介面的的 openSession()方法。

SqlSessionFactory 有六個方法可以用來建立 SqlSession 例項。通常來說,如何決定是你 選擇下面這些方法時:

  • Transaction (事務): 你想為 session 使用事務或者使用自動提交(通常意味著很多 資料庫和/或 JDBC驅動沒有事務)?
  • Execution (執行): 你想 MyBatis 複用預處理語句和/或批量更新語句(包括插入和 刪除)?

過載的 openSession()方法簽名設定允許你選擇這些可選中的任何一個組合。

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

預設的 openSession()方法沒有引數,它會建立有如下特性的 SqlSession:

  • 會開啟一個事務(也就是不自動提交)
  • 連線物件會從由活動環境配置的資料來源例項中得到。
  • 事務隔離級別將會使用驅動或資料來源的預設設定。
  • 預處理語句不會被複用,也不會批量處理更新。

這些方法大都可以自我解釋的。 開啟自動提交, “true” 傳遞 給可選的 autoCommit 引數。 提供自定義的連線,傳遞一個 Connection 例項給 connection 引數。注意沒有覆蓋同時設定 Connection 和 autoCommit 兩者的方法,因為 MyBatis 會使用當前 connection 物件提供的設 置。 MyBatis 為事務隔離級別呼叫使用一個 Java 列舉包裝器, 稱為 TransactionIsolationLevel, 否則它們按預期的方式來工作,並有 JDBC 支援的 5 級 ( NONE,READ_UNCOMMITTED,READ_COMMITTED,REPEA TABLE_READ,SERIALIZA BLE)

還有一個可能對你來說是新見到的引數,就是 ExecutorType。這個列舉型別定義了 3 個 值:

  • ExecutorType.SIMPLE: 這個執行器型別不做特殊的事情。它為每個語句的執行建立一個新的預處理語句。
  • ExecutorType.REUSE: 這個執行器型別會複用預處理語句。
  • ExecutorType.BATCH:這個執行器會批量執行所有更新語句,如果 SELECT 在它們中間執行還會標定它們是 必須的,來保證一個簡單並易於理解的行為。 
    注意: 在 SqlSessionFactory 中還有一個方法我們沒有提及,就是 getConfiguration()。這 個方法會返回一個 Configuration 例項,在執行時你可以使用它來自檢 MyBatis 的配置。

SqlSession 介面

SqlSession 介面物件用於執行持久化操作。一個 SqlSession 對應著一次資料庫會話,一 次會話以 SqlSession物件的建立開始,以 SqlSession 物件的關閉結束。 SqlSession介面物件是執行緒不安全的,所以每次資料庫會話結束前,需要馬上呼叫其close()方法,將其關閉。再次需要會話,再次建立。而在關閉時會判斷當前的 SqlSession 是否被提交:若沒有被提交,則會執行回滾後關閉;若已被提交,則直接將 SqlSession 關閉。 所以,SqlSession 無需手工回滾。

在 SqlSession 類中有超過 20 個方法,所以將它們分開成易於理解的組合。 
語句執行方法 
這些方法被用來執行定義在 SQL 對映的 XML 檔案中的 SELECT,INSERT,UPDA E T 和 DELETE 語句。它們都會自行解釋,每一句都使用語句的 ID 屬性和引數物件,引數可以 是原生型別(自動裝箱或包裝類) ,JavaBean,POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

selectOne 和 selectList 的不同僅僅是 selectOne 必須返回一個物件。 如果多餘一個, 或者 沒有返回 (或返回了 null) 那麼就會丟擲異常。 , 如果你不知道需要多少物件, 使用 selectList。

如果你想檢查一個物件是否存在,那麼最好返回統計數(0 或 1) 。因為並不是所有語句都需 要引數,這些方法都是有不同過載版本的,它們可以不需要引數物件。

<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)

最後,還有查詢方法的三個高階版本,它們允許你限制返回行數的範圍,或者提供自定 義結果控制邏輯,這通常用於大量的資料集合。

<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)

RowBounds 引數會告訴 MyBatis 略過指定數量的記錄,還有限制返回結果的數量。 RowBounds 類有一個構造方法來接收 offset 和 limit,否則是不可改變的。

int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);

不同的驅動會實現這方面的不同級別的效率。對於最佳的表現,使用結果集型別的 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE(或句話說:不是 FORWARD_ONLY)。

ResultHandler 引數允許你按你喜歡的方式處理每一行。你可以將它新增到 List 中,創 建 Map, 或丟擲每個結果而不是隻保留總計。 Set 你可以使用 ResultHandler 做很多漂亮的事, 那就是 MyBatis 內部建立結果集列表。

它的介面很簡單。

package org.apache.ibatis.session;
public interface ResultHandler<T> {
  void handleResult(ResultContext<? extends T> context);
}

ResultContext 引數給你訪問結果物件本身的方法, 大量結果物件被建立, 你可以使用布 爾返回值的 stop()方法來停止 MyBatis 載入更多的結果。

批量立即更新方法(Flush Method)

有一個方法可以重新整理(執行)儲存在JDBC驅動類中的批量更新語句。當你將ExecutorType.BATCH作為ExecutorType使用時可以採用此方法。

List<BatchResult> flushStatements()

原始碼分析 
A 、 輸入流的關閉 
在輸入流物件使用完畢後,不用手工關閉,因為輸入流在使用完畢後,SqlSessionFactoryBuilder 物件的 builder()方法自動將輸入流關閉。 
這裡寫圖片描述 
這裡寫圖片描述 
B 、 SqlSession 的建立 
SqlSession 的建立,需要使用 SqlSessionFactory 物件的 openSession()方法。 
SqlSessionFacotry 介面的實現類是 DefaultSqlSessionFactory. 
這裡寫圖片描述 
開啟 openSession()的原始碼

這裡寫圖片描述 
這裡寫圖片描述 
從以上程式碼可以看出,無引數的 openSession()方法,將事務的自動提交直接賦值為 false.而所謂的建立 SqlSession。就是載入主配置檔案,建立了一個執行器物件(用於執行對映檔案中的 SQL 語句),初始化了一個 DB 資料被修改的標誌變數 dirty,關閉了事務的自動提交功能。 
C 、 增刪改的執行 
對於 SqlSession 的 insert(), update(),delete(),其底層均是呼叫執行了 update()方法。 
這裡寫圖片描述 
這裡寫圖片描述 
這裡寫圖片描述 
從以上的原始碼可知,無論執行增,刪還是修改,均將 dirty 變數設定為 true,且在獲 
取到對映檔案中指定的 id 的 SQL 語句後,由執行器 executor 執行。 
D 、 SqlSession 的提交 commit() 
這裡寫圖片描述 
這裡寫圖片描述 
從程式碼中可知,isCommitOrRollbackRequired(force)方法的返回值為 true.繼承跟蹤executor 的 commit()方法。 
這裡寫圖片描述 
這裡寫圖片描述 
由以上程式碼可知,執行 SqlSession 的無參 commit()方法,最終會將事務提交。 
E 、 SqlSession 的關閉 
這裡寫圖片描述 
這裡寫圖片描述 
由以上程式碼可知,isCommitOrRollbackRequired(force)方法的返回值為 true.繼續跟蹤executor 的 close()方法。 
這裡寫圖片描述 
再跟蹤 Executor 介面的 BaseExecutor 抽象類的 close()方法。 
這裡寫圖片描述 
這裡寫圖片描述 
從以上程式碼可知,在 SqlSession 進行關閉時,會將事務回滾後關閉。所以對於 Mybatis程式,無需通過顯示的對 SqlSession 進行回滾,達到事務回滾的目的。