day13 书城项目第五阶段

功能二 结账

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
    • 从逻辑和表结构的角度来说,这其实是一个外键。
    • 但是开发过程中建议先不要加外键约束:因为开发过程中数据尚不完整,加了外键约束开发过程中使用测试数据非常不方便,建议项目预发布时添加外键约束测试。

1.1.2 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.2 逻辑模型

1.2.1 Order类

package com.atguigu.bean;

/*
包名:com.atguigu.bean

@author Chunsheng Zhang
日期2021-05-19 09:16
/
public class Order {
private Integer orderId;
private String orderSequence;
private String createTime;
private Integer totalCount;
private Double totalAmount;
private Integer orderStatus;
private Integer userId;

  1. @Override<br /> public String toString() {<br /> return "Order{" +<br /> "orderId=" + orderId +<br /> ", orderSequence='" + orderSequence + '\'' +<br /> ", createTime='" + createTime + '\'' +<br /> ", totalCount='" + totalCount + '\'' +<br /> ", totalAmount='" + totalAmount + '\'' +<br /> ", orderStatus=" + orderStatus +<br /> ", userId=" + userId +<br /> '}';<br /> }
  2. public Order() {<br /> }
  3. public Order(Integer orderId, String orderSequence, String createTime, Integer totalCount, Double totalAmount, Integer orderStatus, Integer userId) {<br /> this.orderId = orderId;<br /> this.orderSequence = orderSequence;<br /> this.createTime = createTime;<br /> this.totalCount = totalCount;<br /> this.totalAmount = totalAmount;<br /> this.orderStatus = orderStatus;<br /> this.userId = userId;<br /> }
  4. public Integer getOrderId() {<br /> return orderId;<br /> }
  5. public void setOrderId(Integer orderId) {<br /> this.orderId = orderId;<br /> }
  6. public String getOrderSequence() {<br /> return orderSequence;<br /> }
  7. public void setOrderSequence(String orderSequence) {<br /> this.orderSequence = orderSequence;<br /> }
  8. public String getCreateTime() {<br /> return createTime;<br /> }
  9. public void setCreateTime(String createTime) {<br /> this.createTime = createTime;<br /> }
  10. public Integer getTotalCount() {<br /> return totalCount;<br /> }
  11. public void setTotalCount(Integer totalCount) {<br /> this.totalCount = totalCount;<br /> }
  12. public Double getTotalAmount() {<br /> return totalAmount;<br /> }
  13. public void setTotalAmount(Double totalAmount) {<br /> this.totalAmount = totalAmount;<br /> }
  14. public Integer getOrderStatus() {<br /> return orderStatus;<br /> }
  15. public void setOrderStatus(Integer orderStatus) {<br /> this.orderStatus = orderStatus;<br /> }
  16. public Integer getUserId() {<br /> return userId;<br /> }
  17. public void setUserId(Integer userId) {<br /> this.userId = userId;<br /> }<br />}<br />

1.2.2 OrdrItem类

package com.atguigu.bean;

/*
包名:com.atguigu.bean

@author Chunsheng Zhang
日期2021-05-19 10:13
/
public class OrderItem {
private Integer itemId;
private String bookName;
private Double price;
private String imgPath;
private Integer itemCount;
private Double itemAmount;
private Integer orderId;

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

public OrderItem() {<br />    }

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

public Integer getItemId() {<br />        return itemId;<br />    }

public void setItemId(Integer itemId) {<br />        this.itemId = itemId;<br />    }

public String getBookName() {<br />        return bookName;<br />    }

public void setBookName(String bookName) {<br />        this.bookName = bookName;<br />    }

public Double getPrice() {<br />        return price;<br />    }

public void setPrice(Double price) {<br />        this.price = price;<br />    }

public String getImgPath() {<br />        return imgPath;<br />    }

public void setImgPath(String imgPath) {<br />        this.imgPath = imgPath;<br />    }

public Integer getItemCount() {<br />        return itemCount;<br />    }

public void setItemCount(Integer itemCount) {<br />        this.itemCount = itemCount;<br />    }

public Double getItemAmount() {<br />        return itemAmount;<br />    }

public void setItemAmount(Double itemAmount) {<br />        this.itemAmount = itemAmount;<br />    }

public Integer getOrderId() {<br />        return orderId;<br />    }

public void setOrderId(Integer orderId) {<br />        this.orderId = orderId;<br />    }<br />}<br />

2. 创建组件

2.1 持久化层

day13_书城项目第五阶段 - 图1

2.2 业务逻辑层

day13_书城项目第五阶段 - 图2

2.3 表述层

day13_书城项目第五阶段 - 图3

3. 功能步骤

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

4. 案例思路

day13_书城项目第五阶段 - 图4

5. 代码实现

5.1 购物车页面结账超链接

cart.html
去结账

5.2 OrderClientServlet.checkout()

package com.atguigu.servlet.model;

import com.atguigu.bean.Cart;
import com.atguigu.bean.User;
import com.atguigu.constant.BookStoreConstants;
import com.atguigu.service.OrderService;
import com.atguigu.service.impl.OrderServiceImpl;
import com.atguigu.servlet.base.ModelBaseServlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/
@author Chunsheng Zhang
日期2021-05-19 09:05
*/
public class OrderClientServlet extends ModelBaseServlet {
private OrderService orderService = new OrderServiceImpl();
/

结算
@param request
@param response
/
public void checkout(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
//1. 从session中获取购物车信息
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute(BookStoreConstants.CARTSESSIONKEY);
//2. 从session中获取用户信息
User loginUser = (User) session.getAttribute(BookStoreConstants.USERSESSIONKEY);
//3. 调用业务层的方法进行订单结算,获取订单号
String orderSequence = orderService.checkout(loginUser, cart);
//4. 清空购物车
session.removeAttribute(BookStoreConstants.CARTSESSIONKEY);
//5. 将订单号存储到请求域中,然后跳转到checkout.html
request.setAttribute(“orderSequence”,orderSequence);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}finally {
processTemplate(“cart/checkout”,request,response);
}
}
}

