day07-MyBatis的關聯對映01

一刀一個小西瓜發表於2023-02-28

MyBatis的關聯對映

Mybatis的關聯對映

實際的開發中,對資料庫的操作常常會涉及到多張表,這在物件導向中就涉及到了物件與物件之間的關聯關係。針對多表之間的操作,MyBatis提供了關聯對映,透過關聯對映就可以很好的處理物件與物件之間的關聯關係。

1.關聯關係概述

在關係型資料庫中,多表之間存在著三種關係,分別是一對一,一對多,多對多。

image-20230228175005921
  • 一對一:在任意一個表中引入另外一個表的主鍵作為外來鍵。

  • 一對多:在多個表中都引入了某一個表的主鍵作為外來鍵

  • 多對多:需要用一張中間表表示多對多的關係,這張中間表引入兩張表的主鍵作為外來鍵。

一般來說一個物件對映一張表,因此一對一的關係就是在A類中定義B類屬性,一對多的關係就是在A類中定義List< B> 的屬性,多對多就是分別在A、B類中定義對方的List 屬性。

2.一對一

一對一關係是一個基本的對映關係,比如Person(人)--IDCard(身份證),我們可以透過如下兩種方式實現:

  1. 透過配置XxxMapper.xml實現1對1 [配置方式]
  2. 透過註解的方式實現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:多表聯查

mybatis – MyBatis 3 | XML 對映器

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();
    }
}
image-20230228194043902

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表進行查詢。

image-20230228204133095

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();
    }
}
image-20230228210319588

2.3注意事項

一張表是否設定了外來鍵,對MyBatis進行物件級聯對映沒有影響,外來鍵只是對錶本身資料的約束

2.4練習

前面我們講解的是查詢Person可以級聯查詢到IdenCard,如果要求透過查詢IdenCard,也可以級聯查詢到Person,應該如何解決?

相關文章