【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次。相比第二種方法的只進行一次聯合查詢,這種方式無疑是低效的。
如果業務場景的表格顯示欄位,並沒有跨表,那麼可以採用延遲載入方式