day14 书城项目第六阶段

day14 书城项目第六阶段

1. 结账

1.1 创建订单模型

1.1.1 物理建模
① t_order表
CREATE TABLE t_order(
	order_id INT PRIMARY KEY AUTO_INCREMENT,
	order_sequence VARCHAR(200),
	create_time VARCHAR(100),
	total_count INT,
	total_amount DOUBLE,
	order_status INT,
	user_id INT
);
字段名字段作用
order_id主键
order_sequence订单号
create_time订单创建时间
total_count订单的总数量
total_amount订单的总金额
order_status订单的状态
user_id下单的用户的id
  • 虽然order_sequence也是一个不重复的数值,但是不使用它作为主键。数据库表的主键要使用没有业务功能的字段来担任。
  • 订单的状态
    • 待支付(书城项目中暂不考虑)
    • 已支付,待发货:0
    • 已发货:1
    • 确认收货:2
    • 发起退款或退货(书城项目中暂不考虑)
  • 用户id
    • 从逻辑和表结构的角度来说,这其实是一个外键。
    • 但是开发过程中建议先不要加外键约束:因为开发过程中数据尚不完整,加了外键约束开发过程中使用测试数据非常不方便,建议项目预发布时添加外键约束测试。
② t_order_item表
CREATE TABLE t_order_item(
	item_id INT PRIMARY KEY AUTO_INCREMENT,
	book_name VARCHAR(20),
	price DOUBLE,
	img_path VARCHAR(50),
	item_count INT,
	item_amount DOUBLE,
	order_id VARCHAR(20)
);
字段名称字段作用
item_id主键
book_name书名
price单价
item_count当前订单项的数量
item_amount当前订单项的金额
order_id当前订单项关联的订单表的主键

说明:book_name、author、price这三个字段其实属于t_book表,我们把它们加入到t_order_item表中,其实并不符合数据库设计三大范式。这里做不符合规范的操作的原因是:将这几个字段加入当前表就不必在显示数据时和t_book表做关联查询,提高查询的效率,这是一种变通的做法。

1.1.2 逻辑模型
① Order类
package com.atguigu.pojo;

public class Order {

    private Integer orderid;
    private String ordersequence;
    private String createtime;
    private Integer totalcount;
    private Double totalamount;
    private Integer orderstatus;
    private Integer userid;

    public Order() {
    }

    public Order(Integer orderid, String ordersequence, String createtime, Integer totalcount, Double totalamount, Integer orderstatus, Integer userid) {
        this.orderid = orderid;
        this.ordersequence = ordersequence;
        this.createtime = createtime;
        this.totalcount = totalcount;
        this.totalamount = totalamount;
        this.orderstatus = orderstatus;
        this.userid = userid;
    }

    public Integer getOrderid() {
        return orderid;
    }

    public void setOrderid(Integer orderid) {
        this.orderid = orderid;
    }

    public String getOrdersequence() {
        return ordersequence;
    }

    public void setOrdersequence(String ordersequence) {
        this.ordersequence = ordersequence;
    }

    public String getCreatetime() {
        return createtime;
    }

    public void setCreatetime(String createtime) {
        this.createtime = createtime;
    }

    public Integer getTotalcount() {
        return totalcount;
    }

    public void setTotalcount(Integer totalcount) {
        this.totalcount = totalcount;
    }

    public Double getTotalamount() {
        return totalamount;
    }

    public void setTotalamount(Double totalamount) {
        this.totalamount = totalamount;
    }

    public Integer getOrderstatus() {
        return orderstatus;
    }

    public void setOrderstatus(Integer orderstatus) {
        this.orderstatus = orderstatus;
    }

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderid=" + orderid +
                ", ordersequence='" + ordersequence + '\'' +
                ", createtime='" + createtime + '\'' +
                ", totalcount=" + totalcount +
                ", totalamount=" + totalamount +
                ", orderstatus=" + orderstatus +
                ", userid=" + userid +
                '}';
    }
}

② OrdrItem类
package com.atguigu.pojo;

public class OrderItem {
    private Integer itemId;
    private String bookName;
    private Double price;
    private String imgPath;
    private Integer itemCount;
    private Double itemAmount;
    private Integer orderId;

    public OrderItem() {
    }

    public OrderItem(Integer itemId, String bookName, Double price, String imgPath, Integer itemCount, Double itemAmount, Integer orderId) {
        this.itemId = itemId;
        this.bookName = bookName;
        this.price = price;
        this.imgPath = imgPath;
        this.itemCount = itemCount;
        this.itemAmount = itemAmount;
        this.orderId = orderId;
    }

    public Integer getItemId() {
        return itemId;
    }

    public void setItemId(Integer itemId) {
        this.itemId = itemId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getImgPath() {
        return imgPath;
    }

    public void setImgPath(String imgPath) {
        this.imgPath = imgPath;
    }

    public Integer getItemCount() {
        return itemCount;
    }

    public void setItemCount(Integer itemCount) {
        this.itemCount = itemCount;
    }

    public Double getItemAmount() {
        return itemAmount;
    }

    public void setItemAmount(Double itemAmount) {
        this.itemAmount = itemAmount;
    }

    public Integer getOrderId() {
        return orderId;
    }

    public void setOrderId(Integer orderId) {
        this.orderId = orderId;
    }

    @Override
    public String toString() {
        return "OrderItem{" +
                "itemId=" + itemId +
                ", bookName='" + bookName + '\'' +
                ", price=" + price +
                ", imgPath='" + imgPath + '\'' +
                ", itemCount=" + itemCount +
                ", itemAmount=" + itemAmount +
                ", orderId=" + orderId +
                '}';
    }

}


1.2 创建组件

1.2.1 持久化层

在这里插入图片描述

1.2.2 业务逻辑层

在这里插入图片描述

1.2.3 表述层

在这里插入图片描述

1.3 功能步骤

  • 创建订单对象
  • 给订单对象填充数据
    • 生成订单号
    • 生成订单的时间
    • 从购物车迁移总数量和总金额
    • 从已登录的User对象中获取userId并设置到订单对象中
  • 将订单对象保存到数据库中
  • 获取订单对象在数据库中自增主键的值
  • 根据购物车中的CartItem集合逐个创建OrderItem对象
    • 每个OrderItem对象对应的orderId属性都使用前面获取的订单数据的自增主键的值
  • 把OrderItem对象的集合保存到数据库
  • 每一个item对应的图书增加销量、减少库存
  • 清空购物车

1.4 案例思路

在这里插入图片描述

1.5 代码实现

1.5.1 购物车页面结账超链接

cart.html

<a class="pay" href="protected/order?method=checkOut">去结账</a>
1.5.2 OrderServlet.checkout()
package com.atguigu.servlet.app;

import com.atguigu.pojo.Cart;
import com.atguigu.pojo.Constants;
import com.atguigu.pojo.User;
import com.atguigu.service.OrderService;
import com.atguigu.service.impl.OrderServiceImpl;
import com.atguigu.servlet.base.ModelBaseServlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/protected/order")
public class OrderServlet extends ModelBaseServlet {

    // 结账操作
    protected void checkOut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        // 1.获取购物车
        Cart cart = (Cart) session.getAttribute(Constants.SESSION_CART_KEY);
        // 2.获取用户
        User user = (User) session.getAttribute(Constants.SESSION_LOGON_USER);
        // 3.进行结账的业务层操作 返回订单号
        OrderService service = new OrderServiceImpl();
        // 返回订单号
        String sequence = service.checkOut(cart, user);
        // 4.清空购物车
        session.removeAttribute(Constants.SESSION_CART_KEY);
        // 5.将订单号添加到请求域中
        req.setAttribute("sequence", sequence);
        // 6.跳转到cart/checkout页面
        processTemplate("cart/checkout", req, resp);

    }
}

1.5.3 BaseDao.batch()
    // 通用的批量处理
    public void batch(String sql, Object[][] params){
        // 1.创建操作对象
        QueryRunner runner = new QueryRunner();
        try {
            // 2.获取连接
            Connection connection = JDBCutils.getConnection();
            // 3.执行操作
            runner.batch(connection, sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }
1.5.4 OrderService.checkout()
package com.atguigu.bookstore.service.impl;

import com.atguigu.bookstore.bean.Cart;
import com.atguigu.bookstore.bean.CartItem;
import com.atguigu.bookstore.constants.BookStoreConstants;
import com.atguigu.bookstore.dao.BookDao;
import com.atguigu.bookstore.dao.OrderDao;
import com.atguigu.bookstore.dao.OrderItemDao;
import com.atguigu.bookstore.dao.impl.BookDaoImpl;
import com.atguigu.bookstore.dao.impl.OrderDaoImpl;
import com.atguigu.bookstore.dao.impl.OrderItemDaoImpl;
import com.atguigu.bookstore.entity.Order;
import com.atguigu.bookstore.entity.User;
import com.atguigu.bookstore.service.OrderService;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * 包名:com.atguigu.bookstore.service.impl
 *
 *  
 * 日期2021-06-19  14:19
 */
public class OrderServiceImpl implements OrderService {
    private OrderDao orderDao = new OrderDaoImpl();
    private OrderItemDao orderItemDao = new OrderItemDaoImpl();
    private BookDao bookDao = new BookDaoImpl();
    @Override
    public String checkout(Cart cart, User user) throws Exception {
        //1. 往订单表插入一条数据  
        //1.1 生成一个唯一的订单号(使用UUID)  
        String orderSequence = UUID.randomUUID().toString();
        //1.2 生成当前时间createTime
        String createTime = new SimpleDateFormat("dd-MM-yy:HH:mm:ss").format(new Date());
        //1.3 订单的totalCount就是cart的totalCount  
        Integer totalCount = cart.getTotalCount();
        //1.4 订单的totalAmount就是购物车的totalAmount
        Double totalAmount = cart.getTotalAmount();
        //1.5 设置订单的状态为0
        Integer status = BookStoreConstants.PAYED;
        //1.6 订单的userId就是user对象的id  
        Integer userId = user.getUserId();

        //将上述六个数据封装到一个Order对象中
        Order order = new Order(null,orderSequence,createTime,totalCount,totalAmount,status,userId);
        //1.7 调用持久层OrderDao的insertOrder方法添加订单数据,并且获取自增长的主键值
        Integer orderId = orderDao.insertOrder(order);

        //2. 往订单项表插入多条数据(采用批处理)
        //获取所有的购物项
        List<CartItem> cartItemList = cart.getCartItemList();
        //创建一个二维数组,用来做批量添加订单项的参数
        Object[][] orderItemArrParam = new Object[cartItemList.size()][6];

        //3. 更新t_book表中对应的书的sales和stock  
        //创建一个二维数组,用来做批量修改图书信息的参数
        Object[][] bookArrParam = new Object[cartItemList.size()][3];

        //遍历出每一个购物项
        for (int i=0;i<cartItemList.size();i++) {
            //封装批量添加订单项的二维数组参数
            //每一个购物项就对应一个订单项
            CartItem cartItem = cartItemList.get(i);
            //2.1 bookName就是CartItem的bookName
            //设置第i条SQL语句的第一个参数的值
            orderItemArrParam[i][0] = cartItem.getBookName();
            //2.2 price、imgPath、itemCount、itemAmount都是CartItem中对应的数据 
            //设置第i条SQL语句的第二个参数的值
            orderItemArrParam[i][1] = cartItem.getPrice();
            //设置第i条SQL语句的第三个参数的值
            orderItemArrParam[i][2] = cartItem.getImgPath();
            //设置第i条SQL语句的第四个参数的值
            orderItemArrParam[i][3] = cartItem.getCount();
            //设置第i条SQL语句的第五个参数的值
            orderItemArrParam[i][4] = cartItem.getAmount();
            //2.3 orderId就是第一步中保存的订单的id
            //设置第i条SQL语句的第六个参数的值
            orderItemArrParam[i][5] = orderId;

            //封装批量更新图书库存和销量的二维数组参数
            // 设置第i条SQL语句的第一个参数: 就是要增加的销量就是cartItem的count
            bookArrParam[i][0] = cartItem.getCount();
            // 设置第i条SQL语句的第二个参数: 就是要减少的库存就是cartItem的count
            bookArrParam[i][1] = cartItem.getCount();
            // 设置第i条SQL语句的第三个参数: 就是要修改的图书的bookId就是cartItem的bookId
            bookArrParam[i][2] = cartItem.getBookId();
        }
        //2.4 调用持久层OrderItemDao的insertOrderItemArr方法进行批量添加
        orderItemDao.insertOrderItemArr(orderItemArrParam);



        //3.1 调用持久层BookDao的updateBookArr方法进行批量更新
        bookDao.updateBookArr(bookArrParam);

        //4. 返回订单号
        return orderSequence;
    }
}
1.5.5 orderDao.createOrder(Order order)
package com.atguigu.dao.impl;

import com.atguigu.dao.BaseDao;
import com.atguigu.dao.OrderDao;
import com.atguigu.pojo.Order;
import com.atguigu.utils.JDBCutils;

import java.sql.*;

public class OrderDaoImpl  extends BaseDao implements OrderDao {
    // 添加订单 返回自增的主键
    @Override
    public int createOrder(Order order) {
        try {
            //因为使用DBUtils执行增删改的SQL语句没法获取自增长的id主键,所以我们只能使用原始的JDBC执行这个添加数据的SQL语句并且获取自增长的id
            // 1.获取连接
            Connection connection = JDBCutils.getConnection();
            // 2.准备sql
            String sql = "insert into t_order values(null,?,?,?,?,?,?)";
            // 3.创建命令发送器
            PreparedStatement pst = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            // 4.填充数据
            pst.setObject(1, order.getOrdersequence());
            pst.setObject(2, order.getCreatetime());
            pst.setObject(3, order.getTotalcount());
            pst.setObject(4, order.getTotalamount());
            pst.setObject(5, order.getOrderstatus());
            pst.setObject(6, order.getUserid());
            // 5.执行命令
            pst.executeUpdate();
            // 6.获取自增的主键
            ResultSet rs = pst.getGeneratedKeys();
            int anInt = 0;
            // 因为自增长的主键只有一个值 所以不需要while循环遍历
            if (rs.next()){
                // 因为自增长键值只有一个,所以这里直接getObject(1)即可
                anInt = rs.getInt(1);
            }
            return anInt;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }

    }
}

1.5.6 orderItemDao.insertItems(Object[][] itemParams)
package com.atguigu.bookstore.dao.impl;

import com.atguigu.bookstore.dao.BaseDao;
import com.atguigu.bookstore.dao.OrderItemDao;
import com.atguigu.bookstore.entity.OrderItem;

    @Override
    public void insertItems(Object[][] itemParams) {
        // 批量新增订单明细
        String sql = "insert into t_order_item values(null,?,?,?,?,?,?)";
        batch(sql, itemParams);
    }
}
1.5.7 bookDao.batchUpdateBook(Object[][] bookParams)
    @Override
    public void batchUpdateBook(Object[][] bookParams) {
        String sql = "update books set sales = sales + ?, stock = stock - ? where id = ?";
        batch(sql, bookParams);
    }

2. 结账过程中使用事务

2.1 事务回顾

2.1.1 ACID属性
  • A:原子性 事务中包含的数据库操作缺一不可,整个事务是不可再分的。

  • C:一致性 事务执行之前,数据库中的数据整体是正确的;事务执行之后,数据库中的数据整体仍然是正确的。

    • 事务执行成功:提交(commit)
    • 事务执行失败:回滚(rollback)
  • I:隔离性 数据库系统同时执行很多事务时,各个事务之间基于不同隔离级别能够在一定程度上做到互不干扰。简单说就是:事务在并发执行过程中彼此隔离。

  • D:持久性 事务一旦提交,就永久保存到数据库中,不可撤销。

2.1.2 隔离级别
① 并发问题
并发问题问题描述
脏读当前事务读取了其他事务尚未提交的修改
如果那个事务回滚,那么当前事务读取到的修改就是错误的数据
不可重复读当前事务中多次读取到的数据的内容不一致(数据行数一致,但是行中的具体内容不一致)
幻读当前事务中多次读取到的数据行数不一致
② 隔离级别
隔离级别描述能解决的并发问题
读未提交允许当前事务读取其他事务尚未提交的修改啥问题也解决不了
读已提交允许当前事务读取其他事务已经提交的修改脏读
可重复读当前事务执行时锁定当前记录,不允许其他事务操作脏读、不可重复读
串行化当前事务执行时锁定当前表,不允许其他事务操作脏读、不可重复读、幻读

2.2 JDBC事务控制

2.2.1 同一个数据库连接

只有当多次数据库操作是使用的同一个连接的时候,才能够保证这几次数据库操作在同一个事务中执行

在这里插入图片描述

2.2.2 关闭事务的自动提交
connection.setAutoCommit(false);
2.2.3 提交事务
connection.commit();
2.2.4 回滚事务
connection.rollBack();
2.2.5 事务整体的代码块
try{
    
    // 关闭事务的自动提交
    connection.setAutoCommit(false);
    
    // 事务中包含的所有数据库操作
    
    // 提交事务
    connection.commit();
}catch(Excetion e){
    
    // 回滚事务
    connection.rollBack();
    
} finally {
    connection.setAutoCommit(true);
    //回收到连接池
    connection.close();
}

2.3 将事务对接到书城项目中

2.3.1 三层架构中事务要对接的位置

从逻辑上来说,一个事务对应一个业务方法(Service层的一个方法)。

在这里插入图片描述

2.3.2 假想

每一个Service方法内部,都套用了事务操作所需要的try…catch…finally块。

2.3.3 假想代码的缺陷
  • 会出现大量的冗余代码:我们希望能够抽取出来,只写一次
  • 对核心业务功能是一种干扰:我们希望能够在编写业务逻辑代码时专注于业务本身,而不必为辅助性质的套路代码分心
  • 将持久化层对数据库的操作写入业务逻辑层,是对业务逻辑层的一种污染,导致持久化层和业务逻辑层耦合在一起
2.3.4 事务代码抽取
  • 只要是Filter拦截到的请求都会从doFilter()方法经过
  • chain.doFilter(req, resp);可以包裹住将来要执行的所有方法
  • 事务操作的try…catch…finally块只要把chain.doFilter(req, resp)包住,就能够包住将来要执行的所有方法
2.3.5 编写一个TransactionFilter来统一处理事务
package com.atguigu.filter;

import com.atguigu.utils.JDBCutils;

import javax.servlet.*;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;

// 事务处理的Filter
public class TransactionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        Connection connection = null;
        try {
            connection = JDBCutils.getConnection();
            // 开启事务
            connection.setAutoCommit(false);
            filterChain.doFilter(servletRequest, servletResponse);
            // 没有发生异常 提交事务
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                // 出现异常 回滚事务
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            throw new RuntimeException(e.getMessage());

        }
    }

    @Override
    public void destroy() {

    }
}

2.3.6 配置TransactionFilter指定其拦截要进行事务控制的请求
    <filter>
        <filter-name>TransactionFilter</filter-name>
        <filter-class>com.atguigu.filter.TransactionFilter</filter-class>
    </filter>
<!--    order路径的所有方法都会执行事务操作-->
<!--    别的路径需要添加事务操作 可以在添加一个<url-pattern>-->
    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <url-pattern>/protected/order</url-pattern>
    </filter-mapping>
2.3.7 创建CloseConnectionFilter用于统一关闭连接
    <filter>
        <filter-name>CloseConnectionFilter</filter-name>
        <filter-class>com.atguigu.filter.CloseConnectionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CloseConnectionFilter</filter-name>
        <url-pattern>/index.html</url-pattern>
        <url-pattern>/book</url-pattern>
        <url-pattern>/user</url-pattern>
        <url-pattern>/protected/order</url-pattern>
    </filter-mapping>

Java代码如下:

package com.atguigu.filter;

import com.atguigu.utils.JDBCutils;

import javax.servlet.*;
import java.io.IOException;

public class CloseConnectionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }finally {
            try {
                // 关闭连接实际是 放回池子
                JDBCutils.closeConnection();
                System.out.println("关闭连接执行了.....");
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }
        }

    }

    @Override
    public void destroy() {

    }
}

2.3.8 全局统一的异常处理
  1. 所有的Dao和Service的方法都抛最大的异常

  2. 在Servlet中对异常进行try…catch,在catch中做相应的处理(例如跳转到错误页面),然后在当前方法中throw new RuntimeException(e.getMessage());

  3. 在ModelBaseServlet的catch块里面throw new RuntimeException(e.getMessage())

  4. 在LoginFilter、TransactionFilter、CloseConnectionFilter中都需要对异常进行try…catch,然后在catch块中

    throw new RuntimeException(e.getMessage());

  5. 创建一个ExceptionFilter,该Filter要配置在所有的Filter之前,用来统一处理异常

    package com.atguigu.filter;
    
    import javax.servlet.*;
    import java.io.IOException;
    
    public class ExceptionFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            try {
                filterChain.doFilter(servletRequest, servletResponse);
            } catch (Exception e) {
                e.printStackTrace();
                // 请求转发
                servletRequest.getRequestDispatcher("/WEB-INF/pages/error.html").forward(servletRequest, servletResponse);
            }
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    
        <filter>
            <filter-name>ExceptionFilter</filter-name>
            <filter-class>com.atguigu.filter.ExceptionFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>ExceptionFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值