Mybatis最入門—ResultMaps高階用法(上)

NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

[一步是咫尺,一步即天涯]

接上文,我們基本的單表查詢使用上文中的方式已經能夠達到目的。但是,我們日常的業務中也存在著多表關聯查詢,結果是複雜的資料集合等等。本文我們就來介紹ResultMaps的高階用法,本文,我們先介紹基本的概念,具體用法例項在下一篇中專門演示給大家。敬請期待!

——————————————————————————————————————————————————–

1.首先,我們先看看一個常見的部落格頁面的組成,如下:

a.頁面上能夠展示的部分:正文,標題,日期,作者,評論正文,評論時間,評論人等等

b.頁面之外的部分:使用者名稱,使用者id,使用者密碼,使用者基本資訊(電話,郵箱,地址,興趣,特長,等等)

2.將我們頁面上的資訊從資料庫中查出來的SQL語句轉化為Mapper檔案中的語句,可能是如下內容:

<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
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,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>

其對應著非常複雜的結果集合,Mapper檔案可能長這個樣子,如下:

<!-- Very Complex Result Map -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<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"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>

對於初學者而言,看到這樣的一份XML檔案,我想內心一定是崩潰的!但是,不要擔心,我們日常開發,很少能夠遇到這樣的場景,並且,相信通過我們一步一步的解釋這個配置文件,以後各位也能夠運用自如。

在上面的resultMap中存在很多的子元素,下面我們來逐一解釋:

“constructor”:類在例項化時,用來注入結果到構造方法中。

“idArg”:ID引數,標記結果作為ID,可以幫助提高整體的效率。

“arg”:注入到構造方法的一個不同結果。

“id”:這個id,類似於資料庫的主鍵,能夠幫助提高整體的效率
“result”:即結果欄位,其中包括java物件的屬性值,和資料庫列名

“association”:複雜型別的結果關聯,結果對映能夠關聯自身,或者關聯另一個結果集

“collection”:複雜型別的集合,結果對映自身,或者對映結果集

“discriminator”:使用結果值來決定使用哪個結果對映

“case”:基於某些值的結果對映。嵌入結果對映,這種情形也對映到它本身,因此,能夠包含相同的元素,或者參照一個外部的結果對映。

對於resultMap標籤,上文的基礎用法中我們已經介紹了他的屬性含義。但,在此之外,還有一個屬性值為:

“autoMapping”:如果出現此配置,Mybatis將會啟用或者禁用自動匹配resultMap的功能,這個屬性將會在全域性範圍內覆蓋自動匹配機制。預設情況下是沒有這個配置的,因此,如果需要,請保持慎重。

下面,我們開始詳細說明每一個元素,如果有心急的讀者想使用前面增改刪查功能,請讀者一定按照單元測試的方法推進,千萬不要一次性配置大量屬性,以免影響學習興趣。

a.構造方法

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

儘管對於大部分的DTO物件,以及我們的domain模型,屬性值都是能夠起到相應的作用,但是,在某些情況下如我們想使用一些固定的類。如:通常情況下的表格中包括一些僅供瀏覽的資料或者很少改變的資料。Mybatis的建構函式注入功能允許我們在類初始化時就設定某些值,而不暴露其中的public方法。同時Mybatis也支援私有的屬性與私有的JavaBeans屬性來實現這個目的,儘管這樣,一些開發者還是更願意使用建構函式注入的方式。

例如,程式中我們存在這樣一個實體類,如下:

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

在Mybatis中,為了向這個構造方法中注入結果,Mybatis需要通過它的引數的型別來表示構造方法。java中,沒有反射引數名稱的方法,因此,當建立一個構造方法的元素是,必須保證引數是按照順序排列的,而且,資料型別也必須匹配!

b.關聯

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

關聯元素用來處理資料模型中的“has-one”關係。如我們上文示例的一個部落格有一個使用者。關聯對映大部分是基於這種應用場景。使用時,我們制定目標屬性,可以選用javaType,jdbcType,typeHandler等屬性來覆蓋結果集合。

關聯查詢的不同之處是,我們必須告訴告訴Mybatis如何載入關聯關係,這裡有兩種供我們選擇的方法:

巢狀查詢:即通過執行另一個預期返回複雜型別的SQL語句。

巢狀結果:使用巢狀結果對映來處理聯合結果中重複的子集。
在正式的使用之前,我們先來看看這個的屬性配置的具體含義,注意,這裡的屬性配置跟前文基本增改刪查中的區別:

屬性描述
property對映到列結果的欄位或屬性。如果匹配的是存在的,和給定名稱相同的 property JavaBeans 的屬性, 那麼就會使用。 否則 MyBatis 將會尋找給定名稱的欄位。 這兩種情形你可以使用通常點式的複雜屬性導航。比如,可以這樣對映
 :“ username ”, 或 者 映 射 到 一 些 復 雜 的屬性 : “address.street.number” 。
javaType一個 Java 類的完全限定名,或一個型別別名(參考上面內建型別別名的列 表) 。如果你對映到一個 JavaBean,MyBatis 通常可以斷定型別。然而,如 javaType 果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的行為。
jdbcType在這個表格之前的所支援的 JDBC 型別列表中的型別。JDBC 型別是僅僅 需要對插入, 更新和刪除操作可能為空的列進行處理。這是 JDBC 的需要, jdbcType 而不是 MyBatis 的。如果你直接使用 JDBC
程式設計,你需要指定這個型別-但 僅僅對可能為空的值。
typeHandler我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋預設的 typeHandler 型別處理器。 這個屬性值是類的完全限定名或者是一個型別處理器的實現, 或者是型別別名。

