1. 程式人生 > >MyBatis之Mapper XML 文件詳解(四)-JDBC 類型和嵌套查詢

MyBatis之Mapper XML 文件詳解(四)-JDBC 類型和嵌套查詢

調用 表格 outer model sele 復雜 普通 全局配置 當前

支持的 JDBC 類型
為了未來的參考,MyBatis 通過包含的 jdbcType 枚舉型,支持下面的 JDBC 類型。
BIT
FLOAT
CHAR
TIMESTAMP
OTHER
UNDEFINED
TINYINT
REAL
VARCHAR
BINARY
BLOB
NVARCHAR
SMALLINT
DOUBLE
LONGVARCHAR
VARBINARY
CLOB
NCHAR
INTEGER
NUMERIC
DATE
LONGVARBINARY
BOOLEAN
NCLOB
BIGINT
DECIMAL
TIME
NULL
CURSOR
ARRAY


構造方法
對於大多數數據傳輸對象(Data Transfer Object,DTO)類型,屬性可以起作用,而且像 你絕大多數的領域模型, 指令也許是你想使不可變類的地方。 通常包含引用或查詢數 據的表很少或基本不變的話對不可變類來說是合適的。 構造方法註入允許你在初始化時 為類設置屬性的值,而不用暴露出公有方法。MyBatis 也支持私有屬性和私有 JavaBeans 屬 性來達到這個目的,但是一些人更青睞構造方法註入。構造方法元素支持這個。
看看下面這個構造方法:

public class User {
//...
public User(Integer id, String username, int age) {
//...
}
//...
}

為了將結果註入構造方法,MyBatis需要通過某種方式定位相應的構造方法。 在下面的例子中,MyBatis搜索一個聲明了三個形參的的構造方法,以 java.lang.Integer, java.lang.String and int 的順序排列。

<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>

當你在處理一個帶有多個形參的構造方法時,對arg元素順序的維持是很容易出錯的。為了能利用構造方法形參的name來對形參進行引用,你可以添加 @Param 註解或者使用‘-parameters‘編譯選項和啟用 useActualParamName (此選項默認開啟)來編譯工程。 下面的例子對於同一個構造方法依然是有效的,盡管第二和第三個形參順序與構造方法中聲明的順序不匹配。

<constructor>
<idArg column="id" javaType="int" name="id" />
<arg column="age" javaType="_int" name="age" />
<arg column="username" javaType="String" name="username" />
</constructor>

如果存在具有同名和相同類型的屬性,那麽它的 javaType 可以省略。
剩余的屬性和規則和固定的 id 和 result 元素是相同的。

column:來自數據庫的類名,或重命名的列標簽。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。
javaType:一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列表)。 如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 果你映射到的是 HashMap,那麽你應該明確地指定 javaType 來保證所需的 行為。
jdbcType:在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。
typeHandler:我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現, 或者是類型別名。
select:用於加載復雜類型屬性的映射語句的ID,從column中檢索出來的數據,將作為此select語句的參數。具體請參考Association標簽。
resultMap:ResultMap的ID,可以將嵌套的結果集映射到一個合適的對象樹中,功能和select屬性相似,它可以實現將多表連接操作的結果映射成一個單一的ResultSet。這樣的ResultSet將會將包含重復或部分數據重復的結果集正確的映射到嵌套的對象樹中。為了實現它, MyBatis允許你 “串聯” ResultMap,以便解決嵌套結果集的問題。想了解更多內容,請參考下面的Association元素。
name:構造方法形參的名字。通過指定具體的名字你可以以任意順序寫入arg元素。參看上面的解釋。


關聯

<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>

關聯元素處理“有一個”類型的關系。比如,在我們的示例中,一個博客有一個用戶。 關聯映射就工作於這種結果之上。你指定了目標屬性,來獲取值的列,屬性的 java 類型(很 多情況下 MyBatis 可以自己算出來) ,如果需要的話還有 jdbc 類型,如果你想覆蓋或獲取的 結果值還需要類型控制器。


關聯中不同的是你需要告訴 MyBatis 如何加載關聯。MyBatis 在這方面會有兩種不同的 方式:
嵌套查詢:通過執行另外一個 SQL 映射語句來返回預期的復雜類型。


嵌套結果:使用嵌套結果映射來處理重復的聯合結果的子集。首先,然讓我們來查看這個元素的屬性。所有的你都會看到,它和普通的只由 select 和 resultMap 屬性的結果映射不同。

property:映射到列結果的字段或屬性。如果匹配的是存在的,和給定名稱相同的 property JavaBeans 的屬性, 那麽就會使用。 否則 MyBatis 將會尋找給定名稱的字段。 這兩種情形你可以使用通常點式的復雜屬性導航。比如,你可以這樣映射 一 些 東 西 :“ username ”, 或 者 映 射 到 一 些 復 雜 的 東 西 : “address.street.number” 。

javaType:一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名的列 表) 。如果你映射到一個 JavaBean,MyBatis 通常可以斷定類型。然而,如 javaType 果你映射到的是 HashMap,那麽你應該明確地指定 javaType 來保證所需的 行為。

