在上次mybatis的學習中我們知道了,開發中一般採用mapper代理的方式去開發,並介紹了一下mapper代理的一些開發規範。在本篇文章中將介紹數據模型分析的方法以及使用resultMap完成高級輸出結果的映射。
首先我們回答一個篇文章遺留的問題:細心的朋友可能會發現,由於輸入映射parameterType中參數只有一個,所以mapper接口方法的參數也只能有一個,這樣子會不會不利於系統的擴展呢?關於這個問題,我們將在下篇文章中回答。
使用resultType傳遞pojo的包裝對象
需求:
完成用戶信息的綜合查詢,需要傳入查詢條件很複雜(可能包括用戶信息、其它信息,比如商品、訂單的)
由於查詢條件可能會很複雜(比如需求:查詢所有購買戴爾這款筆記本的用戶),這時一個參數已經遠遠不夠,那麼可以採用自定義包裝類型的pojo將複雜的條件包裝進去(在pojo中包裝我們自己所需要的查詢條件,將這些負責的查詢條件)。由於一般的javabean都是使用逆向工程生成的(人家生成的東西不建議動),不建議直接在生成的javabean上直接修改。(如User.java)建議新建一個javabean的擴展類UserCustomer.java並讓UserCustomer.java繼承自User.java那麼如果想對用戶信息進行擴展,就可以在UserCustomer.java中擴展了,新建一個UserQueryVo.java作爲包裝類型的POJO,將UserCustomer.java包進去。這樣即使以後User類的結構發生了變化,我們直接用逆向工程重新生成即可。
擴展:
PO,VO,POJO:
三個都是java實體對象,vo,跟po比較類似,po是persistent object,是在是orm框架中的entity,po的每個屬性基本上都對應數據庫表裏面的某個字段,而vo(value object)有時可以跟po一樣,有時並不對應。POJO(Plain Old Java Objects)是簡單java對象,他並不繼承任何類(繼承Object),實現任何接口,只有屬性跟get set方法。而po一般要實現序列化接口,有時也繼承一些類.一個POJO持久化以後就是PO,直接用來對應表示層就是VO
如上圖所示:目前只是將用戶信息包進去了,如果查詢條件遠不止用戶信息,甚至還有商品和訂單信息,則同理在UserQueryVo中包入商品模塊和訂單模塊的擴展類,ItemsCustomer.java(商品的擴展類)和OrdersCustomer.java(訂單信息的擴展類)
下面還是跟之前開發步驟一樣,編寫mapper.xml和mapper.java
測試代碼
採用包裝類型pojo的方式解決了單個參數的侷限性問題,輸出結果目前是需要輸出一個用戶信息,很有可能除了輸出用戶信息還會加一些子查詢,那麼原來的User對象就無法映射了,所以我採用將我們自定義的用戶擴展類作爲輸出結果的映射。
resultMap的使用
需求:
首先我們從一個需求引出resultMap的使用場景:將下面的sql使用User完成映射
select id id_,username username_ from user where id = #{id}
觀察發現,查詢出來的列名和pojo的屬性名不一致,此時通過定義一個resultMap對列名和pojo屬性名之間做一個映射關係。具體步驟:1,定義resultMap。2,使用resultMap作爲statement的輸出映射類型。
其餘的開發步驟與之前resultType的開發方式一樣,重點在於resultMap的配置
使用resultMap作爲statement的輸出映射類型
mapper.java
測試
通過以上resultType和resultMap的區別:
使用resultType進行輸出映射,只有查詢出來的列名和pojo中的屬性名一致,該列纔可以映射成功。
如果查詢出來的列名和pojo中的屬性名全部不一致,沒有創建pojo對象。
只要查詢出來的列名和pojo中的屬性有一個一致,就會創建pojo對象。
如果sql查詢字段名和pojo的屬性名不一致,可以通過resultMap將字段名和屬性名作一個對應關係 ,resultMap實質上還需要將查詢結果映射到pojo對象中。
resultMap可以實現將查詢結果映射爲複雜類型的pojo,比如在查詢結果映射對象中包括pojo和list實現一對一查詢和一對多查詢。
用戶,商品,訂單數據模型分析思路
/*Table structure for table `user` */
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用戶名稱',
`birthday` date DEFAULT NULL COMMENT '生日',
`sex` char(1) DEFAULT NULL COMMENT '性別',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
/*Table structure for table `items` */
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL COMMENT '商品名稱',
`price` float(10,1) NOT NULL COMMENT '商品定價',
`detail` text COMMENT '商品描述',
`pic` varchar(64) DEFAULT NULL COMMENT '商品圖片',
`createtime` datetime NOT NULL COMMENT '生產日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Table structure for table `orderdetail` */
CREATE TABLE `orderdetail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orders_id` int(11) NOT NULL COMMENT '訂單id',
`items_id` int(11) NOT NULL COMMENT '商品id',
`items_num` int(11) DEFAULT NULL COMMENT '商品購買數量',
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`orders_id`),
KEY `FK_orderdetail_2` (`items_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
/*Table structure for table `orders` */
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '下單用戶id',
`number` varchar(32) NOT NULL COMMENT '訂單號',
`createtime` datetime NOT NULL COMMENT '創建訂單時間',
`note` varchar(100) DEFAULT NULL COMMENT '備註',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
每張表記錄的數據內容
1.分模塊對每張表記錄的內容進行熟悉,相當 於你學習系統 需求(功能)的過程。
2.每張表重要的字段設置
非空字段、外鍵字段
3.數據庫級別表與表之間的關係
外鍵關係
4.表與表之間的業務關係
在分析表與表之間的業務關係時一定要建立 在某個業務意義基礎上去分析。
用戶表user:
記錄了購買商品的用戶信息
訂單表:orders
記錄了用戶所創建的訂單(購買商品的訂單)
訂單明細表:orderdetail:
記錄了訂單的詳細信息即購買商品的信息
商品表:items
記錄了商品信息
表與表之間的業務關係:
在分析表與表之間的業務關係時需要建立 在某個業務意義基礎上去分析。
先分析數據級別之間有關係的表之間的業務關係:
usre和orders:
user---->orders:一個用戶可以創建多個訂單,一對多
orders--->user:一個訂單隻由一個用戶創建,一對一
orders和orderdetail:
orders---》orderdetail:一個訂單可以包括 多個訂單明細,因爲一個訂單可以購買多個商品,每個商品的購買信息在orderdetail記錄,一對多關係
orderdetail--> orders:一個訂單明細只能包括在一個訂單中,一對一
orderdetail和itesm:
orderdetail---》itesms:一個訂單明細只對應一個商品信息,一對一
items--> orderdetail:一個商品可以包括在多個訂單明細 ,一對多
再分析數據庫級別沒有關係的表之間是否有業務關係:
orders和items:
orders和items之間可以通過orderdetail表建立 關係。
ResultMap實現一對一關聯查詢
需求:查詢訂單信息,關聯查詢創建訂單的用戶信息
確定查詢的主表:訂單表
確定查詢的關聯表:用戶表
關聯查詢使用內鏈接?還是外鏈接?
由於orders表中有一個外鍵(user_id),通過外鍵關聯查詢用戶表只能查詢出一條記錄,可以使用內鏈接。
SELECT
orders.*,
USER.username,
USER.sex,
USER.address
FROM
orders,
USER
WHERE orders.user_id = user.id
創建pojo,使用resultMap的思路
使用resultMap將查詢結果中的訂單信息映射到Orders對象中,在orders類中添加User屬性,將關聯查詢出來的用戶信息映射到orders對象中的user屬性中。
需要在Orders類中添加User屬性
先編寫好sql模擬運行結果
定義resultMap
<!-- 訂單查詢關聯用戶的resultMap將整個查詢的結果映射到cn.travelsky.mybatis.po.Orders中 -->
<resultMap type="cn.travelsky.mybatis.po.Orders" id="OrdersUserResultMap">
<!-- 配置映射的訂單信息 -->
<!-- id:指定查詢列中的唯 一標識,訂單信息的中的唯 一標識,如果有多個列組成唯一標識,配置多個id
column:訂單信息的唯 一標識 列
property:訂單信息的唯 一標識 列所映射到Orders中哪個屬性
-->
<id column="id" property="id"/> <!--注意這裏的id是指能唯一標識訂單信息的唯一標識 -->
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property=note/>
<!-- 配置映射的關聯的用戶信息 -->
<!-- association:用於映射關聯查詢單個對象的信息
property:要將關聯查詢的用戶信息映射到Orders中哪個屬性
-->
<association property="user" javaType="cn.travelsky.mybatis.po.User">
<!-- id:關聯查詢用戶的唯 一標識
column:指定唯 一標識用戶信息的列
javaType:映射到user的哪個屬性
-->
<id column="user_id" property="id"/>
<!--注意這裏的id是指能唯一標識用戶信息的唯一標識,用戶有一個用戶唯一標識user.id,訂單有一個唯一標識order.id,還有一個外鍵user_id
指向用戶表的user.id,查詢時由於沒有添加用戶的id這個字段,但是訂單中的外鍵與用戶的主鍵是一一對應的,因此user_id是能唯一標識訂單的字段 -->
<result column="username" property="username"/><result column="sex" property="sex"/><result column="address" property="address"/></association></resultMap>定義statement
mapper.java
查詢打印結果:
Orders [id=3, userId=1, number=1000010, createtime=Wed Feb 04 13:22:35 CST 2015, note=null, user=User [id=1, username=王五, sex=2, birthday=null, address=null], orderdetails=null]
Orders [id=4, userId=1, number=1000011, createtime=Tue Feb 03 13:22:41 CST 2015, note=null, user=User [id=1, username=王五, sex=2, birthday=null, address=null], orderdetails=null]
Orders [id=5, userId=10, number=1000012, createtime=Thu Feb 12 16:13:23 CST 2015, note=null, user=User [id=10, username=張三, sex=1, birthday=null, address=北京市], orderdetails=null]
其實以上這種一對一的也可以使用resultType方式實現,如果pojo中沒有包括查詢出來的列名,需要增加列名對應的屬性即可完成映射。那麼以上需求中就是原始的Orders.java不能映射全部字段,需要創建新的pojo即OrderCustom繼承自Orders來包括所有的查詢字段。如下圖所示:
如果沒有查詢結果的特殊要求情況下,可以採用resultType方式,實現較爲簡單,但是由於resultType無法實現延遲加載,因此如果需要實現延遲加載,則優先選擇resultMap方式。(關於延遲加載將在下篇文章中描述)
ResultMap實現一對多關聯查詢
需求:查詢訂單及訂單明細的信息
需求分析:
主查詢表:訂單表
關聯表查詢表:訂單明細表
表之間關係:在一個訂單可以對應多個訂單明細的業務場景下,訂單與訂單明細之間是一對多的關係。
sql語句:
SELECT
orders.*,
USER.username,
USER.sex,
USER.address,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num,
orderdetail.orders_id
FROM
orders,
USER,
orderdetail
WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id
首先如果採用resultType的簡單方式,將查詢的結果映射到訂單表中,那麼訂單信息就是重複的。
如果此時需求要求:對主查詢orders映射不能出現重複記錄
採用resultMap的方式:
由於一個orders對應多個orderDetail,那麼類似上面一對一resultMap的實現方式,在orders.java中添加List<orderDetail> orderDetails屬性,最終會將訂單信息映射到orders中,訂單信息對應的訂單明細映射到orders中的orderDetails屬性中。映射成的orders記錄數爲兩條(orders信息不重複)每個orders中的orderDetails屬性存儲了該 訂單所對應的訂單明細。
在Orders中添加list訂單明細屬性
mapper.xml
ResultMap定義
<!-- 訂單及訂單明細的resultMap
使用extends繼承,不用在中配置訂單信息和用戶信息的映射
-->
<resultMap type="com.travelsky.mybatis.po.Orders" id="OrdersAndOrderDetailResultMap" extends="OrderUserResultMap">
<!-- 訂單信息 -->
<!-- 用戶信息 -->
<!-- 使用extends繼承,不用在中配置訂單信息和用戶信息的映射 -->
<!-- 訂單明細信息
一個訂單關聯查詢出了多條明細,要使用collection進行映射
collection:對關聯查詢到多條記錄映射到集合對象中
property:將關聯查詢到多條記錄映射到com.travelsky.mybatis.po.Orders哪個屬性
ofType:指定映射到list集合屬性中pojo的類型
-->
<collection property="orderdetails" ofType="com.travelsky.mybatis.po.Orderdetail">
<!-- id:訂單明細唯 一標識
property:要將訂單明細的唯 一標識 映射到com.travelsky.po.Orderdetail的哪個屬性
-->
<id column="orderdetail_id" property="id"/>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
<result column="orders_id" property="ordersId"/>
</collection>
</resultMap>
mapper.java
查詢結果:
Orders [id=3, userId=1, number=1000010, createtime=Wed Feb 04 13:22:35 CST 2015, note=這是三號訂單, user=User [id=1, username=王五, sex=2, birthday=null, address=null], orderdetails=[Orderdetail [id=1, ordersId=3, itemsId=1, itemsNum=1], Orderdetail [id=2, ordersId=3, itemsId=2, itemsNum=3]]]
Orders [id=4, userId=1, number=1000011, createtime=Tue Feb 03 13:22:41 CST 2015, note=這是四號訂單, user=User [id=1, username=王五, sex=2, birthday=null, address=null], orderdetails=[Orderdetail [id=3, ordersId=4, itemsId=3, itemsNum=4], Orderdetail [id=4, ordersId=4, itemsId=2, itemsNum=3]]]
如上查詢結果所示:兩條訂單信息,每條訂單信息中分別包含一個用戶信息和兩個訂單明細信息
ResultMap實現多對多關聯查詢
需求:查詢用戶及用戶購買的商品信息
查詢主表:用戶表
關聯表:由於用戶表和商品沒有直接關聯,通過訂單和訂單明細進行關聯,所以關聯表爲:orders,orderdetail,items
在一個用戶可以購買多種商品以及一種商品可以被多個用戶購買的業務情況下,用戶表與商品表之間是多對多的關係
sql語句
SELECT
orders.*,
USER.username,
USER.sex,
USER.address,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num,
orderdetail.orders_id,
items.name items_name,
items.detail items_detail,
items.price items_price
FROM
orders,
USER,
orderdetail,
items
WHERE orders.user_id = user.id AND orderdetail.orders_id=orders.id AND orderdetail.items_id = items.id
映射思路:
將用戶信息映射到user中。
在user類中添加訂單列表屬性List<Orders> orderslist,將用戶創建的訂單映射到orderslist
在Orders中添加訂單明細列表屬性List<OrderDetail>orderdetials,將訂單的明細映射到orderdetials
在OrderDetail中添加Items屬性,將訂單明細所對應的商品映射到Items
mapper.xml
resultMap定義
<!-- 查詢用戶及購買的商品 -->
<resultMap type="cn.travelsky.mybatis.po.User" id="UserAndItemsResultMap">
<!-- 用戶信息 -->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<!-- 訂單信息
一個用戶對應多個訂單,使用collection映射
-->
<collection property="ordersList" ofType="cn.travelsky.mybatis.po.Orders">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 訂單明細
一個訂單包括 多個明細
-->
<collection property="orderdetails" ofType="cn.travelsky.mybatis.po.Orderdetail">
<id column="orderdetail_id" property="id"/>
<result column="items_id" property="itemsId"/>
<result column="items_num" property="itemsNum"/>
<result column="orders_id" property="ordersId"/>
<!-- 商品信息
一個訂單明細對應一個商品
-->
<association property="items" javaType="cn.travelsky.mybatis.po.Items">
<id column="items_id" property="id"/>
<result column="items_name" property="name"/>
<result column="items_detail" property="detail"/>
<result column="items_price" property="price"/>
</association>
</collection>
</collection>
</resultMap>
mapper.java
多對多查詢總結
將查詢用戶購買的商品信息明細清單,(用戶名、用戶地址、購買商品名稱、購買商品時間、購買商品數量)
針對上邊的需求就使用resultType將查詢到的記錄映射到一個擴展的pojo中,很簡單實現明細清單的功能。
一對多是多對多的特例,如下需求:
查詢用戶購買的商品信息,用戶和商品的關係是多對多關係。
需求1:
查詢字段:用戶賬號、用戶名稱、用戶性別、商品名稱、商品價格(最常見)
開發中常見明細列表,用戶購買商品明細列表,
使用resultType將上邊查詢列映射到pojo輸出。
需求2:
查詢字段:用戶賬號、用戶名稱、購買商品數量、商品明細(鼠標移上顯示明細)
使用resultMap將用戶購買的商品明細列表映射到user對象中。
總結:
使用resultMap是針對那些對查詢結果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多個list。
ResultMap開發總結
resultType:
作用:將查詢結果按照sql列名pojo屬性名一致性映射到pojo中。
場合:
常見一些明細記錄的展示,比如用戶購買商品明細,將關聯查詢信息全部展示在頁面時,此時可直接使用resultType將每一條記錄映射到pojo中,在前端頁面遍歷list(list中是pojo)即可。
resultMap:
使用association和collection完成一對一和一對多高級映射(對結果有特殊的映射要求)。
association:
作用:將關聯查詢信息映射到一個pojo對象中。
場合:
爲了方便查詢關聯信息可以使用association將關聯訂單信息映射爲用戶對象的pojo屬性中,比如:查詢訂單及關聯用戶信息。
使用resultType無法將查詢結果映射到pojo對象的pojo屬性中,根據對結果集查詢遍歷的需要選擇使用resultType還是resultMap。
collection:作用:將關聯查詢信息映射到一個list集合中。
場合:
爲了方便查詢遍歷關聯信息可以使用collection將關聯信息映射到list集合中,比如:查詢用戶權限範圍模塊及模塊下的菜單,可使用collection將模塊映射到模塊list中,將菜單列表映射到模塊對象的菜單list屬性中,這樣的作的目的也是方便對查詢結果集進行遍歷查詢。
如果使用resultType無法將查詢結果映射到list集合中。