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


業務上對應關系為,一個作者能寫多本書,但是一本書只有一個作者。對應的Java類如下:
public class Book {
private long id;
private String name;
private int price;
private Author author;
//... getter and setter
}
8
1
public class Book {
2
private long id;
3
private String name;
4
private int price;
5
private Author author;
6
7
//... getter and setter
8
}
public class Author {
private long id;
private String name;
private int age;
private List<Book> bookList;
//... getter and setter
}
8
1
public class Author {
2
private long id;
3
private String name;
4
private int age;
5
private List<Book> bookList;
6
7
//... getter and setter
8
}
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>
28
1
<mapper namespace="dulk.learn.mybatis.dao.BookDao">
2
3
<resultMap id="bookResultMap" type="dulk.learn.mybatis.pojo.Book">
4
<id property="id" column="id" />
5
<result property="name" column="name" />
6
<result property="price" column="price" />
7
<!--關聯屬性-->
8
<association property="author" javaType="dulk.learn.mybatis.pojo.Author">
9
<!--注:此處column應為book中外鍵列名-->
10
<id property="id" column="author_id" />
11
<!--注:避免屬性重名,否則屬性值注入錯誤-->
12
<result property="name" column="authorName" />
13
<result property="age" column="authorAge" />
14
</association>
15
</resultMap>
16
17
<!--嵌套查詢,結果映射只能使用resultMap-->
18
<select id="findBookById" parameterType="long" resultMap="bookResultMap">
19
SELECT
20
b.*,
21
a.name AS 'authorName',
22
a.age AS 'authorAge'
23
FROM book b, author a
24
WHERE b.author_id = a.id
25
AND b.id = #{id}
26
</select>
27
28
</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>
29
1
<mapper namespace="dulk.learn.mybatis.dao.BookDao">
2
3
<!--author的resultMap-->
4
<resultMap id="authorResultMap" type="dulk.learn.mybatis.pojo.Author">
5
<id property="id" column="authorId"/>
6
<result property="name" column="authorName"/>
7
<result property="age" column="authorAge"/>
8
</resultMap>
9
10
<resultMap id="bookResultMap" type="dulk.learn.mybatis.pojo.Book">
11
<id property="id" column="id"/>
12
<result property="name" column="name"/>
13
<result property="price" column="price"/>
14
<!--引用author的resultMap-->
15
<association property="author" resultMap="authorResultMap" />
16
</resultMap>
17
18
<!--注意這里a.id的別名和authorResultMap中相對應-->
19
<select id="findBookById" parameterType="long" resultMap="bookResultMap">
20
SELECT
21
b.*,
22
a.id AS 'authorId',
23
a.name AS 'authorName',
24
a.age AS 'authorAge'
25
FROM book b, author a
26
WHERE b.author_id = a.id
27
AND b.id = #{id}
28
</select>
29
</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>
25
1
<mapper namespace="dulk.learn.mybatis.dao.BookDao">
2
3
<resultMap id="bookResultMap" type="dulk.learn.mybatis.pojo.Book">
4
<id property="id" column="id"/>
5
<result property="name" column="name"/>
6
<result property="price" column="price"/>
7
<!--使用select屬性進行查詢關聯-->
8
<association property="author" column="author_id" javaType="dulk.learn.mybatis.pojo.Author" select="findAuthorById"/>
9
</resultMap>
10
11
<!--簡化了book的查詢語句,不再需要與其他表關聯-->
12
<select id="findBookById" parameterType="long" resultMap="bookResultMap">
13
SELECT b.*
14
FROM book b
15
WHERE b.id = #{id}
16
</select>
17
18
<!--新增了author表的查詢語句,將會被調用獲取結果並組裝給book-->
19
<select id="findAuthorById" parameterType="long" resultType="dulk.learn.mybatis.pojo.Author">
20
SELECT *
21
FROM author
22
WHERE id = #{id}
23
</select>
24
25
</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>
22
1
<mapper namespace="dulk.learn.mybatis.dao.AuthorDao">
2
3
<resultMap id="authorResultMap" type="dulk.learn.mybatis.pojo.Author">
4
<id property="id" column="id"/>
5
<result property="name" column="name" />
6
<result property="age" column="age" />
7
<!--使用collection屬性,ofType為集合內元素的類型-->
8
<collection property="bookList" ofType="dulk.learn.mybatis.pojo.Book" columnPrefix="book_">
9
<id property="id" column="id"/>
10
<result property="name" column="name" />
11
<result property="price" column="price" />
12
</collection>
13
</resultMap>
14
15
<select id="findById" parameterType="long" resultMap="authorResultMap">
16
SELECT a.*, b.id AS 'book_id', b.name AS 'book_name', b.price AS 'book_price'
17
FROM author a, book b
18
WHERE a.id = b.author_id
19
AND a.id = #{authorId}
20
</select>
21
22
</mapper>
另外延伸一下關於避免字段重名的方式,如上例 select 中,列名的別名都增加了前綴 "book_",那么在collection中進行映射描時,就有兩種方式:
- 第一種即 column 的值和列名完全一致,如 column="book_id"
- 第二種也就是推薦的方式,在 collection 中使用屬性 columnPrefix 來定義統一前綴,在接下來的 column 中就可以減少工作量了,如上例中 columnPrefix = "book_",column = "id",它們的效果等同於 column = "book_id"