12. 确认订单页面
12.1. 功能分析
确认订单页面主要是显示数据的页面,包括:显示当前用户的所有收货地址,且显示在控件中,最终可以由用户在收货地址列表中选择其中一项来提交,还需要显示选中的购物车信息,由前一个页面(购物车列表页面)中的勾选来决定显示哪些购物车信息。
关于收货地址,使用<select>
与<option>
组合的下拉菜单来实现。
12.2. 显示收货地址
由于前序在开发收货地址管理时,已经完成了获取当前用户的所有收货地址列表的功能,所以,当前页面只需要直接调用并完成显示即可!
12.3. 显示购物数据
购物数据来自前序页面(购物车列表页面)中所勾选的项目,在勾选时,可以将对应的购物车数据的id传递到当前页,所以,当前页需要显示购物数据时,直接根据这些id再次查询数据库,获取数据并显示即可!
所以,目前,服务端需要根据一系列id(数量未知,是用户勾选的数量)获取购物车中的数据列表!
则在服务端的持久层:
/**
* 根据多个数据id获取购物车中的数据的列表
* @param uid 数据归属的用户的id
* @param ids 需要查询的数据的id的数组
* @return 匹配的数据,如果没有匹配的数据,则返回无元素的集合
*/
List<Cart> getListByIds(
@Param("uid") Integer uid,
@Param("ids") Integer[] ids);
<select id="getListByIds"
resultType="com.company.store.entity.Cart">
SELECT
id, uid,
goods_id AS goodsId,
goods_title AS goodsTitle,
goods_image AS goodsImage,
goods_price AS goodsPrice,
goods_num AS goodsNum
FROM
t_cart
WHERE
uid=#{uid} AND
id IN
<foreach collection="ids" separator="," item="id"
open="(" close=")">
#{id}
</foreach>
</select>
在<foreach>
节点中,collection
表示需要遍历的目标,可以是List集合,也可以是数组,当该节点对应的方法只有1个参数时,该属性的取值是list
或array
,当该节点对应的方法的参数超过1个时,该属性的取值是参数的名称(是@Param
注解中确定的名称);item
属性是遍历过程中,取出的数据的名称,是自定义的,在<foreach>
节点内部也会使用该名称表示变量;seperator
表示分隔符,例如以上删除时,SQL中各个id值应该使用逗号分隔,形成例如1,3,5,7
这样的格式,则该属性的值为逗号;还有open
和close
属性,用于配置整个遍历出来的结果的前缀和后缀,例如在编写SQL语句时没有指定IN
关键字后面的括号时,可以添加open="(" close=")"
这2项配置。
当持久层已经完成相关数据的获取,则在业务层添加同名方法并实现。
然后,在控制器提供数据访问功能:
@RequestMapping("/get_list_by_ids.do")
@ResponseBody
public ResponseResult<List<Cart>> getListByIds(
HttpSession session,
@RequestParam("ids") Integer[] ids) {
Integer uid = getUidFromSession(session);
List<Cart> carts
= cartService.getListByIds(uid, ids);
ResponseResult<List<Cart>> rr
= new ResponseResult<List<Cart>>();
rr.setData(carts);
return rr;
}
14. 订单
14.1. 设计数据表
关于订单,需要订单表和订单商品表:
CREATE TABLE t_order (
id INT AUTO_INCREMENT,
uid INT,
recv_name VARCHAR(16),
recv_phone VARCHAR(20),
recv_address VARCHAR(100),
total_price BIGINT(20),
status INT,
create_time DATETIME,
pay_time DATETIME,
created_user VARCHAR(16),
created_time DATETIME,
modified_user VARCHAR(16),
modified_time DATETIME,
PRIMARY KEY(id)
) DEFAULT CHARSET=UTF8;
CREATE TABLE t_order_item (
id INT AUTO_INCREMENT,
order_id INT,
goods_id BIGINT(20),
goods_image VARCHAR(500),
goods_title VARCHAR(100),
goods_price BIGINT(20),
goods_num INT,
created_user VARCHAR(16),
created_time DATETIME,
modified_user VARCHAR(16),
modified_time DATETIME,
PRIMARY KEY(id)
) DEFAULT CHARSET=UTF8;
完成后,创建对应的实体类。
14.2. 创建订单-持久层
使用1个OrderMapper
接口,通过它处理订单数据和订单商品数据:
public interface OrderMapper {
/**
* 插入订单数据
* @param order 订单数据
* @return 受影响的行数
*/
Integer insertOrder(Order order);
/**
* 插入订单商品数据
* @param orderItem 订单商品数据
* @return 受影响的行数
*/
Integer insertOrderItem(OrderItem orderItem);
}
然后,复制得到OrderMapper.xml
文件,配置以上接口的抽象方法的映射:
<mapper namespace="com.company.store.mapper.OrderMapper">
<!-- 插入订单数据 -->
<!-- Integer insertOrder(Order order) -->
<insert id="insertOrder"
parameterType="com.company.store.entity.Order"
useGeneratedKeys="true"
keyProperty="id">
INSERT INTO t_order (
uid,
recv_name,
recv_phone,
recv_address,
total_price,
status,
create_time,
pay_time,
created_user,
created_time,
modified_user,
modified_time
) VALUES (
#{uid},
#{recvName},
#{recvPhone},
#{recvAddress},
#{totalPrice},
#{status},
#{createTime},
#{payTime},
#{createdUser},
#{createdTime},
#{modifiedUser},
#{modifiedTime}
)
</insert>
<!-- 插入订单商品数据 -->
<!-- Integer insertOrderItem(OrderItem orderItem) -->
<insert id="insertOrderItem"
parameterType="com.company.store.entity.OrderItem"
useGeneratedKeys="true"
keyProperty="id">
INSERT INTO t_order_item (
order_id,
goods_id,
goods_image,
goods_title,
goods_price,
goods_num,
created_user,
created_time,
modified_user,
modified_time
) VALUES (
#{orderId},
#{goodsId},
#{goodsImage},
#{goodsTitle},
#{goodsPrice},
#{goodsNum},
#{createdUser},
#{createdTime},
#{modifiedUser},
#{modifiedTime}
)
</insert>
</mapper>
14.3. 业务层
创建业务层接口com.company.store.service.IOrderService
,声明抽象方法:
/**
* 创建订单
* @param uid 当前登录的用户
* @param addressId 选择的收货地址的id
* @param cartIds 选中的购物车中的数据的id
* @return 成功创建的订单
*/
Order createOrder(Integer uid, Integer addressId, Integer[] cartIds);
创建业务层实现类com.company.store.service.impl.OrderServiceImpl
,固定流程参考其它实现类(实现接口、添加注解、添加持久层对象并自动装配),然后,添加与持久层对应的2个插入数据的方法:
private Order insertOrder(Order order) { ...
private OrderItem insertOrderItem(OrderItem orderItem) {...
14.4. 控制器层
在spring-mvc.xml
的登录拦截器添加对/order/**
的拦截。
创建com.company.store.controller.OrderController
,并完成固定流程(继承BaseController
,添加2项注解,声明业务层对象),然后,添加处理请求的方法
@RequestMapping("/create.do")
public String create(
HttpSession session,
@RequestParam("address_id") Integer addressId,
@RequestParam("cart_id") Integer[] cartIds) {
// 获取uid
// 调用业务层对象的方法创建订单
// ...
}
MyBatis中的resultMap
通常,对于单表查询而言,不需要使用resultMap
,因为查询结果可以和某个实体类对应,从而将实体类作为查询的返回结果的类型!
但是,对于多表联合查询,没有匹配的实体类!例如订单数据:
SELECT o.id, o.recv_name, oi.goods_title
FROM t_order AS o INNER JOIN t_order_item AS oi
ON o.id = oi.order_id
这样的查询结果无法放到任何一个实体类中!而无法配置MyBatis的映射中的resultType
!
针对这类问题,首先,创建所需查询结果的VO类(Value Object),VO类与实体类的设计基本相同,即属性全部私有,并全部添加SET/GET方法,区别在于VO类并不对应某1张数据表,而是对应某次查询的结果!例如:
public class OrderVO {
private Integer id;
private String recvName;
private String recvPhone;
private List<OrderItem> orderItems;
// ...
}
然后,在查询时,<select>
节点不能配置resultType
,因为不确定哪些字段的查询结果应该放在VO类的哪个属性中!则需要通过resultMap
来确定查询结果与VO类的属性的对应关系!
在使用resultMap
前,必须先声明一个<resultMap>
:
<!-- 查询的resultMap -->
<!-- type:对应的VO类 -->
<!-- id:自定义标识 -->
<resultMap type="com.company.store.vo.OrderVO"
id="OrderVOMap">
<!-- id节点对应的是查询主表的自增长主键 -->
<!-- column:数据表中的字段名 -->
<!-- property:VO类中的属性名 -->
<id column="id" property="id" />
<!-- result节点配置非id字段 -->
<result column="recv_name" property="recvName"/>
<result column="recv_phone" property="recvPhone"/>
<!-- 1对多的集合,使用collection -->
<!-- ofType:集合中的元素的类型 -->
<collection property="orderItems"
ofType="com.company.store.entity.OrderItem">
<!-- property:ofType对应的类型中的属性名 -->
<result column="goods_title" property="goodsTitle"/>
</collection>
</resultMap>
然后,和一般的查询相同,配置<select>
,只不过,不再配置<resultType>
,而是配置<resultMap>
:
<!-- resultMap:以上配置的<resultMap>节点的id -->
<select id="getOrder"
resultMap="OrderVOMap">
SELECT
o.id,
o.recv_name,
o.recv_phone,
oi.goods_title
FROM
t_order AS o
INNER JOIN
t_order_item AS oi
ON
o.id = oi.order_id
</select>
以上查询的结果可能是:
OrderVO [
id=1,
recvName=苍老师,
recvPhone=13900139002,
orderItems=[
OrderItem [id=null, orderId=null, goodsId=null, goodsImage=null, goodsTitle=广博(GuangBo)16K115页线圈记事本子日记本文具笔记本图案随机, goodsPrice=null, goodsNum=null],
OrderItem [id=null, orderId=null, goodsId=null, goodsImage=null, goodsTitle=施耐德(Schneider) K15 经典款圆珠笔 (5支混色装), goodsPrice=null, goodsNum=null],
OrderItem [id=null, orderId=null, goodsId=null, goodsImage=null, goodsTitle=戴尔Dell 燃700R1605银色, goodsPrice=null, goodsNum=null]]
]