MyBatis的關聯對映
實際的開發中,對資料庫的操作常常會涉及到多張表,這在物件導向中就涉及到了物件與物件之間的關聯關係。針對多表之間的操作,MyBatis提供了關聯對映,透過關聯對映就可以很好的處理物件與物件之間的關聯關係。
1.關聯關係概述
在關係型資料庫中,多表之間存在著三種關係,分別是一對一,一對多,多對多。
-
一對一:在任意一個表中引入另外一個表的主鍵作為外來鍵。
-
一對多:在多個表中都引入了某一個表的主鍵作為外來鍵
-
多對多:需要用一張中間表表示多對多的關係,這張中間表引入兩張表的主鍵作為外來鍵。
一般來說一個物件對映一張表,因此一對一的關係就是在A類中定義B類屬性,一對多的關係就是在A類中定義List< B> 的屬性,多對多就是分別在A、B類中定義對方的List 屬性。
2.一對一
一對一關係是一個基本的對映關係,比如Person(人)--IDCard(身份證),我們可以透過如下兩種方式實現:
- 透過配置XxxMapper.xml實現1對1 [配置方式]
- 透過註解的方式實現1對1 [註解方式]
2.1配置方式
2.1.1環境搭建
配置對映檔案來實現一對一的對映關係,實現級聯查詢,要求透過person可以獲取到對應的idencard資訊
關於級聯查詢:若表A中有一個外來鍵引用了表B的主鍵,A表就是子表,B表就是父表。當查詢表A的資料時,透過表A的外來鍵將表B的記錄也查詢出來,這就是級聯查詢。相應的還有級聯刪除,當刪除B表的記錄時,會先將A表中關聯的記錄刪掉
(1)person表和 idencard表
-- 建立 idencard 表
-- 記錄身份證
CREATE TABLE `idencard`(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`card_sn` VARCHAR(32) NOT NULL DEFAULT ''
)CHARSET utf8;
INSERT INTO `idencard` VALUES(1,'123456789098765');
-- 建立person表
CREATE TABLE `person`(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL DEFAULT '',
`card_id` INT, -- 對應idencard表的主鍵-id
FOREIGN KEY (`card_id`) REFERENCES idencard (`id`)-- card_id作為外來鍵
)CHARSET utf8;
INSERT INTO `person` VALUES(1,'張三',1);
(2)實體類 IdenCard 和 Person
package com.li.entity;
/**
* @author 李
* @version 1.0
*/
public class IdenCard {
private Integer id;
private String card_sn;
//省略setter,getter,toString方法
}
package com.li.entity;
/**
* @author 李
* @version 1.0
*/
public class Person {
private Integer id;
private String name;
private IdenCard card;
//省略setter,getter,toString方法
}
(3)PersonMapper 介面
package com.li.mapper;
import com.li.entity.Person;
/**
* @author 李
* @version 1.0
*/
public interface PersonMapper {
//透過Person的id獲取到Person,包括這個Person關聯的IdenCard物件(級聯操作)
public Person getPersonById(Integer id);
}
2.1.2方式1:多表聯查
PersonMapper.xml對映檔案實現級聯查詢,實現方法是使用多表聯查,返回的資料透過resultMap結果對映
<mapper namespace="com.li.mapper.PersonMapper">
<!--1.介面宣告:public Person getPersonById(Integer id);
2.透過Person的id獲取到Person,包括這個Person關聯的IdenCard物件(級聯操作)
3.返回型別如果配置成resultType="Person",不能實現級聯查詢,
在返回的person物件中IdenCard屬性物件為 null
4.因此需要使用自定義resultMap,在resultMap中指定級聯關係-->
<select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap">
SELECT * FROM `person`,`idencard` WHERE `person`.`id`= #{id} AND
`person`.`card_id` = `idencard`.`id`;
</select>
<!--association – 一個複雜型別的關聯;許多結果將包裝成這種型別巢狀結果對映 – 關聯可以是
resultMap 元素,或是對其它結果對映的引用
1.property="card" 表示 Person物件的card屬性
2.javaType="IdenCard" 表示card屬性的型別-->
<resultMap id="PersonResultMap" type="Person">
<!--id標籤–一個ID結果(就是主鍵);標記出作為主鍵的結果可以幫助提高整體效能-->
<!--這裡的property表示Person類的屬性名,column表示對應表的欄位-->
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="card" javaType="IdenCard">
<!--這裡的property表示IdenCard類的屬性名,column表示表的欄位名-->
<result property="id" column="id"/>
<result property="card_sn" column="card_sn"/>
</association>
</resultMap>
</mapper>
測試:
@Test
public void getPersonById() {
Person person = personMapper.getPersonById(1);
System.out.println("person=" + person);
if (sqlSession != null) {
sqlSession.close();
}
}
2.2.3方式2:分解為多次單表操作(推薦使用)
第一種方式使用了多表聯查的形式實現級聯查詢,但是如果涉及的表過多,sql語句可讀性就會變差。第二種方式的核心思想是將多表聯查分解成單表操作,這樣更簡潔,易於維護,而且可以複用你寫好的方法,推薦使用
(1)建立IdenCardMapper介面
public interface IdenCardMapper {
//根據id獲取到身份證序列號
public IdenCard getIdenCardById(Integer id);
}
(2)在IdenCardMapper的對映檔案中實現該方法
<mapper namespace="com.li.mapper.IdenCardMapper">
<!--配置實現public IdenCard getIdenCardById(Integer id);-->
<select id="getIdenCardById" parameterType="Integer" resultType="IdenCard">
SELECT * FROM `idencard` WHERE `id` = #{id}
</select>
</mapper>
(3)PersonMapper介面
//透過Person的id獲取到Person,包括這個Person關聯的IdenCard物件(方式2)
public Person getPersonById2(Integer id);
(4)實現PersonMapper介面的對映檔案
1. 先透過 SELECT * FROM person WHERE id =#{id} 返回 person 資訊
2. 以第一個操作返回的 card_id 欄位資料,作為條件再次查詢,得到對應的 IdenCard 資料
如果第一個操作使用了別名,那麼返回的時候的欄位也是別名,因此第二個操作也要使用別名才能匹配到
<!--透過Person的id獲取到Person,包括這個Person關聯的IdenCard物件(方式2)
介面方法:public Person getPersonById2(Integer id);-->
<resultMap id="PersonResultMap2" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--第二種方式的核心思想是將多表聯查操作分解成單表操作,
這樣更簡潔,易於維護,複用性更強,推薦使用-->
<!--1.property="card"表示Person物件的card屬性
2.column="card_id"是SELECT * FROM person WHERE id = #{id}語句返回的card_id欄位名/別名
3.返回的欄位card_id資訊/資料會作為getIdenCardById()的入參來執行方法-->
<association property="card" column="card_id"
select="com.li.mapper.IdenCardMapper.getIdenCardById"/>
</resultMap>
<select id="getPersonById2" parameterType="Integer" resultMap="PersonResultMap2">
SELECT * FROM person WHERE id = #{id};
</select>
測試結果:
可以看到底層執行了兩次sql查詢操作。首先對person表進行查詢,查詢結果(card_id)作為第二張表的查詢條件(id),再對idencard表進行查詢。
2.2註解方式
透過註解的方式來實現一對一的對映關係,實現級聯查詢,透過person可以獲取到對應的idencard的資訊。這裡只進行方式二的演示。
在實際開發中還是推薦使用配置方式
(1)註解實現方法
IdenCardMapperAnnotation 介面:
package com.li.mapper;
import com.li.entity.IdenCard;
import org.apache.ibatis.annotations.Select;
/**
* @author 李
* @version 1.0
* 使用註解的方式實現一對一的對映
*/
public interface IdenCardMapperAnnotation {
//根據id獲取到身份證序列號
@Select(value = "SELECT * FROM `idencard` WHERE `id` = #{id}")
public IdenCard getIdenCardById(Integer id);
}
PersonMapperAnnotation 介面:
package com.li.mapper;
import com.li.entity.Person;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
/**
* @author 李
* @version 1.0
*/
public interface PersonMapperAnnotation {
//透過Person的id獲取到Person,包括這個Person關聯的IdenCard物件
//註解的形式就是對前面xml配置方式的體現
@Select(value = "SELECT * FROM person WHERE id = #{id}")//如果這裡返回的欄位使用了別名,則@result的card_id也要使用該別名
@Results({//配置返回資料的對映
@Result(id = true, property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "card", column = "card_id",
one = @One(select = "com.li.mapper.IdenCardMapper.getIdenCardById"))
})
public Person getPersonById(Integer id);
}
(2)測試
@Test
public void getIdenCardById() {
Person person = personMapperAnnotation.getPersonById(1);
System.out.println("person=" + person);
if (sqlSession != null) {
sqlSession.close();
}
}
2.3注意事項
一張表是否設定了外來鍵,對MyBatis進行物件級聯對映沒有影響,外來鍵只是對錶本身資料的約束
2.4練習
前面我們講解的是查詢Person可以級聯查詢到IdenCard,如果要求透過查詢IdenCard,也可以級聯查詢到Person,應該如何解決?