MyBatis 3 結(jié)果映射-關(guān)聯(lián)

2022-04-09 14:39 更新

?關(guān)聯(lián)

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

關(guān)聯(lián)(?association?)元素處理“有一個”類型的關(guān)系。 比如,在我們的示例中,一個博客有一個用戶。關(guān)聯(lián)結(jié)果映射和其它類型的映射工作方式差不多。 你需要指定目標(biāo)屬性名以及屬性的?javaType?(很多時候 MyBatis 可以自己推斷出來),在必要的情況下你還可以設(shè)置 ?JDBC ?類型,如果你想覆蓋獲取結(jié)果值的過程,還可以設(shè)置類型處理器。

關(guān)聯(lián)的不同之處是,你需要告訴 MyBatis 如何加載關(guān)聯(lián)。MyBatis 有兩種不同的方式加載關(guān)聯(lián):

  • 嵌套 ?Select ?查詢:通過執(zhí)行另外一個 SQL 映射語句來加載期望的復(fù)雜類型。
  • 嵌套結(jié)果映射:使用嵌套的結(jié)果映射來處理連接結(jié)果的重復(fù)子集。

首先,先讓我們來看看這個元素的屬性。你將會發(fā)現(xiàn),和普通的結(jié)果映射相比,它只在 ?select ?和 ?resultMap ?屬性上有所不同。

屬性 描述
property 映射到列結(jié)果的字段或?qū)傩浴H绻脕砥ヅ涞?JavaBean 存在給定名字的屬性,那么它將會被使用。否則 MyBatis 將會尋找給定名稱的字段。 無論是哪一種情形,你都可以使用通常的點式分隔形式進行復(fù)雜屬性導(dǎo)航。 比如,你可以這樣映射一些簡單的東西:“username”,或者映射到一些復(fù)雜的東西上:“address.street.number”。
javaType 一個 Java 類的完全限定名,或一個類型別名(關(guān)于內(nèi)置的類型別名,可以參考上面的表格)。 如果你映射到一個 JavaBean,MyBatis 通常可以推斷類型。然而,如果你映射到的是 HashMap,那么你應(yīng)該明確地指定 javaType 來保證行為與期望的相一致。
jdbcType JDBC 類型,所支持的 JDBC 類型參見這個表格之前的“支持的 JDBC 類型”。 只需要在可能執(zhí)行插入、更新和刪除的且允許空值的列上指定 JDBC 類型。這是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 編程,你需要對可能存在空值的列指定這個類型。
typeHandler 我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的類型處理器。 這個屬性值是一個類型處理器實現(xiàn)類的完全限定名,或者是類型別名。

關(guān)聯(lián)的嵌套 Select 查詢

屬性 描述
column 數(shù)據(jù)庫中的列名,或者是列的別名。一般情況下,這和傳遞給 resultSet.getString(columnName) 方法的參數(shù)一樣。 注意:在使用復(fù)合主鍵的時候,你可以使用 column="{prop1=col1,prop2=col2}" 這樣的語法來指定多個傳遞給嵌套 Select 查詢語句的列名。這會使得 prop1 和 prop2 作為參數(shù)對象,被設(shè)置為對應(yīng)嵌套 Select 語句的參數(shù)。
select 用于加載復(fù)雜類型屬性的映射語句的 ID,它會從 column 屬性指定的列中檢索數(shù)據(jù),作為參數(shù)傳遞給目標(biāo) select 語句。 具體請參考下面的例子。注意:在使用復(fù)合主鍵的時候,你可以使用 column="{prop1=col1,prop2=col2}" 這樣的語法來指定多個傳遞給嵌套 Select 查詢語句的列名。這會使得 prop1 和 prop2 作為參數(shù)對象,被設(shè)置為對應(yīng)嵌套 Select 語句的參數(shù)。
fetchType 可選的。有效值為 lazy 和 eager。 指定屬性后,將在映射中忽略全局配置參數(shù) 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>

就是這么簡單。我們有兩個 select 查詢語句:一個用來加載博客(Blog),另外一個用來加載作者(Author),而且博客的結(jié)果映射描述了應(yīng)該使用 ?selectAuthor ?語句加載它的 ?author ?屬性。

其它所有的屬性將會被自動加載,只要它們的列名和屬性名相匹配。

這種方式雖然很簡單,但在大型數(shù)據(jù)集或大型數(shù)據(jù)表上表現(xiàn)不佳。這個問題被稱為“N+1 查詢問題”。 概括地講,N+1 查詢問題是這樣子的:

  • 你執(zhí)行了一個單獨的 SQL 語句來獲取結(jié)果的一個列表(就是“+1”)。
  • 對列表返回的每條記錄,你執(zhí)行一個 select 查詢語句來為每條記錄加載詳細信息(就是“N”)。

這個問題會導(dǎo)致成百上千的 SQL 語句被執(zhí)行。有時候,我們不希望產(chǎn)生這樣的后果。

好消息是,MyBatis 能夠?qū)@樣的查詢進行延遲加載,因此可以將大量語句同時運行的開銷分散開來。 然而,如果你加載記錄列表之后立刻就遍歷列表以獲取嵌套的數(shù)據(jù),就會觸發(fā)所有的延遲加載查詢,性能可能會變得很糟糕。

所以還有另外一種方法。

關(guān)聯(lián)的嵌套結(jié)果映射