jdbcType:在這個表格之前的所支持的 JDBC 類型列表中的類型。JDBC 類型是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC 編程,你需要指定這個類型-但 僅僅對可能為空的值。

typeHandler:我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的 typeHandler 類型處理器。 這個屬性值是類的完全限定名或者是一個類型處理器的實現, 或者是類型別名。


關聯的嵌套查詢

column:來自數據庫的類名,或重命名的列標簽。這和通常傳遞給 resultSet.getString(columnName)方法的字符串是相同的。 column 註 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數對象形式來設置給目標嵌套查詢語句。

select:另外一個映射語句的 ID,可以加載這個屬性映射需要的復雜類型。獲取的 在列屬性中指定的列的值將被傳遞給目標 select 語句作為參數。表格後面 有一個詳細的示例。 select 註 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給嵌套查詢語 句。這會引起 prop1 和 prop2 以參數對象形式來設置給目標嵌套查詢語句。

fetchType:可選的。有效值為 lazy和eager。 如果使用了,它將取代全局配置參數lazyLoadingEnabled。
示例:

<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

我們有兩個查詢語句:一個來加載博客,另外一個來加載作者,而且博客的結果映射描 述了“selectAuthor”語句應該被用來加載它的 author 屬性。 其他所有的屬性將會被自動加載,假設它們的列和屬性名相匹配。

這種方式很簡單, 但是對於大型數據集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:


  • 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)。


  • 對返回的每條記錄,你執行了一個查詢語句來為每個加載細節(就是“N”)。

這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的。 MyBatis 能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消 耗。然而,如果你加載一個列表,之後迅速叠代來訪問嵌套的數據,你會調用所有的延遲加 載,這樣的行為可能是很糟糕的。

所以還有另外一種方法。
關聯的嵌套結果

resultMap:這是結果映射的 ID,可以映射關聯的嵌套結果到一個合適的對象圖中。這 是一種替代方法來調用另外一個查詢語句。這允許你聯合多個表來合成到 resultMap 一個單獨的結果集。這樣的結果集可能包含重復,數據的重復組需要被分 解,合理映射到一個嵌套的對象圖。為了使它變得容易,MyBatis 讓你“鏈 接”結果映射,來處理嵌套結果。一個例子會很容易來仿照,這個表格後 面也有一個示例。
columnPrefix:當連接多表時,你將不得不使用列別名來避免ResultSet中的重復列名。指定columnPrefix允許你映射列名到一個外部的結果集中。 請看後面的例子。
notNullColumn:默認情況下,子對象僅在至少一個列映射到其屬性非空時才創建。 通過對這個屬性指定非空的列將改變默認行為,這樣做之後Mybatis將僅在這些列非空時才創建一個子對象。 可以指定多個列名,使用逗號分隔。默認值:未設置(unset)。
autoMapping:如果使用了,當映射結果到當前屬性時,Mybatis將啟用或者禁用自動映射。 該屬性覆蓋全局的自動映射行為。 註意它對外部結果集無影響,所以在select or resultMap屬性中這個是毫無意義的。 默認值:未設置(unset)。


在上面你已經看到了一個非常復雜的嵌套關聯的示例。 下面這個是一個非常簡單的示例 來說明它如何工作。代替了執行一個分離的語句,我們聯合博客表和作者表在一起,就像:

<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>

註意這個聯合查詢, 以及采取保護來確保所有結果被唯一而且清晰的名字來重命名。 這使得映射非常簡單。現在我們可以映射這個結果:

<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>

在上面的示例中你可以看到博客的作者關聯代表著“authorResult”結果映射來加載作 者實例。
非常重要: id元素在嵌套結果映射中扮演著非 常重要的角色。你應該總是指定一個或多個可以唯一標識結果的屬性。實際上如果你不指定它的話, MyBatis仍然可以工作,但是會有嚴重的性能問題。在可以唯一標識結果的情況下, 盡可能少的選擇屬性。主鍵是一個顯而易見的選擇(即使是復合主鍵)。


現在,上面的示例用了外部的結果映射元素來映射關聯。這使得 Author 結果映射可以 重用。然而,如果你不需要重用它的話,或者你僅僅引用你所有的結果映射合到一個單獨描 述的結果映射中。你可以嵌套結果映射。這裏給出使用這種方式的相同示例:

<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</association>
</resultMap>

如果blog有一個co-author怎麽辦? select語句將看起來這個樣子:

<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
CA.id as co_author_id,
CA.username as co_author_username,
CA.password as co_author_password,
CA.email as co_author_email,
CA.bio as co_author_bio
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Author CA on B.co_author_id = CA.id
where B.id = #{id}
</select>

再次調用Author的resultMap將定義如下:

<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>

因為結果中的列名與resultMap中的列名不同。 你需要指定columnPrefix去重用映射co-author結果的resultMap。

<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author"
resultMap="authorResult" />
<association property="coAuthor"
resultMap="authorResult"
columnPrefix="co_" />
</resultMap>

上面你已經看到了如何處理“有一個”類型關聯。但是“有很多個”是怎樣的?下面這 個部分就是來討論這個主題的。