————————————————————————————————————————————-

現在正式的介紹這兩種方式:

1.巢狀查詢

屬性描述
column這是來自資料庫的類名,或重新命名的列標籤的值作為一個輸入引數傳遞給巢狀語句,這和通常傳遞給 resultSet.getString(columnName)方法的字串是相同的。
注 意 : 要 處 理 復 合 主 鍵 , 你 可 以 指 定 多 個 列 名 通 過 column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給巢狀查詢語 句。這會引起 prop1 和 prop2 以引數物件形式來設定給目標巢狀查詢語句
select另外一個對映語句的 ID,將會按照屬性的對映來載入複雜型別。獲取的在列屬性中指定的列的值將被傳遞給目標 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 能延遲載入這樣的查詢就是一個好處,因此你可以分散這些語句同時執行的消耗。然而,如果你載入一個列表,之後迅速迭代來訪問巢狀的資料,你會呼叫所有的延遲載入,這樣的行為可能是很糟糕的。

所以還有另外一種方法。

2.巢狀結果:

首先,我們先來看看有哪些屬效能夠供我們使用:

屬性描述
resultMap這是結果對映的 ID,可以對映關聯的巢狀結果到一個合適的物件圖中。這是一種替代方法來呼叫另外一個查詢語句。這允許你聯合多個表來合成到 resultMap 一個單獨的結果集。這樣的結果集可能包含需要被分解的相同的,重複的資料組並且合理對映到一個巢狀的物件圖。為了使它變得容易,MyBatis
讓你“連結”結果對映,來處理巢狀結果。下面給予一個很容易來仿照例子。
columnPrefix當連線多個表時,你最好使用列的別名來避免在一個結果集合中出現的名稱重複。對於制定的字首,Mybatis允許我們對映列到外部集合中,具體用法請參照後面的例子
notNullColumn只有在至少有一個非空列對映到子物件的屬性時,才建立一個預設的子物件。通過這個屬性,我們可以設定哪一個列必須有值來改變這個行為,此時的Mybatis就會按照這個非空設定來建立一個子物件。多個列存在時,可以通過逗號作為分割符。預設情況下,該屬性是不會被設定的,即unset
autoMapping如果存在此屬性的話,Mybatis會在對映到對應屬性時啟用或者禁用自動對映的功能。這個屬性將會在全域性範圍內覆蓋自動對映的功能。
注意:該屬性沒有對外部結果集造成影響。因此,在select或者結果集合中使用是沒有意義的。預設情況下,它是不設定的,即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" 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>

在上面的這個例子中,我們已經觀察到部落格的“author”關聯另外一個“resultMap”結果對映,來載入“Author”例項

特別注意的是:在巢狀結果對映中,元素“id”扮演了一個非常重要的角色。我們應該特別指明一個或者多個屬性來唯一的標識這個結果。在實際應用中,如果不指定這個“id”,Mybatis仍然能夠繼續執行,但是會產生很大的效能消耗。但是,也需要儘可能的少的選擇這些屬性,資料庫的主鍵顯然是一個非常好的選擇!

上面的例子使用了外部結果集元素來對映關聯。這樣的做法,使得id為“Authot”的結果集能夠被不斷的重用。但是,假如我們沒有重用的需求,或者,我們只是想簡單的把我們的結果對映到一個單獨描述的結果集合當中的話,就不再需要上面的方式書寫了,直接巢狀關聯結果對映就好。具體的做法如下:

<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>

針對上面的例子,加入這篇部落格存在一個“聯合作者”又該怎麼辦呢?具體的做法如下:

<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>

其對應的結果集合,如下:

<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>

由於結果集當中的列名與查詢結果當中列名不一致,我們需要使用明確指定“columnPrefix”來重用這個結果集,以此來對映“聯合作者”的查詢結果。具體寫法如下:

<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>

多結果集關聯

屬性描述
column當時用多結果集的這個屬性來指定被逗號分隔的列時,將會使得該列與foreignColumn 相關聯,從而確定了關聯列之間的父子關係
foreignColumn標識出包含foreing keys的列的名稱。這個foreing keys的值將會和父型別中指定的列屬性的值相匹配
resultSet標識這個將會從哪裡載入的複雜型別資料的結果集合的名稱

從3.2.3版本開始,Mybatis提供了另外一種方式來解決“N 1”問題

某些資料庫允許在儲存過程中返回多個結果集,或者同時執行多個語句並且每一個都返回一個結果集合。這就使得我們可以只用訪問資料庫一次,且不用使用join,就返回存在關聯的資料。

舉個例子,執行下面的語句將會返回兩個結果集合。第一個返回部落格文章的結果集合,第二個返回作者的結果集合

SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}

我們必須給予每一個結果集合一個指定的名稱。方式是:在結果集合中增加一個resultSets 屬性,來對映語句中被逗號分隔的名稱。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
{call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

現在,我們來指定:資料填充的“author”的集合包含在“authors”的結果集中

<resultMap id="blogResult" type="Blog">
<id property="id" column="id" />
<result property="title" column="title"/>
<association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="bio" column="bio"/>
</association>
</resultMap>

上面所屬的這些內容,解決了我們“has-one”的問題。但是,現實中我們也經常遇到“has-many”(先這麼叫吧)類似的問題,鑑於篇幅的關係,我們將在下文中進行介紹,敬請期待!

————————————————————————————————————————————-
至此,Mybatis最入門—ResultMaps高階用法(上)結束

備註:

第一次翻譯文件,有不清楚的地方還請各位看官見諒!

參考資料:

官方文件:http://www.mybatis.org/mybatis-3/

相關文章

程式語言 最新文章