5.3 BaseDao.batchUpdate()

/*
批处理方法
@param sql
@param paramArr
@return
/
public int[] batchUpdate(String sql,Object[][] paramArr){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.batch(conn,sql,paramArr);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}finnaly{
JDBCUtil.releaseConnection(conn);
}
}

5.4 OrderService.checkout()

package com.atguigu.service.impl;

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

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/*
包名:com.atguigu.service.impl

@author Chunsheng Zhang
日期2021-05-19 09:12
/
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao = new OrderDaoImpl();
private OrderItemDao orderItemDao = new OrderItemDaoImpl();
private BookDao bookDao = new BookDaoImpl();

@Override<br />    public String checkout(User user, Cart cart) throws Exception {<br />        //1. 往订单表中插入一条数据<br />        Order order = new Order();<br />        //1.1 生成一个唯一的订单号:方式一、使用UUID;方式二、使用当前毫秒数拼接用户id<br />        //UUID是java提供的一个生成唯一字符串的工具类<br />        String orderSequence = UUID.randomUUID().toString();<br />        order.setOrderSequence(orderSequence);

    //1.2 创建当前时间的字符串,存入到order中<br />        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");<br />        String createTime = dateFormat.format(new Date());<br />        order.setCreateTime(createTime);

    //1.3 设置订单的totalCount,其实就是cart的totalCount<br />        order.setTotalCount(cart.getTotalCount());

    //1.4 设置订单的totalAmount,其实就是cart的totalAmount<br />        order.setTotalAmount(cart.getTotalAmount());

    //1.5 设置订单状态: 0表示已支付未发货、1表示已发货、2表示已收货<br />        order.setOrderStatus(BookStoreConstants.UNFILLEDORDER);

    //1.6 设置订单的用户id<br />        order.setUserId(user.getUserId());

    //1.7 调用持久层的方法保存order<br />        orderDao.insertOrder(order);

    //2. 往订单详情表中插入多条订单详情(批处理)

    //获取购物车中的所有购物项所在的map<br />        Map<Integer, CartItem> cartItemMap = cart.getCartItemMap();<br />        //获取map的所有值:就是所有的购物项<br />        Collection<CartItem> cartItemCollection = cartItemMap.values();<br />        //将Collection封装到List,集合中存储的就是所有购物项<br />        List<CartItem> cartItemList = new ArrayList<>(cartItemCollection);<br />        // 使用二维数组封装批处理需要的数据:一维表示要执行的数据条数,二维表示sql语句的参数个数<br />        //要执行的数据条数,就是要添加的订单项的个数,就是购物车中购物项的个数<br />        Object[][] insertOrderItemParamArr = new Object[cartItemList.size()][6];

    //3. 批量修改书的库存和销量<br />        //封装批量修改书的库存和销量的参数<br />        Object[][] updateBookParamArr = new Object[cartItemList.size()][3];<br />        for (int i = 0; i < cartItemList.size(); i++) {<br />            //获取到遍历出来的购物项<br />            CartItem cartItem = cartItemList.get(i);

        //封装批量添加订单项的参数<br />            //封装sql语句中的第一个参数book_name的值,就是cartItem的bookName<br />            insertOrderItemParamArr[i][0] = cartItem.getBookName();<br />            //就是cartItem的price<br />            insertOrderItemParamArr[i][1] = cartItem.getPrice();<br />            //就是cartItem的imgPath<br />            insertOrderItemParamArr[i][2] = cartItem.getImgPath();<br />            //就是cartItem的count<br />            insertOrderItemParamArr[i][3] = cartItem.getCount();<br />            //就是cartItem的amount<br />            insertOrderItemParamArr[i][4] = cartItem.getAmount();<br />            //orderId就是订单的id,就是第一步获取的自增长的主键值<br />            insertOrderItemParamArr[i][5] = order.getOrderId();

        //封装批量修改t_book的库存和销量的参数<br />            //要增加的销量,其实就是购物项的count<br />            updateBookParamArr[i][0] = cartItem.getCount();<br />            //要减少的库存,其实也是购物项的count<br />            updateBookParamArr[i][1] = cartItem.getCount();<br />            //要修改的数的id,其实就是购物项的bookId<br />            updateBookParamArr[i][2] = cartItem.getBookId();<br />        }

    //调用持久层orderItemDao的方法,进行批量添加订单项<br />        orderItemDao.insertOrderItemArr(insertOrderItemParamArr);

    //调用持久层的方法,进行批量修改书的销量和库存<br />        bookDao.updateBookArr(updateBookParamArr);

    //功能执行完毕没有出现异常,提交事务<br />        //4. 返回订单的序列号<br />        return orderSequence;<br />    }<br />}

5.5 orderDao.insertOrder(order)

package com.atguigu.dao.impl;

import com.atguigu.bean.Order;
import com.atguigu.dao.BaseDao;
import com.atguigu.dao.OrderDao;
import com.atguigu.utils.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/*
包名:com.atguigu.dao.impl

@author Chunsheng Zhang
日期2021-05-19 09:12
/
public class OrderDaoImpl extends BaseDao implements OrderDao {
@Override
public void insertOrder(Order order) throws Exception{
ResultSet resultSet = null;
Connection conn = null;
try {
//往t_order表中插入一条订单信息
//使用DBUtils没法获取自增长的主键值,所以我们只能使用原始的JDBC执行SQL语句,获取自增长的主键
String sql = “insert into t_order (order_sequence,create_time,total_count,total_amount,order_status,user_id) values (?,?,?,?,?,?)”;
conn = JDBCUtil.getConnection();

        //预编译,并且指定获取自增长主键<br />            PreparedStatement preparedStatement = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);

        //设置参数<br />            preparedStatement.setObject(1, order.getOrderSequence());<br />            preparedStatement.setObject(2, order.getCreateTime());<br />            preparedStatement.setObject(3, order.getTotalCount());<br />            preparedStatement.setObject(4, order.getTotalAmount());<br />            preparedStatement.setObject(5, order.getOrderStatus());<br />            preparedStatement.setObject(6, order.getUserId());

        //执行sql语句<br />            preparedStatement.executeUpdate();

        //获取自增长主键值<br />            resultSet = preparedStatement.getGeneratedKeys();<br />            if (resultSet.next()) {<br />                int orderId = resultSet.getInt(1);<br />                //将orderId存入到order对象中<br />                order.setOrderId(orderId);<br />            }<br />        } catch (SQLException e) {<br />            e.printStackTrace();<br />            throw new RuntimeException(e.getMessage());<br />        }<br />    }<br />}

5.6 orderItemDao.insertOrderItemArr(insertOrderItemParamArr)

package com.atguigu.dao.impl;

import com.atguigu.bean.OrderItem;
import com.atguigu.dao.BaseDao;
import com.atguigu.dao.OrderItemDao;

/*
包名:com.atguigu.dao.impl

@author Chunsheng Zhang
日期2021-05-19 10:13
/
public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {

@Override<br />    public void insertOrderItemArr(Object[][] insertOrderItemParamArr) throws Exception{<br />        String sql = "insert into t_order_item (book_name,price,img_path,item_count,item_amount,order_id) values (?,?,?,?,?,?)";<br />        batchUpdate(sql,insertOrderItemParamArr);<br />    }<br />}

5.7 bookDao.updateBookArr(updateBookParamArr)

@Override
public void updateBookArr(Object[][] updateBookParamArr) throws Exception {
String sql = “update t_book set sales=sales+?,stock=stock-? where book_id=?”;
batchUpdate(sql,updateBookParamArr);
}

功能三 结账过程中使用事务

1. 事务回顾

1.1 ACID属性

  • A:原子性 事务中包含的数据库操作缺一不可,整个事务是不可再分的。
  • C:一致性 事务执行之前,数据库中的数据整体是正确的;事务执行之后,数据库中的数据整体仍然是正确的。
    • 事务执行成功:提交(commit)
    • 事务执行失败:回滚(rollback)
  • I:隔离性 数据库系统同时执行很多事务时,各个事务之间基于不同隔离级别能够在一定程度上做到互不干扰。简单说就是:事务在并发执行过程中彼此隔离。
  • D:持久性 事务一旦提交,就永久保存到数据库中,不可撤销。

1.2 隔离级别

1.2.1 并发问题
并发问题 问题描述
脏读 当前事务读取了其他事务尚未提交的修改
如果那个事务回滚,那么当前事务读取到的修改就是错误的数据
不可重复读 当前事务中多次读取到的数据不一致(数据行数一致,但是行中的具体内容不一致)
幻读 当前事务中多次读取到的数据行数不一致

1.2.2 隔离级别
隔离级别 描述 能解决的并发问题
读未提交 允许当前事务读取其他事务尚未提交的修改 啥问题也解决不了
读已提交 允许当前事务读取其他事务已经提交的修改 脏读
可重复读 当前事务执行时锁定当前记录,不允许其他事务操作 脏读、不可重复读
串行化 当前事务执行时锁定当前表,不允许其他事务操作 脏读、不可重复读、幻读

2. JDBC事务控制

2.1 同一个数据库连接

只有当多次数据库操作是使用的同一个连接的时候,才能够保证这几次数据库操作在同一个事务中执行
day13_书城项目第五阶段 - 图5

2.2 关闭事务的自动提交

connection.setAutoCommit(false);

2.3 提交事务

connection.commit();

2.4 回滚事务

connection.rollBack();

2.5 事务整体的代码块

try{

// 关闭事务的自动提交<br />    connection.setAutoCommit(false);

// 事务中包含的所有数据库操作

// 提交事务<br />    connection.commit();<br />}catch(Excetion e){

// 回滚事务<br />    connection.rollBack();

}finally{
// 释放数据库连接
connection.close();
}

3. 将事务对接到书城项目中

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

从逻辑上来说,一个事务对应一个业务方法(Service层的一个方法)。
day13_书城项目第五阶段 - 图6

3.2 假想

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

3.3 假想代码的缺陷

  • 会出现大量的冗余代码:我们希望能够抽取出来,只写一次
  • 对核心业务功能是一种干扰:我们希望能够在编写业务逻辑代码时专注于业务本身,而不必为辅助性质的套路代码分心
  • 将持久化层对数据库的操作写入业务逻辑层,是对业务逻辑层的一种污染,导致持久化层和业务逻辑层耦合在一起

3.4 事务代码抽取

  • 只要是Filter拦截到的请求都会从doFilter()方法经过
  • chain.doFilter(req, resp);可以包裹住将来要执行的所有方法
  • 事务操作的try…catch…finally块只要把chain.doFilter(req, resp)包住,就能够包住将来要执行的所有方法

3.5 编写一个TransactionFilter来统一处理事务

package com.atguigu.filter;

import com.atguigu.utils.JDBCUtil;

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

/*
@author Chunsheng Zhang
日期2021-05-19 14:34
/
public class TransactionFilter implements Filter {
@Override
public void destroy() {
}

@Override<br />    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {<br />        Connection conn = null;<br />        try {<br />            //开启事务<br />            conn = JDBCUtil.getConnection();<br />            conn.setAutoCommit(false);

        chain.doFilter(req, resp);<br />            //提交事务<br />            conn.commit();<br />        } catch (Exception e) {<br />            e.printStackTrace();<br />            try {<br />                conn.rollback();<br />            } catch (SQLException ex) {<br />                ex.printStackTrace();<br />            }<br />            throw new RuntimeException(e.getMessage());<br />        }finally {<br />            try {<br />                conn.setAutoCommit(true);<br />            } catch (SQLException e) {<br />                e.printStackTrace();<br />            }<br />        }<br />    }

@Override<br />    public void init(FilterConfig config) throws ServletException {

}

}

3.6 配置TransactionFilter指定其拦截要进行事务控制的请求


TransactionFilter
com.atguigu.filter.TransactionFilter


TransactionFilter

/protected/orderClient
#### 3.7 保证所有数据库操作使用同一个连接 『重要发现』:在书城项目中所有执行SQL语句的代码都是通过JDBCUtils.getConnection()方法获取数据库连接。所以我们可以通过重构JDBCUtils.getConnection()方法实现:所有数据库操作使用同一个连接。
day13_书城项目第五阶段 - 图7 ##### 3.6.1 从数据源中只拿出一个 为了保证各个需要Connection对象的地方使用的都是同一个对象,我们从数据源中只获取一个Connection。不是说整个项目只用一个Connection,而是说调用JDBCUtils.getConnection()方法时,只使用一个。所以落实到代码上就是:每次调用getConnection()方法时先检查是否已经拿过了,拿过就给旧的,没拿过给新的。
##### 3.6.2 公共区域 为了保证各个方法中需要Connection对象时都能拿到同一个对象,需要做到:将唯一的对象存入一个大家都能接触到的地方。
day13_书城项目第五阶段 - 图8 结论:使用线程本地化技术实现Connection对象从上到下传递。
#### 3.7 线程本地化 ##### 3.7.1 确认同一个线程 在从Filter、Servlet、Service一直到Dao运行的过程中,我们始终都没有做类似new Thread().start()这样开启新线程的操作,所以整个过程在同一个线程中。
##### 3.7.2 一条小河 day13_书城项目第五阶段 - 图9 ##### 3.7.3 一个线程 day13_书城项目第五阶段 - 图10 ##### 3.7.4 ThreadLocal的API 1. set(T t)方法:在当前线程中,往ThreadLocal对象中存入一个数据 1. get()方法:在当前线程中,从ThreadLocal对象中取出数据 1. remove()方法: 移除ThreadLocal中保存的当前线程的数据 ##### 3.7.5 结论 TheadLocal的基本结论: 一个ThreadLocal对象,在一个线程中只能存储一个数据,在该线程的任何地方调用get()方法获取到的都是同一个数据
### 4. 代码实现 #### 4.1 重构JDBCUtils类 - 要点1:将ThreadLocal对象声明为静态成员变量 - 要点2:重构获取数据库连接的方法 - 要点3:重构释放数据库连接的方法 package com.atguigu.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties; /
包名:com.atguigu.jdbc.utils

@author Chunsheng Zhang
日期2021-04-29 15:15
这个工具类中会提供仨方法:
1. 获取连接池对象
2. 从连接池中获取连接
3. 将链接归还到连接池
*/
public class JDBCUtil {
private static DataSource dataSource;
private static ThreadLocal connectionThreadLocal = new ThreadLocal<>();
static {
try {
//1. 使用类加载器读取配置文件,转成字节输入流
InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream(“druid.properties”);
//2. 使用Properties对象加载字节输入流
Properties properties = new Properties();
properties.load(is);
//3. 使用DruidDataSourceFactory创建连接池对象
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
} /

获取连接池对象
@return
/
public static DataSource getDataSource(){ return dataSource;
} /**
获取连接,让它在同一个线程中调用的时候拿到的是同一个连接
@return
/
public static Connection getConnection() {
try {
Connection conn = connectionThreadLocal.get();
if (conn == null) {
//说明ThreadLocal中还没有数据
//那么就从连接池中获取一个连接
conn = dataSource.getConnection();
//将这个连接存储到ThreadLocal
connectionThreadLocal.set(conn);
} return conn;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
} public static void releaseConnection(){
try {
//将链接归还回连接池
getConnection().close();
//从ThreadLocal中移除连接
connectionThreadLocal.remove();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
#### 4.2 重构BaseDao - 要点:去除释放数据库连接的操作(转移到过滤器中) package com.atguigu.dao; import com.atguigu.utils.JDBCUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler; import java.sql.Connection;
import java.sql.SQLException;
import java.util.List; /
包名:com.atguigu.dao

@author Chunsheng Zhang
日期2021-05-12 11:00
*/
public class BaseDao {
private QueryRunner queryRunner = new QueryRunner(); /

批处理方法
@param sql
@param paramArr
@return
/
public int[] batchUpdate(String sql,Object[][] paramArr){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.batch(conn,sql,paramArr);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
执行增删改的sql语句
@param sql
@param params
@return
/
public int update(String sql,Object… params){
Connection conn = JDBCUtil.getConnection();
try {
//执行增删改的sql语句,返回受到影响的行数
return queryRunner.update(conn,sql,params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
} /
执行查询一行数据的sql语句,将结果集封装到JavaBean对象中
@param clazz
@param sql
@param params
@return
/
public T getBean(Class clazz,String sql,Object… params){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.query(conn,sql,new BeanHandler<>(clazz),params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
} /

执行查询多行数据的sql语句,并且将结果集封装到List
@param clazz
@param sql
@param params
@return
/
public List getBeanList(Class clazz, String sql, Object… params){
Connection conn = JDBCUtil.getConnection();
try {
return queryRunner.query(conn,sql,new BeanListHandler<>(clazz),params);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
注意:OrderDao中insertOrder()方法也要去掉关闭数据库连接的操作。
@Override
public void insertOrder(Order order) throws Exception{
ResultSet resultSet = null;
Connection conn = null;
try {
//往t_order表中插入一条订单信息
//使用DBUtils没法获取自增长的主键值,所以我们只能使用原始的JDBC执行SQL语句,获取自增长的主键
String sql = “insert into t_order (order_sequence,create_time,total_count,total_amount,order_status,user_id) values (?,?,?,?,?,?)”;
conn = JDBCUtil.getConnection(); //预编译,并且指定获取自增长主键
PreparedStatement preparedStatement = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); //设置参数
preparedStatement.setObject(1, order.getOrderSequence());
preparedStatement.setObject(2, order.getCreateTime());
preparedStatement.setObject(3, order.getTotalCount());
preparedStatement.setObject(4, order.getTotalAmount());
preparedStatement.setObject(5, order.getOrderStatus());
preparedStatement.setObject(6, order.getUserId()); //执行sql语句
preparedStatement.executeUpdate(); //获取自增长主键值
resultSet = preparedStatement.getGeneratedKeys();
if (resultSet.next()) {
int orderId = resultSet.getInt(1);
//将orderId存入到order对象中
order.setOrderId(orderId);
}
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
#### 4.3 创建CloseConnectionFilter用于统一关闭连接

CloseConnectionFilter
com.atguigu.filter.CloseConnectionFilter



CloseConnectionFilter
/bookManager
/protected/orderClient
/index.html
/user

Java代码如下:
package com.atguigu.filter;

import com.atguigu.utils.JDBCUtil;

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

/*
@author Chunsheng Zhang
日期2021-05-19 14:12
/
public class CloseConnectionFilter implements Filter {
@Override
public void destroy() {
}

@Override<br />    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {<br />        try {<br />            chain.doFilter(req, resp);<br />        } catch (Exception e) {<br />            e.printStackTrace();<br />        }finally {<br />            JDBCUtil.releaseConnection();<br />        }<br />    }

@Override<br />    public void init(FilterConfig config) throws ServletException {

}<br />}<br />

4.4 项目异常处理的原则

  1. 所有的Dao和Service的方法都抛最大的异常
  2. 在Servlet中对异常进行try……catch,在catch中做相应的处理(例如跳转到错误页面),如果当前方法需要事务控制,那么一定要在catch中throw new RuntimeException(e.getMessage());
  3. 在ModelBaseServlet的catch块里面throw new RuntimeException(e.getMessage())