[04] 高級映射 association和collection

以前咱們提到的映射,都是簡單的字段和對象屬性一對一,假設對象的屬性也是一個對象,即涉及到兩個表的關聯,此時應該如何進行映射處理?

先看兩張表,author 和 book:
  

業務上對應關係爲,一個做者能寫多本書,可是一本書只有一個做者。對應的Java類以下:
public class Book {
    private long id;
    private String name;
    private int price;
    private Author author;

    //... getter and setter
}
public class Author {
    private long id;
    private String name;
    private int age;
    private List<Book> bookList;
    
    //... getter and setter
}

一、association 關聯

如今咱們但願經過查詢獲得一個Book類,且該類中的author屬性要求同時獲取出來,這時候已經不是簡單的數據庫字段和對象屬性的一對一映射,而涉及到兩張表,此時咱們就要用到 association 關鍵字。

association 表示一個複雜類型的關聯,能夠將許多結果包裝成這種類型。它是 resultMap 中的標籤屬性,這意味着 當你須要使用嵌套查詢返回結果,那麼你的結果映射只能選擇 resultMap,而不能再使用 resultType。

1.1 method1

使用起來和resultMap的基本結構無異,因此如上咱們提到的查詢需求,在mapper中能夠這樣寫:
<mapper namespace="dulk.learn.mybatis.dao.BookDao">

    <resultMap id="bookResultMap" type="dulk.learn.mybatis.pojo.Book">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="price" column="price" />
        <!--關聯屬性-->
        <association property="author" javaType="dulk.learn.mybatis.pojo.Author">
            <!--注:此處column應爲book中外鍵列名-->
            <id property="id" column="author_id" />
            <!--注:避免屬性重名,不然屬性值注入錯誤-->
            <result property="name" column="authorName" />
            <result property="age" column="authorAge" />
        </association>
    </resultMap>

    <!--嵌套查詢,結果映射只能使用resultMap-->
    <select id="findBookById" parameterType="long" resultMap="bookResultMap">
        SELECT
            b.*,
            a.name AS 'authorName',
            a.age  AS 'authorAge'
        FROM book b, author a
        WHERE b.author_id = a.id
        AND b.id = #{id}
    </select>
    
</mapper>

能夠看到 association 最基本的兩個屬性:
  • property - 關聯對象在類中的屬性名(即Author在Book類中的屬性名,author)
  • javaType - 關聯對象的Java類型

而association中的結構,則和resultMap無異了,一樣是id和result,可是仍然有兩個須要注意的點:
  • id中的column屬性,其值應該儘可能使用外鍵列名,主要是對於重名的處理,避免映射錯誤
  • 一樣的,對於result中的column屬性的值,也要避免重名帶來的映射錯誤,如上例若 a.name 不採用別名 "authorName",則會錯誤地將 b.name 賦值給Author的name屬性

1.2 method2

以前有提到,說 association 中結構和resultMap無異,事實上咱們也能夠直接引用其餘的resultMap,以下(注意修改id別名):
<mapper namespace="dulk.learn.mybatis.dao.BookDao">

    <!--author的resultMap-->
    <resultMap id="authorResultMap" type="dulk.learn.mybatis.pojo.Author">
        <id property="id" column="authorId"/>
        <result property="name" column="authorName"/>
        <result property="age" column="authorAge"/>
    </resultMap>

    <resultMap id="bookResultMap" type="dulk.learn.mybatis.pojo.Book">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="price" column="price"/>
        <!--引用author的resultMap-->
        <association property="author" resultMap="authorResultMap" />
    </resultMap>

    <!--注意這裏a.id的別名和authorResultMap中相對應-->
    <select id="findBookById" parameterType="long" resultMap="bookResultMap">
        SELECT
            b.*,
            a.id   AS 'authorId',
            a.name AS 'authorName',
            a.age  AS 'authorAge'
        FROM book b, author a
        WHERE b.author_id = a.id
        AND b.id = #{id}
    </select>
</mapper>

1.3 method3

最後,還有一種方式,就是咱們先查詢出author,再將其放到book中去,至關於查詢語句分爲兩次,只是最終結果交給MyBatis來幫咱們組裝,這種方式利用了 association 的 select 屬性,同時還須要另寫 author 的查詢sql,book 的查詢sql也能夠不用再聯表。這種方式至關於兩次查詢,性能和效率較低,並不提倡。如上例使用這樣的方式,則以下:
<mapper namespace="dulk.learn.mybatis.dao.BookDao">

    <resultMap id="bookResultMap" type="dulk.learn.mybatis.pojo.Book">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="price" column="price"/>
        <!--使用select屬性進行查詢關聯-->
        <association property="author" column="author_id" javaType="dulk.learn.mybatis.pojo.Author" select="findAuthorById"/>
    </resultMap>

    <!--簡化了book的查詢語句,再也不須要與其餘表關聯-->
    <select id="findBookById" parameterType="long" resultMap="bookResultMap">
        SELECT b.*
        FROM book b
        WHERE b.id = #{id}
    </select>

    <!--新增了author表的查詢語句,將會被調用獲取結果並組裝給book-->
    <select id="findAuthorById" parameterType="long" resultType="dulk.learn.mybatis.pojo.Author">
        SELECT *
        FROM author
        WHERE id = #{id}
    </select>

</mapper>

二、collection 集合

有了對 association 的認識,使用 collection 其實也就無非是依葫蘆畫瓢了,一樣只能是 resultMap,一樣須要注意列名重複的問題,一樣能夠引用resultMap或者使用select。下面索性直接看個例子吧,即獲取一個Author做者,其中包含屬性 List<Book>:
<mapper namespace="dulk.learn.mybatis.dao.AuthorDao">

    <resultMap id="authorResultMap" type="dulk.learn.mybatis.pojo.Author">
        <id property="id" column="id"/>
        <result property="name" column="name" />
        <result property="age" column="age" />
        <!--使用collection屬性,ofType爲集合內元素的類型-->
        <collection property="bookList" ofType="dulk.learn.mybatis.pojo.Book" columnPrefix="book_">
            <id property="id" column="id"/>
            <result property="name" column="name" />
            <result property="price" column="price" />
        </collection>
    </resultMap>

    <select id="findById" parameterType="long" resultMap="authorResultMap">
        SELECT a.*, b.id AS 'book_id', b.name AS 'book_name', b.price AS 'book_price'
        FROM author a, book b
        WHERE a.id = b.author_id
        AND a.id = #{authorId}
    </select>

</mapper>

另外延伸一下關於避免字段重名的方式,如上例 select 中,列名的別名都增長了前綴 "book_",那麼在collection中進行映射描時,就有兩種方式:
  • 第一種即 column 的值和列名徹底一致,如 column="book_id"
  • 第二種也就是推薦的方式,在 collection 中使用屬性 columnPrefix 來定義統一前綴,在接下來的 column 中就能夠減小工做量了,如上例中 columnPrefix = "book_",column = "id",它們的效果等同於 column = "book_id"
相關文章
相關標籤/搜索