屬性 描述
resultMap 結(jié)果映射的 ID,可以將此關(guān)聯(lián)的嵌套結(jié)果集映射到一個合適的對象樹中。 它可以作為使用額外 select 語句的替代方案。它可以將多表連接操作的結(jié)果映射成一個單一的 ResultSet。這樣的 ResultSet 有部分?jǐn)?shù)據(jù)是重復(fù)的。 為了將結(jié)果集正確地映射到嵌套的對象樹中, MyBatis 允許你“串聯(lián)”結(jié)果映射,以便解決嵌套結(jié)果集的問題。使用嵌套結(jié)果映射的一個例子在表格以后。
columnPrefix 當(dāng)連接多個表時,你可能會不得不使用列別名來避免在 ResultSet 中產(chǎn)生重復(fù)的列名。指定 columnPrefix 列名前綴允許你將帶有這些前綴的列映射到一個外部的結(jié)果映射中。 詳細說明請參考后面的例子。
notNullColumn 默認情況下,在至少一個被映射到屬性的列不為空時,子對象才會被創(chuàng)建。 你可以在這個屬性上指定非空的列來改變默認行為,指定后,Mybatis 將只在這些列非空時才創(chuàng)建一個子對象??梢允褂枚禾柗指魜碇付ǘ鄠€列。默認值:未設(shè)置(unset)。
autoMapping 如果設(shè)置這個屬性,MyBatis 將會為本結(jié)果映射開啟或者關(guān)閉自動映射。 這個屬性會覆蓋全局的屬性 autoMappingBehavior。注意,本屬性對外部的結(jié)果映射無效,所以不能搭配 select 或 resultMap 元素使用。默認值:未設(shè)置(unset)。

之前,你已經(jīng)看到了一個非常復(fù)雜的嵌套關(guān)聯(lián)的例子。 下面的例子則是一個非常簡單的例子,用于演示嵌套結(jié)果映射如何工作。 現(xiàn)在我們將博客表和作者表連接在一起,而不是執(zhí)行一個獨立的查詢語句,就像這樣:

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

注意查詢中的連接,以及為確保結(jié)果能夠擁有唯一且清晰的名字,我們設(shè)置的別名。 這使得進行映射非常簡單。現(xiàn)在我們可以映射這個結(jié)果:

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

在上面的例子中,你可以看到,博客(Blog)作者(author)的關(guān)聯(lián)元素委托名為 “authorResult” 的結(jié)果映射來加載作者對象的實例。

非常重要: id 元素在嵌套結(jié)果映射中扮演著非常重要的角色。你應(yīng)該總是指定一個或多個可以唯一標(biāo)識結(jié)果的屬性。 雖然,即使不指定這個屬性,MyBatis 仍然可以工作,但是會產(chǎn)生嚴(yán)重的性能問題。 只需要指定可以唯一標(biāo)識結(jié)果的最少屬性。顯然,你可以選擇主鍵(復(fù)合主鍵也可以)。

現(xiàn)在,上面的示例使用了外部的結(jié)果映射元素來映射關(guān)聯(lián)。這使得 Author 的結(jié)果映射可以被重用。 然而,如果你不打算重用它,或者你更喜歡將你所有的結(jié)果映射放在一個具有描述性的結(jié)果映射元素中。 你可以直接將結(jié)果映射作為子元素嵌套在內(nèi)。這里給出使用這種方式的等效例子:

<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 的結(jié)果映射定義如下:

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

由于結(jié)果中的列名與結(jié)果映射中的列名不同。你需要指定 ?columnPrefix ?以便重復(fù)使用該結(jié)果映射來映射 ?co-author? 的結(jié)果。

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

關(guān)聯(lián)的多結(jié)果集(ResultSet)

屬性 描述
column 當(dāng)使用多個結(jié)果集時,該屬性指定結(jié)果集中用于與 foreignColumn 匹配的列(多個列名以逗號隔開),以識別關(guān)系中的父類型與子類型。
foreignColumn 指定外鍵對應(yīng)的列名,指定的列將與父類型中 column 的給出的列進行匹配。
resultSet 指定用于加載復(fù)雜類型的結(jié)果集名字。

從版本 3.2.3 開始,MyBatis 提供了另一種解決 N+1 查詢問題的方法。

某些數(shù)據(jù)庫允許存儲過程返回多個結(jié)果集,或一次性執(zhí)行多個語句,每個語句返回一個結(jié)果集。 我們可以利用這個特性,在不使用連接的情況下,只訪問數(shù)據(jù)庫一次就能獲得相關(guān)數(shù)據(jù)。

在例子中,存儲過程執(zhí)行下面的查詢并返回兩個結(jié)果集。第一個結(jié)果集會返回博客(Blog)的結(jié)果,第二個則返回作者(Author)的結(jié)果。

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM AUTHOR WHERE ID = #{id}

在映射語句中,必須通過 ?resultSets ?屬性為每個結(jié)果集指定一個名字,多個名字使用逗號隔開。

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

現(xiàn)在我們可以指定使用 “authors” 結(jié)果集的數(shù)據(jù)來填充 “author” 關(guān)聯(lián):

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

你已經(jīng)在上面看到了如何處理“有一個”類型的關(guān)聯(lián)。但是該怎么處理“有很多個”類型的關(guān)聯(lián)呢?這就是我們接下來要介紹的。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號