🌳显示订单[订单管理]
🌳暂时缺货
需求分析
1.如果某家居库存为0, 首页的"Add to Cart" 按钮显示为"暂时缺货"
2.后台也加上校验. 只有在 库存>0 时, 才能添加到购物车
代码实现
1.修改web/views/customer/index.jsp
<c:if test="${furn.inventory <= 0}">
<button disabled title="Add To Cart" class="add-to-cart">Add
To Cart[缺货]
</button>
</c:if>
<c:if test="${furn.inventory > 0}">
<button furnId="${furn.id}" title="Add To Cart" class="add-to-cart">Add
To Cart
</button>
</c:if>
2.修改src/com/zzw/furns/web/CartServlet.java
, 当添加购物车时, 要保证商品的库存>购物车中的已有的商品数量, 不然库存不够没法继续添加
//添加一个添加家居到购物车的方法
protected void addItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id = DataUtils.parseInt(request.getParameter("id"), 1);//家居id
//根据id获取对应的家居信息
Furn furn = furnService.queryFurnById(id);
//先把正常的逻辑走完, 再处理异常的情况
if (furn != null && furn.getInventory() <= 0) {
response.sendRedirect(request.getHeader("Referer"));
return;
}
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
//得到购物车 有可能是空的,也有可能是上次的
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
Map<Integer, CartItem> items = cart.getItems();
if (cart.isEmpty()) {
CartItem cartItem = new CartItem(id, furn.getName(), 1, furn.getPrice(), furn.getPrice());
cart.addItem(cartItem, furn.getInventory());
}
//构建一条家居明细: id,家居名,数量, 单价, 总价
//count类型为Integer, 不赋值默认值为null
CartItem cartItem = new CartItem(id, furn.getName(), 1, furn.getPrice(), furn.getPrice());
//将家居明细加入到购物车中. 如果家居id相同,数量+1;如果是一条新的商品,那么就新增
cart.addItem(cartItem, furn.getInventory());
System.out.println("cart= " + cart);
String referer = request.getHeader("referer");
response.sendRedirect(referer);
}
3…修改src/com/zzw/furns/entity/Cart.java
//添加购物车, 只有在结账的时候库存变化.
public void addItem(CartItem cartItem, Integer inventory) {
//检查要添加的商品是否已存在购物车中
//如果没有, 则添加; 如果有, 则增加购物车中商品的数量.
//这里默认添加的数量为1
CartItem item = items.get(cartItem.getId());
if (item == null) {
items.put(cartItem.getId(), cartItem);
} else if (item.getCount() < inventory){//添加购物车时, 要保证商品的库存>购物车中商品数量, 不然没法下单发货
item.setCount(item.getCount() + 1);
//修改总价,新的数量✖单价
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));
}
}
需求分析
1.购物车里, 更新家居数量时,前台加以限制.
2.后台也加上校验. 只有在 库存>0 时, 才能更新家居数量
代码实现
1.修改src/com/zzw/furns/web/CartServlet.java
//修改指定cartItem的数量和总价
protected void updateCount(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//传 商品id, 商品count
Integer id = DataUtils.parseInt(request.getParameter("id"), 1);
Integer count = DataUtils.parseInt(request.getParameter("count"), 0);
//得到商品库存
Furn furn = furnService.queryFurnById(id);
if (furn != null && !(furn.getInventory() > 0 && count <= furn.getInventory())) {
response.sendRedirect(request.getHeader("Referer"));
return;
}
//得到购物车
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart != null) {
cart.updateCount(id, count);
}
response.sendRedirect(request.getHeader("Referer"));
}
🌳管理订单
需求分析
1.完成订单管理-查看
2.具体流程参考显示家居
3.静态页面order.html 和 order_detail.html 已提供
程序框架图
代码实现
1.修改com.zzw.furns.OrderDAO
//根据memberId返回Order对象
public List<Order> queryOrderByMemberId(Integer memberId);
2.修改com.zzw.furns.impl.OrderDaoImpl
@Override
public List<Order> queryOrderByMemberId(Integer memberId) {
String sql = "SELECT id, `create_time` as createTime, price, `count`, " +
"`status`, member_id as memberId FROM `order` WHERE member_Id=?" +
" order by createTime desc";
List<Order> orderList = queryMany(sql, Order.class, memberId);
return orderList;
}
3.测试, 修改OrderDaoTest
@Test
public void queryOrderByMemberId() {
List<Order> orderList = orderDAO.queryOrderByMemberId(9);
for (Order order : orderList) {
System.out.println(order);
}
}
4.修改com.zzw.furns.OrderService
//显示订单
public List<Order> queryOrderByMemberId(Integer memberId);
5.修改com.zzw.furns.impl.OrderServiceImpl
@Override
public List<Order> queryOrderByMemberId(Integer memberId) {
List<Order> list = orderDAO.queryOrderByMemberId(memberId);
return list;
}
6.测试,修改com.zzw.furns.impl.OrderServiceTest
@Test
public void queryOrderByMemberId() {
List<Order> orders = orderService.queryOrderByMemberId(9);
for (Order order : orders) {
System.out.println(order);
}
}
7.web层 - 修改src/com/zzw/furns/web/OrderServlet.java
, 增加listByMemberId
方法
protected void listByMemberId(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取到登陆的member对象
Member member = (Member) req.getSession().getAttribute("member");
if (member == null) {//说明用户没有登录
//重定向到登陆页面
req.getRequestDispatcher("/views/member/login.jsp")
.forward(req, resp);
return;//直接返回
}
List<Order> orders = orderService.queryOrderByMemberId(member.getId());
//把订单集合放入到request域中
req.setAttribute("orders", orders);
//请求转发到order.jsp
req.getRequestDispatcher("/views/order/order.jsp")
.forward(req, resp);
}
8.新增web/views/order/order.jsp
<c:forEach items="${requestScope.orders}" var="order">
<tr>
<td class="product-name">${order.id}</td>
<td class="product-name">${order.createTime}</td>
<td class="product-price-cart"><span class="amount">${order.price}</span></td>
<td class="product-name"><a href="#">
<c:choose>
<c:when test="${order.status == 1}">未发货</c:when>
<c:when test="${order.status == 2}">已发货</c:when>
<c:when test="${order.status == 3}">未结账</c:when>
<c:otherwise>错误</c:otherwise>
</c:choose>
</a></td>
<td class="product-remove">
<a href="#"><i class="icon-eye"></i></a>
</td>
</tr>
</c:forEach>
🌳管理订单项
程序框架图
代码实现
1.修改com.zzw.furns.OrderDAO
//根据id返回Order对象
public Order queryOrderById(String id);
2.修改com.zzw.furns.impl.OrderDaoImpl
@Override
public Order queryOrderById(String id) {
String sql = "SELECT id, `create_time` AS createTime, price, `count`, `status`, " +
"member_id AS memberId FROM `order` WHERE id= ?";
Order order = querySingle(sql, Order.class, id);
return order;
}
3.测试, 修改OrderDaoTest
@Test
public void queryOrderById() {
Order order = orderDAO.queryOrderById("968d9002-a92e-445f-b77e-7654d7a7598e");
System.out.println(order);
}
4.修改com.zzw.furns.OrderItemDAO
//返回所有OrderItem对象
public List<OrderItem> queryOrderItemByOrderId(String orderId);
5.修改com.zzw.furns.impl.OrderItemDaoImpl
@Override
public List<OrderItem> queryOrderItemByOrderId(String orderId) {
String sql = "SELECT id, `name`, `count`, price, total_price as totalPrice, " +
"order_id as orderId FROM order_item WHERE order_id = ?";
List<OrderItem> orderItemList = queryMany(sql, OrderItem.class, orderId);
return orderItemList;
}
6.测试, 修改OrderItemDaoTest
@Test
public void queryOrderItemByOrderId() {
String orderId = "54062db9-e514-4218-b784-a0f5db91ef41";
List<OrderItem> orderItemList = orderItemDAO.queryOrderItemByOrderId(orderId);
for (OrderItem orderItem : orderItemList) {
System.out.println(orderItem);
}
}
7.修改com.zzw.furns.OrderService
//根据id返回Order对象
public Order queryOrderById(String id);
8.修改com.zzw.furns.impl.OrderServiceImpl
@Override
public Order queryOrderById(String id) {
Order order = orderDao.queryOrderById(id);
List<OrderItem> orderItems = orderItemDao.queryOrderItemByOrderId(id);
order.setItems(orderItems);
return order;
}
9.测试,修改com.zzw.furns.impl.OrderServiceTest
@Test
public void queryOrderById() {
Order order = orderService.queryOrderById("968d9002-a92e-445f-b77e-7654d7a7598e");
System.out.println(order);
}
10.web层 - 修改src/com/zzw/furns/web/OrderServlet.java
, 增加listOrderItemByOrderId
方法
protected void listOrderItemByOrderId(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String orderId = req.getParameter("orderId");
orderId = (orderId == null) ? "" : orderId;
Order order = orderService.queryOrderById(orderId);
req.setAttribute("order", order);
req.getRequestDispatcher("/views/order/order_detail.jsp").forward(req, resp);
}
11.修改前端order.jsp
<td class="product-remove">
<a href="orderServlet?action=listOrderItemByOrderId&orderId=${order.id}"><i class="icon-eye"></i></a>
</td>
12.新增并修改order_detail.jsp
<c:forEach items="${requestScope.order.items}" var="orderItem">
<tr>
<td class="product-name"><a href="#">${orderItem.name}</a></td>
<td class="product-price-cart"><span class="amount">$${orderItem.price}</span></td>
<td class="product-quantity">${orderItem.count}</td>
<td class="product-subtotal">$${orderItem.totalPrice}</td>
</tr>
</c:forEach>
<div class="cart-shiping-update-wrapper">
<h4>共${requestScope.order.totalCount}件商品 总价 ${requestScope.order.price}元</h4>
<div class="cart-clear">
<a href="#">继 续 购 物</a>
</div>
</div>
🌈过滤器权限验证
需求分析
1.加入过滤器权限验证
2.如果没有登陆, 查看购物车和添加到购物车, 就会自动转到会员登陆页面
代码实现
1.修改web.xml
, 配置过滤器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--过滤器一般我们配置在上面-->
<filter>
<filter-name>AuthFilter</filter-name>
<filter-class>com.zzw.furns.filter.AuthFilter</filter-class>
<init-param>
<!--这里配置了后, 还需要在过滤器中处理-->
<param-name>excludedUrls</param-name>
<param-value>/views/manage/manage_login.jsp,/views/member/login.jsp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<!--这里配置要验证的url
1.在filter-mapping中的url-pattern配置 要拦截/验证的url
2.对于我们不去拦截的url, 就不配置
3.对于要拦截的目录中的某些要放行的资源, 再通过配置指定
-->
<url-pattern>/views/cart/*</url-pattern>
<url-pattern>/views/manage/*</url-pattern>
<url-pattern>/views/member/*</url-pattern>
<url-pattern>/views/order/*</url-pattern>
<url-pattern>/cartServlet</url-pattern>
<url-pattern>/manage/furnServlet</url-pattern>
<url-pattern>/orderServlet</url-pattern>
</filter-mapping>
</web-app>
2.新建src/com/zzw/furns/filter/AuthFilter.java
,过滤器逻辑判断
/**
* 这是用于权限验证的过滤器, 对指定的url进行验证
* 如果登陆过, 就放行; 如果没有登陆, 就回到登陆页面
*
* @author 赵志伟
* @version 1.0
*/
public class AuthFilter implements Filter {
private List<String excludedUrls;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//获取到配置的excludedUrls
String strExcludedUrls = filterConfig.getInitParameter("excludedUrls");
//分割 /views/manage/manage_login.jsp,/views/member/login.jsp
String[] split = strExcludedUrls.split(",");
//将 splitUrl 转成 list
excludedUrls = Arrays.asList(split);
System.out.println("excludedUrls= " + excludedUrls);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("请求/cartServlet 被拦截...");
HttpServletRequest request = (HttpServletRequest) servletRequest;
//得到请求的url
String url = request.getServletPath();
System.out.println("url= " + url);
//判断是否要验证
if (!excludedUrls.contains(url)) {
//获取到登陆的member对象
Member member = (Member) request.getSession().getAttribute("member");
if (member == null) {//说明用户没有登录
//转发到登陆页面, 转发不走过滤器
servletRequest.getRequestDispatcher("/views/member/login.jsp")
.forward(servletRequest, servletResponse);
重定向-拦截-重定向-拦截-重定向-拦截
//((HttpServletResponse) servletResponse)
// .sendRedirect(request.getContextPath() + "/views/member/login.jsp");
return;//直接返回
}
}
//验证通过, 放行
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("请求/cartServlet验证通过, 放行");
}
@Override
public void destroy() {
}
}
🌈事务管理
数据不一致问题
1.将FurnDAOImpl.java的updateFurn方法的sql故意写错. [furnDAO.updateFurn(furn);
由ctrl+alt+b定位到updateFurn的实现方法]
2.在OrderServiceImpl的saveOrder()方法内捕获一下异常, 目的是保证程序能够继续执行
3.查看数据库里的数据会有什么结果. 会出现数据不一致的问题.
我在首页购买了一个小台灯, 数据库中生成了对应的订单和订单项, 但家居表里该小台灯的销量和库存没有变化, 纹丝不动. 相当于客户下单了, 但没有给人家发货.
程序框架图
思路分析
1.使用 Filter + ThreadLocal 来进行事务管理
2.说明: 在一次http请求中, servlet-service-dao 的调用过程, 始终是一个线程, 这是使用ThreadLocal的前提
3.使用ThreadLocal来确保所有dao操作都在同一个Connection内
程序框架图
1.修改src/com/zzw/furns/utils/JdbcUtilsByDruid.java
工具类
public class JdbcUtilsByDruid {
private static DataSource dataSource;
//定义属性ThreadLocal, 这里存放一个Connection
private static ThreadLocal<Connection> threadlocalConn = new ThreadLocal<>();
/**
* 从ThreadLocal获取connection, 从而保证在一个线程中
* 获取的是同一个Connection
*/
public static Connection getConnection() {
Connection connection = threadlocalConn.get();
if (connection == null) {//说明当前的threadlocal没有这个连接
try {
//就从数据库连接池中取出连接放入threadlocal
connection = dataSource.getConnection();
//将连接设置为手动提交, 既不要让它自动提交
connection.setAutoCommit(false);
threadlocalConn.set(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return connection;
}
/**
* 提交事务
*/
public static void commit() {
Connection connection = threadlocalConn.get();
if (connection != null) {
try {
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//1.当提交后, 需要把connection从threadlocalConn中清除掉
//2.不然会造成threadlocalConn长时间持有该连接, 会影响效率
//3.也因为Tomcat底层使用的是线程池技术
threadlocalConn.remove();
}
}
/**
* 说明: 所谓回滚是 回滚/撤销 和connection管理的操作 删除/修改/添加
*/
public static void rollback() {
Connection connection = threadlocalConn.get();
if (connection != null) {
try {
connection.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
threadlocalConn.remove();
}
}
//public static Connection getConnection() {
// try {
// return dataSource.getConnection();
// } catch (SQLException e) {
// throw new RuntimeException(e);
// }
//}
static {
try {
Properties properties = new Properties();
//properties.load(new FileInputStream("src/druid.properties"));
//因为我们是web项目, 它的工作目录在out. 文件的加载,需要使用类加载器
//需要找到我们的工作目录
properties.load(JdbcUtilsByDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
2.修改src/com/zzw/furns/dao/BasicDAO.java
删掉各个方法finally代码块里的close方法. 只有在事务结束后才实施关闭连接的操作. 一是提交事务后关闭连接; 二是增删改出错后, 回滚关闭连接.
public class BasicDAO<T> {
private QueryRunner queryRunner = new QueryRunner();
/**
* @param sql sql语句, 可以有 占位符?
* @param clazz 传入一个类的Class对象, 例如User.class
* @param objects 传入具体的值, 对应sql中的占位符?, 可以有多个
* @return 根据对应的User.class, preparedStatement->resultSet->ArrayList, 返回最终的ArrayList集合
*/
public List<T> queryMany(String sql, Class<T> clazz, Object... objects) {
Connection connection = null;
try {
connection = JdbcUtilsByDruid.getConnection();
List<T> tList =
queryRunner.query(connection, sql, new BeanListHandler<>(clazz), objects);
return tList;
} catch (SQLException e) {
throw new RuntimeException(e);//编译异常->运行异常抛出
}
}
//查询单行, 返回的是一个对象
public T querySingle(String sql, Class<T> clazz, Object... objects) {
Connection connection = null;
try {
connection = JdbcUtilsByDruid.getConnection();
T object
= queryRunner.query(connection, sql, new BeanHandler<>(clazz), objects);
return object;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//查询某一字段
public Object queryScalar(String sql, Object... objects) {
Connection connection = null;
try {
connection = JdbcUtilsByDruid.getConnection();
Object query = queryRunner.query(connection, sql, new ScalarHandler(), objects);
return query;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public int update(String sql, Object... objects) {
Connection connection = null;
try {
//这里是从数据库连接池获取connection
//注意:每次从连接池中取出connection, 不能保证是同一个
//1.我们目前已经是从和当前线程关联的ThreadLocal获取的connection
//2.所以可以保证是同一个连接[在同一个线程中/在同一个请求中 => 因为一个请求对应一个线程]
connection = JdbcUtilsByDruid.getConnection();
return queryRunner.update(connection, sql, objects);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
3.修改控制层src/com/zzw/furns/web/OrderServlet.java
, 进行事务管理
前提OrderServiceImpl里报错的代码取消try-catch, 在OrderServlet控制层捕获
protected void saveOrder(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
//如果购物车为空,或者购物车在session,没有家居信息
if (cart == null || cart.isEmpty()) {
response.sendRedirect(request.getHeader("Referer"));
return;
}
//获取登录到的member
Member member = (Member) session.getAttribute("member");
if (member == null) {
//用户未登录,不可点击订单管理,重定向到登陆页面
request.getRequestDispatcher("/views/member/login.jsp").forward(request, response);
return;
}
String orderId = "";
try {
orderId = orderService.saveOrder(cart, member.getId());
JdbcUtilsByDruid.commit();
} catch (Exception e) {
JdbcUtilsByDruid.rollback();
e.printStackTrace();
}
session.setAttribute("orderId", orderId);
response.sendRedirect(request.getContextPath() + "/views/order/checkout.jsp");
}
Transaction过滤器
程序框架图
体会: 异常机制是可以参与业务逻辑的
代码实现
1.在OrderService控制层里取消捕获异常, 将代码重新改回下述模样
String orderId = orderService.saveOrder(cart, member.getId());
2.同时BasicServlet模板里也取消异常捕获, 或者将异常抛出, 代码如下
String action = req.getParameter("action");
System.out.println("action= " + action);//login register page
try {
Method declaredMethod =
this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
System.out.println("declaredMethod= " + declaredMethod);
System.out.println("this = " + this);//com.zzw.furns.web.MemberServlet@38f54ed7
declaredMethod.invoke(this, req, resp);
System.out.println("this.getClass() = " + this.getClass());
} catch (Exception e) {
//将发生的异常, 继续throw
throw new RuntimeException(e);
}
3.新建src/com/zzw/furns/filter/TransactionFilter.java.
在代码执行完毕后, 会运行到Transaction过滤器的后置代码, 在这里进行异常捕获, 如果发生异常, 则回滚.
public class TransactionFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
try {
//放行
chain.doFilter(request, response);
JdbcUtilsByDruid.commit();//统一提交
} catch (Exception e) {//出现了异常
JdbcUtilsByDruid.rollback();//回滚
e.printStackTrace();
}
}
}
配置web.xml
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.zzw.furns.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!--这里我们对请求都进行事务管理 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
🌈统一错误页面
需求分析
1.如果在访问/操作网站时, 出现了内部错误, 统一显示 500.jsp
2.如果访问/操作的页面/servlet不存在时, 统一显示 404.jsp
思路分析
1.发生错误/异常时, 将错误/异常抛给tomcat
2.在web.xml中配置不同错误显示的页面即可
1.在/views/error
引入404.html, 500.html, 修改成jsp文件
2.将跳转链接改成index.jsp
<a class="active" href="index.jsp">
<h4 style="color: darkblue">您访问的页面不存在 返回首页</h4>
</a>
<a class="active" href="index.jsp">
<h4 style="color: darkblue">sorry,您访问的页面出现了错误 返回首页</h4>
</a>
3.配置web.xml
<!--错误提示的配置一般写在web.xml的下面-->
<!--500 错误提示页面-->
<error-page>
<error-code>500</error-code>
<location>/views/error/500.jsp</location>
</error-page>
<!--404 错误提示页面-->
<error-page>
<error-code>404</error-code>
<location>/views/error/404.jsp</location>
</error-page>
4…修改事务过滤器, 将异常抛给tomcat
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 {
try {
//放行
filterChain.doFilter(servletRequest, servletResponse);
JdbcUtilsByDruid.commit();//统一提交
} catch (Exception e) {//出现了异常
//只有在try{}中出现了异常, 才会进行catch{}
//才会进行回滚
JdbcUtilsByDruid.rollback();//回滚
//抛出异常, 给tomcat. tomcat会根据error-page来显示对应页面
throw new RuntimeException(e);
//e.printStackTrace();
}
}
@Override
public void destroy() {
}
}