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

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

2、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"



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM