SSM架构商城项目(十三)

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个参数时,该属性的取值是listarray,当该节点对应的方法的参数超过1个时,该属性的取值是参数的名称(是@Param注解中确定的名称);item属性是遍历过程中,取出的数据的名称,是自定义的,在<foreach>节点内部也会使用该名称表示变量;seperator表示分隔符,例如以上删除时,SQL中各个id值应该使用逗号分隔,形成例如1,3,5,7这样的格式,则该属性的值为逗号;还有openclose属性,用于配置整个遍历出来的结果的前缀和后缀,例如在编写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]]
    ]

猜你喜欢

转载自www.cnblogs.com/wood-life/p/10290919.html