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

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

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

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

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

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

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

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

其它所有的屬性將會(huì)被自動(dòng)加載,只要它們的列名和屬性名相匹配。

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

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

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

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

所以還有另外一種方法。

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

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

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

<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é)果能夠擁有唯一且清晰的名字,我們?cè)O(shè)置的別名。 這使得進(jìn)行映射非常簡(jiǎn)單。現(xiàn)在我們可以映射這個(gè)結(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é)果映射來加載作者對(duì)象的實(shí)例。

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

現(xiàn)在,上面的示例使用了外部的結(jié)果映射元素來映射關(guān)聯(lián)。這使得 Author 的結(jié)果映射可以被重用。 然而,如果你不打算重用它,或者你更喜歡將你所有的結(jié)果映射放在一個(gè)具有描述性的結(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)有一個(gè)共同作者(co-author)該怎么辦?select 語句看起來會(huì)是這樣的:

<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)使用多個(gè)結(jié)果集時(shí),該屬性指定結(jié)果集中用于與 foreignColumn 匹配的列(多個(gè)列名以逗號(hào)隔開),以識(shí)別關(guān)系中的父類型與子類型。
foreignColumn 指定外鍵對(duì)應(yīng)的列名,指定的列將與父類型中 column 的給出的列進(jìn)行匹配。
resultSet 指定用于加載復(fù)雜類型的結(jié)果集名字。

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

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

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

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM AUTHOR WHERE ID = #{id}

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

<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)在上面看到了如何處理“有一個(gè)”類型的關(guān)聯(lián)。但是該怎么處理“有很多個(gè)”類型的關(guān)聯(lián)呢?這就是我們接下來要介紹的。



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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)