引入
在进行商品订单的分页查询的时候,我们Mapper是这样写的:
<select id="queryMyOrders" resultMap="myOrdersVO" parameterType="Map">
SELECT
od.id as orderId,
od.created_time as createdTime,
od.pay_method as payMethod,
od.real_pay_amount as realPayAmount,
od.post_amount as postAmount,
os.order_status as orderStatus,
oi.item_id as itemId,
oi.item_name as itemName,
oi.item_img as itemImg,
oi.item_spec_name as itemSpecName,
oi.buy_counts as buyCounts,
oi.price as price
FROM
orders od
LEFT JOIN
order_status os
on od.id = os.order_id
LEFT JOIN
order_items oi
ON od.id = oi.order_id
WHERE
od.user_id = #{paramsMap.userId}
AND
od.is_delete = 0
<if test="paramsMap.orderStatus != null">
and os.order_status = #{paramsMap.orderStatus}
</if>
ORDER BY
od.updated_time ASC
</select>
<resultMap id="myOrdersVO" type="com.imooc.pojo.vo.MyOrdersVO">
<id column="orderId" property="orderId"/>
<result column="createdTime" property="createdTime"/>
<result column="payMethod" property="payMethod"/>
<result column="realPayAmount" property="realPayAmount"/>
<result column="postAmount" property="postAmount"/>
<result column="orderStatus" property="orderStatus"/>
<result column="isComment" property="isComment"/>
<collection property="subOrderItemList"
ofType="com.imooc.pojo.vo.MySubOrderItemVO">
<result column="itemId" property="itemId"/>
<result column="itemName" property="itemName"/>
<result column="itemImg" property="itemImg"/>
<result column="itemSpecId" property="itemSpecId"/>
<result column="itemSpecName" property="itemSpecName"/>
<result column="buyCounts" property="buyCounts"/>
<result column="price" property="price"/>
</collection>
</resultMap>
上述Mapper很长(进行了三表的链接),我们只需要关注:
在<resultMap>
中出现了<collection>
,也就是说MyOrdersVO
对象的properties里面包含了一个List<MySubOrderItemVO>
。
当然,我们使用的分页工具是PageHelper:
<!--pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
在后端代码中,是这样的:
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public PagedGridResult queryMyOrders(String userId,
Integer orderStatus,
Integer page,
Integer pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("userId", userId);
if (orderStatus != null) {
map.put("orderStatus", orderStatus);
}
PageHelper.startPage(page, pageSize);
List<MyOrdersVO> list = ordersMapperCustom.queryMyOrders(map);
return setterPagedGrid(list, page);
}
public PagedGridResult setterPagedGrid(List<?> list, Integer page) {
PageInfo<?> pageList = new PageInfo<>(list);
PagedGridResult grid = new PagedGridResult();
grid.setPage(page);
grid.setRows(list);
grid.setTotal(pageList.getPages());
grid.setRecords(pageList.getTotal());
return grid;
}
是很标准的分页方法,不过上述的代码组合后,会产生一个BUG:
- 前端分页内容数量不准确。也就是说,原本应该是
MyOrdersVO
的数目为pageSize,但是现在是MyOrdersVO
对象中的MySubOrderItemVO
对象的数目一页为pageSize。
产生这种情况的原因在于,我们使用了一对多的嵌套查询。在Github上的PageHelper.startPage,该插件作者也强调了:
1. Please do not configure more than one PageHelper
When using Spring, you can config PageHelper by mybatis-config.xml or Spring<bean>. Select one of them, do not configure PageHelper in two ways at the same time.
2. PageHelper does not support paging with for update statement
3. PageHelper does not support Nested Results Mapping
Since the nested result mode causes the resultSet to be folded, the total number of results for the paged query will decrease after folding. So It cannot guarantee the number of paged results correctly.
也就是说由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。
解决方案
- 方案一:分两步查询
第一步:查询MyOrdersVO
的内容,但是不查询其子订单MySubOrderItemVO
的内容,前端暂时以null显示。
第二步:当用户浏览到该条订单的时候,才进行子订单的查询。
该方法的缺点就是会增加查询请求。
- 方法二:采用
<collection>
中的select
属性
该方法和上一个方法差不多,其实也是分了两步,第一步也是只查询订单不查询子订单,其select将三表链接改为两表链接:
<select id="queryMyOrders" resultMap="myOrdersVO" parameterType="Map">
SELECT
od.id as orderId,
od.created_time as createdTime,
od.pay_method as payMethod,
od.real_pay_amount as realPayAmount,
od.post_amount as postAmount,
os.order_status as orderStatus,
od.is_comment as isComment
FROM
orders od
LEFT JOIN
order_status os
on od.id = os.order_id
WHERE
od.user_id = #{paramsMap.userId}
AND
od.is_delete = 0
<if test="paramsMap.orderStatus != null">
and os.order_status = #{paramsMap.orderStatus}
</if>
ORDER BY
od.updated_time ASC
</select>
修改后,将<resultMap>
改为:
<resultMap id="myOrdersVO" type="com.imooc.pojo.vo.MyOrdersVO">
<id column="orderId" property="orderId"/>
<result column="createdTime" property="createdTime"/>
<result column="payMethod" property="payMethod"/>
<result column="realPayAmount" property="realPayAmount"/>
<result column="postAmount" property="postAmount"/>
<result column="orderStatus" property="orderStatus"/>
<result column="isComment" property="isComment"/>
<collection property="subOrderItemList"
select="getSubItems"
column="orderId"
ofType="com.imooc.pojo.vo.MySubOrderItemVO">
<result column="itemId" property="itemId"/>
<result column="itemName" property="itemName"/>
<result column="itemImg" property="itemImg"/>
<result column="itemSpecId" property="itemSpecId"/>
<result column="itemSpecName" property="itemSpecName"/>
<result column="buyCounts" property="buyCounts"/>
<result column="price" property="price"/>
</collection>
</resultMap>
注意,这里的<collection>
新增了两个属性,一个是select
一个是column
。
我们在查询完queryMyOrders
时,它会发现它对应的resultMap
里面,带有subOrderItemList
,这个属性会根据select
来进行对应的查询。查询就会有查询的属性值,我们希望将订单id<id column="orderId" property="orderId"/>
传到子订单来查询,所以就使用column
属性来指定resultMap
传入collection
的行。
所以这里我们会再写一个<select>
,其id与之对应:
<select id="getSubItems" parameterType="String" resultType="com.imooc.pojo.vo.MySubOrderItemVO">
select
oi.item_id as itemId,
oi.item_name as itemName,
oi.item_img as itemImg,
oi.item_spec_name as itemSpecName,
oi.buy_counts as buyCounts,
oi.price as price
from
order_items oi
where
oi.order_id = #{orderId}
</select>
我们也能够看出,这其实就是方法一第二步的内容。