集合

<collection property="posts" ofType="domain.blog.Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>

集合元素的作用幾乎和關聯是相同的。實際上,它們也很相似,文檔的異同是多余的。 所以我們更多關註於它們的不同。

我們來繼續上面的示例,一個博客只有一個作者。但是博客有很多文章。在博客類中, 這可以由下面這樣的寫法來表示:

private List posts;
要映射嵌套結果集合到 List 中,我們使用集合元素。就像關聯元素一樣,我們可以從 連接中使用嵌套查詢,或者嵌套結果。


集合的嵌套查詢
首先,讓我們看看使用嵌套查詢來為博客加載文章。

<resultMap id="blogResult" type="Blog">
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

這裏你應該註意很多東西,但大部分代碼和上面的關聯元素是非常相似的。首先,你應 該註意我們使用的是集合元素。然後要註意那個新的“ofType”屬性。這個屬性用來區分 JavaBean(或字段)屬性類型和集合包含的類型來說是很重要的。所以你可以讀出下面這個 映射:

<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>

讀作: “在 Post 類型的 ArrayList 中的 posts 的集合。”
javaType 屬性是不需要的,因為 MyBatis 在很多情況下會為你算出來。所以你可以縮短 寫法:

<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>

集合的嵌套結果
至此,你可以猜測集合的嵌套結果是如何來工作的,因為它和關聯完全相同,除了它應 用了一個“ofType”屬性 First, let‘s look at the SQL:

<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>

我們又一次聯合了博客表和文章表,而且關註於保證特性,結果列標簽的簡單映射。現 在用文章映射集合映射博客,可以簡單寫為:

<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>

同樣,要記得 id 元素的重要性,如果你不記得了,請閱讀上面的關聯部分。
同樣, 如果你引用更長的形式允許你的結果映射的更多重用, 你可以使用下面這個替代 的映射:

<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
<id property="id" column="id"/>
<result property="subject" column="subject"/>
<result property="body" column="body"/>
</resultMap>

這個對你所映射的內容沒有深度,廣度或關聯和集合相聯合的限制。當映射它們 時你應該在大腦中保留它們的表現。 你的應用在找到最佳方法前要一直進行的單元測試和性 能測試。好在 myBatis 讓你後來可以改變想法,而不對你的代碼造成很小(或任何)影響。

高級關聯和集合映射是一個深度的主題。文檔只能給你介紹到這了。加上一點聯系,你 會很快清楚它們的用法。


鑒別器

<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>

有時一個單獨的數據庫查詢也許返回很多不同 (但是希望有些關聯) 數據類型的結果集。 鑒別器元素就是被設計來處理這個情況的, 還有包括類的繼承層次結構。 鑒別器非常容易理 解,因為它的表現很像 Java 語言中的 switch 語句。


定義鑒別器指定了 column 和 javaType 屬性。 列是 MyBatis 查找比較值的地方。 JavaType 是需要被用來保證等價測試的合適類型(盡管字符串在很多情形下都會有用)。比如:

<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>

在這個示例中, MyBatis 會從結果集中得到每條記錄, 然後比較它的 vehicle 類型的值。 如果它匹配任何一個鑒別器的實例,那麽就使用這個實例指定的結果映射。換句話說,這樣 做完全是剩余的結果映射被忽略(除非它被擴展,這在第二個示例中討論) 。如果沒有任何 一個實例相匹配,那麽 MyBatis 僅僅使用鑒別器塊外定義的結果映射。所以,如果 carResult 按如下聲明:

<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>

那麽只有 doorCount 屬性會被加載。這步完成後完整地允許鑒別器實例的獨立組,盡管 和父結果映射可能沒有什麽關系。這種情況下,我們當然知道 cars 和 vehicles 之間有關系, 如 Car 是一個 Vehicle 實例。因此,我們想要剩余的屬性也被加載。我們設置的結果映射的 簡單改變如下。

<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>

現在 vehicleResult 和 carResult 的屬性都會被加載了。
盡管曾經有些人會發現這個外部映射定義會多少有一些令人厭煩之處。 因此還有另外一 種語法來做簡潔的映射風格。比如:

<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultType="carResult">
<result property="doorCount" column="door_count" />
</case>
<case value="2" resultType="truckResult">
<result property="boxSize" column="box_size" />
<result property="extendedCab" column="extended_cab" />
</case>
<case value="3" resultType="vanResult">
<result property="powerSlidingDoor" column="power_sliding_door" />
</case>
<case value="4" resultType="suvResult">
<result property="allWheelDrive" column="all_wheel_drive" />
</case>
</discriminator>
</resultMap>

要記得 這些都是結果映射, 如果你不指定任何結果, 那麽 MyBatis 將會為你自動匹配列 和屬性。所以這些例子中的大部分是很冗長的,而其實是不需要的。也就是說,很多數據庫 是很復雜的,我們不太可能對所有示例都能依靠它。

關註微信公眾號:IT哈哈(it_haha),學習更多內容。

MyBatis之Mapper XML 文件詳解(四)-JDBC 類型和嵌套查詢