既然你已經知道如何配置 MyBatis 和創建映射文件,你就已經準備好來提升技能了。MyBatis 的 Java API 就是你收穫你所做的努力的地方。正如你即將看到的,和 JDBC 相比,MyBatis 很大程度簡化了你的代碼並保持代碼簡潔,容易理解並維護。MyBatis 3 已經引入了很多重要的改進來使得 SQL 映射更加優秀。
應用目錄結構
在我們深入 Java API 之前,理解關於目錄結構的最佳實踐是很重要的。MyBatis 非常靈活,你可以用你自己的文件來做幾乎所有的事情。但是對於任一框架,都有一些最佳的方式。
讓我們看一下典型的應用目錄結構:
當然這是推薦的目錄結構,並非強制要求,但是使用一個通用的目錄結構將更利於大家溝通。
這部分內容剩餘的示例將假設你使用了這種目錄結構。
SqlSessions
使用 MyBatis 的主要 Java 接口就是 SqlSession。你可以通過這個接口來執行命令,獲取映射器和管理事務。我們會概括討論一下 SqlSession 本身,但是首先我們還是要了解如何獲取一個 SqlSession 實例。SqlSessions 是由 SqlSessionFactory 實例創建的。SqlSessionFactory 對象包含創建 SqlSession 實例的所有方法。而 SqlSessionFactory 本身是由 SqlSessionFactoryBuilder 創建的,它可以從 XML、註解或手動配置 Java 代碼來創建 SqlSessionFactory。
當 Mybatis 與一些依賴注入框架(如 Spring 或者 Guice)同時使用時,SqlSessions 將被依賴注入框架所創建,所以你不需要使用 SqlSessionFactoryBuilder 或者 SqlSessionFactory,可以直接看 SqlSession 這一節。請參考 Mybatis-Spring 或者 Mybatis-Guice 手冊了解更多信息。
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 有五個 build() 方法,每一種都允許你從不同的資源中創建一個 SqlSession 實例。
第一種方法是最常用的,它使用了一個參照了 XML 文檔或上面討論過的更特定的 mybatis-config.xml 文件的 Reader 實例。可選的參數是 environment 和 properties。environment 決定加載哪種環境,包括數據源和事務管理器。比如:
如果你調用了參數有 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 的示例:
注意到這裡我們使用了 Resources 工具類,這個類在 org.apache.ibatis.io 包中。Resources 類正如其名,會幫助你從類路徑下、文件系統或一個 web URL 中加載資源文件。看一下這個類的原始碼或者通過你的 IDE 來查看,就會看到一整套相當實用的方法。這裡給出一個簡表:
最後一個 build 方法的參數為 Configuration 實例。configuration 類包含你可能需要了解 SqlSessionFactory 實例的所有內容。Configuration 類對於配置的自查很有用,它包含查找和操作 SQL 映射(當應用接收請求時便不推薦使用)。作為一個 Java API 的 configuration 類具有所有配置的開關,這些你已經了解了。這裡有一個簡單的示例,教你如何手動配置 configuration 實例,然後將它傳遞給 build() 方法來創建 SqlSessionFactory。
現在你就獲得一個可以用來創建 SqlSession 實例的 SqlSessionFactory 了!
SqlSessionFactory
SqlSessionFactory 有六個方法創建 SqlSession 實例。通常來說,當你選擇這些方法時你需要考慮以下幾點:
事務處理:我需要在 session 使用事務或者使用自動提交功能(auto-commit)嗎?(通常意味著很多資料庫和/或 JDBC 驅動沒有事務)連接:我需要依賴 MyBatis 獲得來自數據源的配置嗎?還是使用自己提供的配置?執行語句:我需要 MyBatis 復用預處理語句和/或批量更新語句(包括插入和刪除)嗎?
基於以上需求,有下列已重載的多個 openSession() 方法供使用。
默認的 openSession()方法沒有參數,它會創建有如下特性的 SqlSession:
會開啟一個事務(也就是不自動提交)。 將從由當前環境配置的 DataSource 實例中獲取 Connection 對象。事務隔離級別將會使用驅動或數據源的默認設置。預處理語句不會被復用,也不會批量處理更新。
這些方法大都是可讀性強的。向 autoCommit 可選參數傳遞 true 值即可開啟自動提交功能。若要使用自己的 Connection 實例,傳遞一個 Connection 實例給 connection 參數即可。注意並未覆寫同時設置 Connection 和 autoCommit 兩者的方法,因為 MyBatis 會使用正在使用中的、設置了 Connection 的環境。MyBatis 為事務隔離級別調用使用了一個 Java 枚舉包裝器,稱為 TransactionIsolationLevel,若不使用它,將使用 JDBC 所支持五個隔離級(NONE、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE),並按它們預期的方式來工作。
還有一個可能對你來說是新見到的參數,就是 ExecutorType。這個枚舉類型定義了三個值:
ExecutorType.SIMPLE:這個執行器類型不做特殊的事情。它為每個語句的執行創建一個新的預處理語句。ExecutorType.REUSE:這個執行器類型會復用預處理語句。ExecutorType.BATCH:這個執行器會批量執行所有更新語句,如果 SELECT 在它們中間執行,必要時請把它們區分開來以保證行為的易讀性。
在 SqlSessionFactory 中還有一個方法我們沒有提及,就是 getConfiguration()。這個方法會返回一個 Configuration 實例,在運行時你可以使用它來自檢 MyBatis 的配置。
如果你使用的是 MyBatis 之前的版本,你要重新調用 openSession,因為舊版本的 session、事務和批量操作是分離開來的。如果使用的是新版本,那麼就不必這麼做了,因為它們現在都包含在 session 的作用域內了。你不必再單獨處理事務或批量操作就能得到想要的全部效果。
SqlSession
正如上面所提到的,SqlSession 實例在 MyBatis 中是非常強大的一個類。在這裡你會看到所有執行語句、提交或回滾事務和獲取映射器實例的方法。
在 SqlSession 類中有超過 20 個方法,所以將它們組合成易於理解的分組。
執行語句方法
這些方法被用來執行定義在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 語句。它們都會自行解釋,每一句都使用語句的 ID 屬性和參數對象,參數可以是原生類型(自動裝箱或包裝類)、JavaBean、POJO 或 Map。
selectOne 和 selectList 的不同僅僅是 selectOne 必須返回一個對象或 null 值。如果返回值多於一個,那麼就會拋出異常。如果你不知道返回對象的數量,請使用 selectList。如果需要查看返回對象是否存在,可行的方案是返回一個值即可(0 或 1)。selectMap 稍微特殊一點,因為它會將返回的對象的其中一個屬性作為 key 值,將對象作為 value 值,從而將多結果集轉為 Map 類型值。因為並不是所有語句都需要參數,所以這些方法都重載成不需要參數的形式。
最後,還有 select 方法的三個高級版本,它們允許你限制返回行數的範圍,或者提供自定義結果控制邏輯,這通常在數據集合龐大的情形下使用。
RowBounds 參數會告訴 MyBatis 略過指定數量的記錄,還有限制返回結果的數量。RowBounds 類有一個構造方法來接收 offset 和 limit,另外,它們是不可二次賦值的。
所以在這方面,不同的驅動能夠取得不同級別的高效率。為了取得最佳的表現,請使用結果集的 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 的類型(換句話說:不用 FORWARD_ONLY)。
ResultHandler 參數允許你按你喜歡的方式處理每一行。你可以將它添加到 List 中、創建 Map 和 Set,或者丟棄每個返回值都可以,它取代了僅保留執行語句過後的總結果列表的死板結果。你可以使用 ResultHandler 做很多事,並且這是 MyBatis 自身內部會使用的方法,以創建結果集列表。
Since 3.4.6, ResultHandler passed to a CALLABLE statement is used on every REFCURSOR output parameter of the stored procedure if there is any.
它的接口很簡單。
ResultContext 參數允許你訪問結果對象本身、被創建的對象數目、以及返回值為 Boolean 的 stop 方法,你可以使用此 stop 方法來停止 MyBatis 加載更多的結果。
使用 ResultHandler 的時候需要注意以下兩種限制:
從被 ResultHandler 調用的方法返回的數據不會被緩存。當使用結果映射集(resultMap)時,MyBatis 大多數情況下需要數行結果來構造外鍵對象。如果你正在使用 ResultHandler,你可以給出外鍵(association)或者集合(collection)尚未賦值的對象。
批量立即更新方法
有一個方法可以刷新(執行)存儲在 JDBC 驅動類中的批量更新語句。當你將 ExecutorType.BATCH 作為 ExecutorType 使用時可以採用此方法。
事務控制方法
控制事務作用域有四個方法。當然,如果你已經設置了自動提交或你正在使用外部事務管理器,這就沒有任何效果了。然而,如果你正在使用 JDBC 事務管理器,由Connection 實例來控制,那麼這四個方法就會派上用場:
默認情況下 MyBatis 不會自動提交事務,除非它偵測到有插入、更新或刪除操作改變了資料庫。如果你已經做出了一些改變而沒有使用這些方法,那麼你可以傳遞 true 值到 commit 和 rollback 方法來保證事務被正常處理(注意,在自動提交模式或者使用了外部事務管理器的情況下設置 force 值對 session 無效)。很多時候你不用調用 rollback(),因為 MyBatis 會在你沒有調用 commit 時替你完成回滾操作。然而,如果你需要在支持多提交和回滾的 session 中獲得更多細粒度控制,你可以使用回滾操作來達到目的。
注意 MyBatis-Spring 和 MyBatis-Guice 提供了聲明事務處理,所以如果你在使用 Mybatis 的同時使用了Spring 或者 Guice,那麼請參考它們的手冊以獲取更多的內容。
本地緩存
Mybatis 使用到了兩種緩存:本地緩存(local cache)和二級緩存(second level cache)。
每當一個新 session 被創建,MyBatis 就會創建一個與之相關聯的本地緩存。任何在 session 執行過的查詢語句本身都會被保存在本地緩存中,那麼,相同的查詢語句和相同的參數所產生的更改就不會二度影響資料庫了。本地緩存會被增刪改、提交事務、關閉事務以及關閉 session 所清空。
默認情況下,本地緩存數據可在整個 session 的周期內使用,這一緩存需要被用來解決循環引用錯誤和加快重複嵌套查詢的速度,所以它可以不被禁用掉,但是你可以設置 localCacheScope=STATEMENT 表示緩存僅在語句執行時有效。
注意,如果 localCacheScope 被設置為 SESSION,那麼 MyBatis 所返回的引用將傳遞給保存在本地緩存裡的相同對象。對返回的對象(例如 list)做出任何更新將會影響本地緩存的內容,進而影響存活在 session 生命周期中的緩存所返回的值。因此,不要對 MyBatis 所返回的對象作出更改,以防後患。
你可以隨時調用以下方法來清空本地緩存:
確保 SqlSession 被關閉
你必須保證的最重要的事情是你要關閉所打開的任何 session。保證做到這點的最佳方式是下面的工作模式:
還有,如果你正在使用jdk 1.7以上的版本還有MyBatis 3.2以上的版本,你可以使用try-with-resources語句:
就像 SqlSessionFactory,你可以通過調用當前使用中的 SqlSession 的 getConfiguration 方法來獲得 Configuration 實例。
使用映射器
上述的各個 insert、update、delete 和 select 方法都很強大,但也有些繁瑣,可能會產生類型安全問題並且對於你的 IDE 和單元測試也沒有實質性的幫助。在上面的入門章節中我們已經看到了一個使用映射器的示例。
因此,一個更通用的方式來執行映射語句是使用映射器類。一個映射器類就是一個僅需聲明與 SqlSession 方法相匹配的方法的接口類。下面的示例展示了一些方法籤名以及它們是如何映射到 SqlSession 上的。
總之,每個映射器方法籤名應該匹配相關聯的 SqlSession 方法,而字符串參數 ID 無需匹配。相反,方法名必須匹配映射語句的 ID。
此外,返回類型必須匹配期望的結果類型,單返回值時為所指定類的值,多返回值時為數組或集合。所有常用的類型都是支持的,包括:原生類型、Map、POJO 和 JavaBean。
映射器接口不需要去實現任何接口或繼承自任何類。只要方法可以被唯一標識對應的映射語句就可以了。
映射器接口可以繼承自其他接口。當使用 XML 來構建映射器接口時要保證語句被包含在合適的命名空間中。而且,唯一的限制就是你不能在兩個繼承關係的接口中擁有相同的方法籤名(潛在的危險做法不可取)。
你可以傳遞多個參數給一個映射器方法。如果你這樣做了,默認情況下它們將會以 "param" 字符串緊跟著它們在參數列表中的位置來命名,比如:#{param1}、#{param2}等。如果你想改變參數的名稱(只在多參數情況下),那麼你可以在參數上使用 @Param("paramName") 註解。
你也可以給方法傳遞一個 RowBounds 實例來限制查詢結果。
映射器註解
因為最初設計時,MyBatis 是一個 XML 驅動的框架。配置信息是基於 XML 的,而且映射語句也是定義在 XML 中的。而到了 MyBatis 3,就有新選擇了。MyBatis 3 構建在全面且強大的基於 Java 語言的配置 API 之上。這個配置 API 是基於 XML 的 MyBatis 配置的基礎,也是新的基於註解配置的基礎。註解提供了一種簡單的方式來實現簡單映射語句,而不會引入大量的開銷。
注意 不幸的是,Java 註解的的表達力和靈活性十分有限。儘管很多時間都花在調查、設計和試驗上,最強大的 MyBatis 映射並不能用註解來構建——並不是在開玩笑,的確是這樣。比方說,C#屬性就沒有這些限制,因此 MyBatis.NET 將會比 XML 有更豐富的選擇。也就是說,基於 Java 註解的配置離不開它的特性。
註解如下表所示:
映射申明樣例
這個例子展示了如何使用 @SelectKey 註解來在插入前讀取資料庫序列的值:
這個例子展示了如何使用 @SelectKey 註解來在插入後讀取資料庫識別列的值:
這個例子展示了如何使用 @Flush 註解去調用 SqlSession#flushStatements():
這些例子展示了如何通過指定 @Result 的 id 屬性來命名結果集:
這個例子展示了單一參數使用 @SqlProvider 註解:
這個例子展示了多參數使用 @SqlProvider 註解: