1. 程式人生 > >【MyBatis學習10】關聯關係association:1對1關聯的三種方法

【MyBatis學習10】關聯關係association:1對1關聯的三種方法

本篇主要講關聯關係:一對一關係與一對多關係。
先建5個表:
這裡寫圖片描述

DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `catename` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `order`;
CREATE TABLE `order`
( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `orderno` varchar(20) NOT NULL COMMENT '系統訂單號', `totalprice` decimal(10,2) DEFAULT NULL COMMENT '訂單總價', `create_time` int(11) NOT NULL COMMENT '建立時間', `create_userid` int(10) unsigned NOT NULL COMMENT '建立使用者', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2
DEFAULT CHARSET=utf8 DROP TABLE IF EXISTS `order_detail`;
CREATE TABLE `order_detail` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `order_id` int(10) unsigned NOT NULL, `product_id` int(10) unsigned NOT NULL, `productname` varchar(50) NOT NULL COMMENT '商品快照:名稱', `price` decimal(10,2) NOT
NULL COMMENT '商品快照:單價', `num` int(10) unsigned NOT NULL COMMENT '購買數量', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `productname` varchar(50) NOT NULL, `price` decimal(10,2) DEFAULT NULL, `cateid` int(11) NOT NULL DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(50) DEFAULT NULL, `password` varchar(50) DEFAULT NULL, `salt` varchar(50) DEFAULT NULL, `sex` smallint(1) DEFAULT NULL COMMENT '0-未知 1-男 2-女', `address` varchar(50) DEFAULT NULL, `cellphone` varchar(30) DEFAULT NULL, `email` varchar(30) DEFAULT NULL, `islock` smallint(1) unsigned NOT NULL DEFAULT '0', `isvalidate` smallint(1) unsigned NOT NULL DEFAULT '1', `isdel` smallint(1) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8;

五個表關係如下:
這裡寫圖片描述
梳理圖上的4根關係線:
1、1個使用者可以有0個或多個訂單:[user表]–>[order表]是1對多(0.n)關係
2、1個有效訂單會購買1條或多條商品分錄:[order表]–>[order_detail表]是1對多(1.n)關係
3、1條商品分錄必定對應一個產品詳情:[order_detail表]–>[product表]是1對1關係
4、1個商品分類下可能會有0個或多個商品:[category表]–>[product表]是1對多(0.n)關係
5、所有1對多關係,反過來一定是1對1關係。比如[user表]–>[order表]是1對多(0.n)關係,那麼反過來[order表]–>[user表]是1對1關係。每個訂單都會有與之對應的唯一使用者。
要注意,所有1對1關係,反過來卻不一定是1對1關係。比如[order_detail表]–>[product表]是1對1關係,反過來[product表]–>[order_detail表]就不是1對1關係。因為同一個產品可能會在多個不同的訂單分錄中出現(熱門商品大家都願意買嘛)

這篇講一對一關係
[order表]–>[user表]是1對1關係,那麼就拿這個來練手。1對1關聯關係用於對一個表外來鍵的擴充套件。
現在要在後臺系統中按以下欄位顯示訂單列表。
這裡寫圖片描述
[order表]中沒有使用者名稱稱,使用者地址,聯絡電話這三個欄位,只能靠creat_userid去關聯查詢對應的那個使用者資訊。
查詢語句如下:

SELECT `order`.*,`user`.username,`user`.address,`user`.cellphone 
    FROM `order`,`user` 
    WHERE `order`.create_userid=`user`.id

有三種方式可以實現:

一、擴充套件新建POJO物件,不使用association標籤

step1.我們已經有一個與表對應的Order類,但是沒有使用者名稱稱,使用者地址,聯絡電話這三個欄位。現在新建一個類OrderExtend,擴充欄位:

public class OrderExtend extends Order{
    public OrderExtend() {
        super();
    }
    /*新增用於展示的使用者名稱稱,使用者地址,聯絡電話這三個欄位*/
    String username;
    String address;
    String cellphone;

    /*下面get和set方法*/
    getter and setter....
}

step2.建立介面及Xml

public interface OrderExtendMapper {
    //查詢單個訂單詳情,關聯查詢使用者資訊
    public OrderExtend getByOrderno(String orderno) throws Exception;
    //查詢訂單列表,關聯查詢使用者資訊
    public List<OrderExtend> getList() throws Exception;
}
<mapper namespace="twm.mybatisdemo.mapper.OrderExtendMapper">
    <select id="getByOrderno" parameterType="String"
        resultType="twm.mybatisdemo.pojo.OrderExtend">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order` ,`user`
        WHERE `order`.create_userid=`user`.id AND `order`.create_userid=#{id}
    </select>

    <select id="getList" resultType="twm.mybatisdemo.pojo.OrderExtend">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order`
        ,`user`
        WHERE `order`.create_userid=`user`.id
    </select>
</mapper>

step3.呼叫

public static void main(String[] args) throws Exception {
        SqlSession session = SqlSessionAssist.getSession();

        OrderExtendMapper ordermapper = session
                .getMapper(OrderExtendMapper.class);
        OrderExtend order = ordermapper.getByOrderno("M201209012578917");
        System.out.println(order.getOrderno() + "," + order.getUsername() + ","
                + order.getAddress() + "," + order.getCellphone());
    }

二、(推薦)用sql聯合查詢,使用association標籤

這個不需要新建擴充套件類了。在Order類中,新增一個User型別的屬性,將查詢出來的使用者相關資料通過association標籤對映到user。
association專門用來建立1對1關聯關係。其中
property:指定物件的屬性名
javaType:指定要對映的物件的型別。

step1.在Order類中,新增一個User型別的屬性

public class OrderExtend extends Order{
    /*新增用於展示的使用者名稱稱,使用者地址,聯絡電話這三個欄位*/
    String username;
    String address;
    String cellphone;
    User user;

    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    /*下面get和set方法*/
    getter and setter....
}

step2.建立對映器介面及配置Xml
twm.mybatisdemo.mapper包下建立
OrderMapper.java:

public interface OrderMapper {
    //查詢單個訂單詳情,關聯查詢使用者資訊
    public Order getByOrderno(String orderno) throws Exception;
    //查詢訂單列表,關聯查詢使用者資訊
    public List<Order> getList() throws Exception;
}

OrderMapper.xml:

<mapper namespace="twm.mybatisdemo.mapper.OrderMapper">
    <!-- 定義型別對映 -->
    <resultMap type="Order" id="OrderMap">
        <!-- 訂單表屬性 -->
        <id column="id" property="id" />
        <result column="orderno" property="orderno" />
        <result column="create_time" property="create_time" />
        <result column="create_userid" property="create_userid" />
        <!-- 關聯的使用者資訊 -->
        <!-- association用於關聯查詢:
        property指屬性,javaType是要對映的物件的型別。 -->
        <association property="user" javaType="User">
            <result column="username" property="username" />
            <result column="address" property="address" />
            <result column="cellphone" property="cellphone" />
        </association>
    </resultMap>

    <select id="getByOrderno" parameterType="String"
        resultMap="OrderMap">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order`
        ,`user`
        WHERE `order`.create_userid=`user`.id AND
        `order`.orderno=#{orderno}
    </select>

    <select id="getList" resultMap="OrderMap">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order`
        ,`user`
        WHERE `order`.create_userid=`user`.id
    </select>
</mapper>

step3.呼叫

public static void main(String[] args) throws Exception {
    SqlSession session = SqlSessionAssist.getSession();

    OrderMapper ordermapper = session.getMapper(OrderMapper.class);
    Order order = ordermapper.getByOrderno("M201209012578917");
    System.out.println(order.getOrderno() + ","
            + order.getUser().getUsername() + ","
            + order.getUser().getAddress() + ","
            + order.getUser().getCellphone());

}

三、不用sql聯合查詢,通過association的延遲載入來實現

什麼是延遲載入?如果先查詢訂單資訊即可滿足業務要求就不會去查詢使用者,只有當用到使用者資訊時再查詢使用者資訊。
對使用者資訊按需去查詢就是延遲載入。
比如上面,只有當呼叫Order中的getUser方法獲取關聯的user資料時,才會觸發資料庫查詢user表。

mybatis預設沒有開啟延遲載入,需要在SqlMapConfig.xml中setting配置。
lazyLoadingEnabled:全域性性設定懶載入。如果設為‘false’,則所有相關聯的都會被初始化載入。允許值有:true | false。預設值:false
aggressiveLazyLoading:當設定為‘true’的時候,懶載入的物件可能被任何懶屬性全部載入。否則,每個屬性都按需載入。允許值有:true | false。預設值:true

和第二種方式比,其它都不變。只是DAOImplement層有一些變化,XML檔案要調整三處:

第一處:新增一個使用者查詢語句:

<!-- 新增一個使用者查詢語句:getUser -->
<select id="getUser" parameterType="int" resultType="User">
    SELECT
    `username`,`address`,`cellphone`
    FROM `user`
    WHERE `id` =#{_parameter}
</select>

第二處:把原來resultMap的association標籤改為

<association property="user" javaType="User" column="create_userid" select="getUser" />

第三處:把getByOrderno和getList查詢語句改為普通的select單表查詢。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="twm.mybatisdemo.mapper.OrderMapper">
    <!-- 定義型別對映 -->
    <resultMap type="Order" id="OrderMap">
        <!-- 訂單表屬性 -->
        <id column="id" property="id" />
        <result column="orderno" property="orderno" />
        <result column="create_time" property="create_time" />
        <result column="create_userid" property="create_userid" />
        <!-- 關聯的使用者資訊 -->
        <!-- association用於關聯查詢: property指屬性,javaType是要對映的物件的型別。 -->
        <association property="user" javaType="User" column="create_userid"
            select="getUser" />
    </resultMap>

<!-- 新增一個使用者查詢:getUser。getUser這一段可以刪掉,用user物件的查詢方法 -->
<select id="getUser" parameterType="int" resultType="User">
    SELECT
    `username`,`address`,`cellphone`
    FROM `user`
    WHERE `id` =#{_parameter}
</select>

    <select id="getByOrderno" parameterType="String" resultMap="OrderMap">
        SELECT * FROM `order`  WHERE `order`.orderno=#{orderno}
    </select>

    <select id="getList" resultMap="OrderMap">
        SELECT * FROM `order` 
    </select>
</mapper>

一切OK了。
association的幾個屬性:
property:指定內部物件屬性名
javaType:內部對映的物件的型別。
column:要傳給select語句的引數,相當於指定外來鍵欄位。
select:指定使用者查詢語句的ID

getUser使用者查詢這一段語句也可以省略,因為之前在twm.mybatisdemo.mapper.UserMapper(UserMapper.xml)中建立過一個selectById查詢。所以這裡可以刪掉getUser那一段查詢,把association改一下:

<association property="user" javaType="User" column="create_userid" select="twm.mybatisdemo.mapper.UserMapper.selectById" />

事實上,大多數業務場景顯示的表格,都會用到多個表字段。
如果採用延遲載入,會存在N+1問題。
什麼是N+1問題呢?
每一個獲取Order內部的User物件,都會進行一次select查詢
那麼當執行過程中執行Order的getList方法時,SQL首先進行1次查詢,查詢結果如果有N條訂單記錄,那麼實際在每條訂單中顯示過程中還要執行一次select使用者的查詢,共n次。
SQL總共執行了n+1次。相比第二種方法的只進行一次聯合查詢,這種方式無疑是低效的。
如果業務場景的表格顯示欄位,並沒有跨表,那麼可以採用延遲載入方式