JavaWeb尚硅谷网上书城项目总结(下)
第七阶段:购物车(Session版本)
7.1 提取购物车模型
- 购物车模块分析:
- 购物车商品项
public class CartItem { private Integer id; private String name; private Integer count; private BigDecimal price; private BigDecimal totalPrice; }
- 购物车对象
public class Cart { private Map<Integer,CartItem> items = new HashMap<Integer,CartItem>(); /** * 添加商品项 */ public void addItem(CartItem cartItem){ CartItem item = items.get(cartItem.getId()); if(item == null){ //之前没添加过此商品 items.put(cartItem.getId(),cartItem); }else{ //已经 添加过的情况 item.setCount(cartItem.getCount()+item.getCount()); item.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(item.getCount()))); } } /** * 删除商品项 */ public void deleteItem(Integer id){ items.remove(id); } /** * 清空商品项 */ public void clear(){ items.clear(); } /** * 修改商品数量 * @param id * @param count */ public void updateCount(Integer id,Integer count){ CartItem cartItem = items.get(id); if(cartItem != null){ cartItem.setCount(count);//修改商品数量 cartItem.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount()))); } } //得到购物车总数量 public Integer getTotalCount() { Integer totalCount = 0; for(Map.Entry<Integer,CartItem> entry : items.entrySet()){ totalCount += entry.getValue().getCount(); } return totalCount; } //得到购物车总价格 public BigDecimal getTotalPrice() { BigDecimal totalPrice = new BigDecimal(0); for(Map.Entry<Integer,CartItem> entry : items.entrySet()){ totalPrice = totalPrice.add(entry.getValue().getTotalPrice()); } return totalPrice; } }
7.2 加入购物车——如何跳回添加商品的页面(重点)
-
CartServlet 程序中的代码:
/** * 加入购物车 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求的参数 商品编号 Integer id = WebUtils.parseInt(req.getParameter("id"),0); // 调用bookService.queryBookById(id):Book得到图书的信息 Book book = bookService.queryBookById(id); // 把图书信息,转换成为CartItem商品项 CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice()); // 调用Cart.addItem(CartItem);添加商品项 Cart cart = (Cart)req.getSession().getAttribute("cart"); if(cart == null){ cart = new Cart(); req.getSession().setAttribute("cart",cart); } cart.addItem(cartItem); //将最后一个添加的商品名称加到session中 req.getSession().setAttribute("lastName",cartItem.getName()); //重定向回之前的页面 String referer = req.getHeader("Referer"); resp.sendRedirect(referer); }
-
index.jsp 页面 js 的代码:
//加入购物车操作 $(".addCartItem").click(function(){ var bookId = $(this).attr("bookId"); $.getJSON("http://localhost:8080/07_book/cartServlet","action=ajaxAddItem&id="+bookId,function(data){ $("#cartTotalCount").text("您的购物车中有"+data.totalCount+"件商品"); $("#cartLastName").text(data.lastName); }) //之前使用同步的方式处理 //在事件相应的function函数中,有一个this对象,这个this对象,是当前正在响应事件的dom对象 // location.href = "http://localhost:8080/07_book/cartServlet?action=addItem&id="+bookId; });
-
重点:如何跳回添加商品的页面
HTTP 请求的头信息里面,Referer字段实际上告诉了服务器,用户在访问当前资源之前的位置
//重定向回之前的页面 String referer = req.getHeader("Referer"); resp.sendRedirect(referer);
7.3 购物车的展示(购物车存在session中)
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>购物车</title>
<%--静态包含 base标签,css样式,jQuery--%>
<%@ include file="/pages/common/head.jsp" %>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="static/img/logo.gif" >
<span class="wel_word">购物车</span>
<%@ include file="/pages/common/login_success_menu.jsp"%>
</div>
<div id="main">
<table>
<tr>
<td>商品名称</td>
<td>数量</td>
<td>单价</td>
<td>金额</td>
<td>操作</td>
</tr>
<c:if test="${empty sessionScope.cart.items}">
<%--如果购物车为空的情况--%>
<tr>
<td colspan="5"><a href="index.jsp">亲,您的购物车为空</a></td>
</tr>
</c:if>
<c:if test="${not empty sessionScope.cart.items}">
<%--如果购物车非空的情况--%>
<c:forEach items="${sessionScope.cart.items}" var="entry">
<tr>
<td>${entry.value.name}</td>
<td><input class="updateCount" type="text" bookId="${entry.value.id}"
value="${entry.value.count}" style="width:50px"/></td>
<td>${entry.value.price}</td>
<td>${entry.value.totalPrice}</td>
<td><a class="deleteItem" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>
</tr>
</c:forEach>
</c:if>
</table>
<%--如果购物车非空才输出页面的内容--%>
<c:if test="${not empty sessionScope.cart.items}">
<div class="cart_info">
<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span>元</span>
<span class="clearCart"><a href="cartServlet?action=clear">清空购物车</a></span>
<span class="cart_span"><a href="pages/cart/checkout.jsp">去结账</a></span>
</div>
</c:if>
</div>
<%--静态包含页脚内容--%>
<%@ include file="/pages/common/footer.jsp" %>
</body>
</html>
7.4 修改购物车中的商品数量
-
修改 pages/cart/cart.jsp 购物车页面
-
修改商品数量 js 代码:
-
CartServlet 程序
/** * 修改商品数量 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求的参数 商品编号、商品数量 int id = WebUtils.parseInt(req.getParameter("id"), 0); int count = WebUtils.parseInt(req.getParameter("count"), 0); //获取cart购物车对象 Cart cart = (Cart)req.getSession().getAttribute("cart"); if(cart != null){ //修改商品数量 cart.updateCount(id,count); //重定向回原来购物车展示页面 resp.sendRedirect(req.getHeader("Referer")); } }
第八阶段——订单模块
第九阶段——事务处理(重点、难点)
9.1 使用 Filter 过滤器实现权限检查
Filter 代码:
public class ManagerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
Object user = httpServletRequest.getSession().getAttribute("user");
if(user == null){
//如果没有登录跳转到登录页面
httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
return ;
}else{
//继续执行接下来的操作
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
}
}
9.2 ThreadLocal的使用(难点)
-
ThreadLocal 的作用,它可以解决多线程的数据安全问题。
-
ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
-
ThreadLocal 的特点:
1、ThreadLocal 可以为当前线程关联一个数据。(它可以像Map一样存取数据,key 为当前线程) 2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例。 3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型 4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放
测试代码:
public class ThreadLocalTest {
// public static Map<String,Object> data = new Hashtable<>();
public static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
private static Random random = new Random();
public static class Task implements Runnable{
@Override
public void run() {
//在run方法中,随机生成一个变量(线程要关联的数据),然后已当前线程名为key保存到map中
Integer i = random.nextInt(1000);
//获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("线程["+name+"]生成的随机数是:" + i);
// data.put(name,i);
threadLocal.set(i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new OrderService().createOrder();
// 在Run方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作
// Object o = data.get(name);
Object o = threadLocal.get();
System.out.println("在线程["+name+"]快结束时取出关联的数据是:" + o);
}
}
public static void main(String[] args){
for(int i = 0;i<3;i++){
new Thread(new Task()).start();
}
}
}
class OrderService {
public void createOrder(){
String name = Thread.currentThread().getName();
System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" + ThreadLocalTest.threadLocal.get());
}
}
执行结果:
9.3 使用Filter和ThreadLocal组合管理事务(难点、重点)
分析订单结账功能,既要生成订单,还要生成订单项,同时还要保证商品的库存等等。所以必须使用事务保证这些操作要么都成功,要么都失败。
要求:
1.保证所有操作都在一个事务内。就必须保证,所有操作都是用同一个Connection连接对象。
2.如何确保所有操作使用同一个Connection连接对象?
可以使用ThreadLocal对象,来确保所有操作都是用同一个Connection对象。(前提条件:所有的操作必须在同一个线程中完成)
-
使用ThreadLocal来确保所有dao操作都在同一个Connection连接对象中完成
JdbcUtils 工具类的修改:
public class JDBCUtils { private static DataSource dataSource = null; private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>(); static{ try { Properties properties = new Properties(); InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); properties.load(is); dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } /** * 获取数据库连接池中的连接 * @return */ public static Connection getConnection(){ Connection conn = conns.get(); if(conn == null){ try { conn = dataSource.getConnection(); conns.set(conn);//保存到ThreadLocal对象中,供后面的jdbc操作使用 conn.setAutoCommit(false);//设置为手动管理事务 } catch (Exception e) { e.printStackTrace(); } } return conn; } /** * 提交事务,并关闭释放连接 */ public static void commitAndClose(){ Connection conn = conns.get(); if(conn != null){ try { conn.commit();//提交事务 } catch (Exception e) { e.printStackTrace(); }finally{ try { conn.close();//关闭连接,资源释放 } catch (SQLException e) { e.printStackTrace(); } } } //移除conns中的连接,防止下次发现不为空直接使用导致出错 conns.remove(); } /** * 回滚事务,并关闭释放连接 */ public static void rollbackAndClose(){ Connection conn = conns.get(); if(conn != null){ try { conn.rollback();//回滚事务 } catch (SQLException e) { e.printStackTrace(); }finally{ try { conn.close();//关闭连接,资源释放 } catch (SQLException e) { e.printStackTrace(); } } } //移除conns中的连接,防止下次发现不为空直接使用导致出错 conns.remove(); } }
-
修改 BaseDao:(把异常捕获改成异常抛出)
public abstract class BaseDao { private QueryRunner queryRunner = new QueryRunner(); /** * update() 方法用来执行 insert/update/delete语句 * @param sql * @param args * @return 如果返回-1,说明执行失败,返回其他表示受影响的行数 */ public int update(String sql,Object...args){ Connection conn = null; try { conn = JDBCUtils.getConnection(); return queryRunner.update(conn,sql,args); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 查询返回一个javaBean的sql语句 * @param type 返回的对象类型 * @param sql 执行的sql语句 * @param args sql对应的参数值 * @param <T> 返回的类型的泛型 * @return */ public <T> T queryForOne(Class<T> type,String sql,Object...args){ Connection conn = null; try{ conn = JDBCUtils.getConnection(); return queryRunner.query(conn,sql,new BeanHandler<T>(type),args); }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); } } /** * 查询返回多个javaBean的sql语句 * @param type 返回的对象类型 * @param sql 执行的sql语句 * @param args sql对应的参数值 * @param <T> 返回的类型的泛型 * @return */ public <T> List<T> queryForList(Class<T> type, String sql, Object...args){ Connection conn = null; try{ conn = JDBCUtils.getConnection(); return queryRunner.query(conn,sql,new BeanListHandler<T>(type),args); }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); } } /** * 执行返回一行一列的sql语句 * @param sql 执行的sql语句 * @param args sql对应的参数值 * @return */ public Object queryForSingleValue(String sql,Object...args){ Connection conn = null; try{ conn = JDBCUtils.getConnection(); return queryRunner.query(conn,sql,new ScalarHandler(),args); }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); } } }
9.4 使用 Filter过滤器统一给所有的Service方法都加上try-catch
分析:
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 {
try {
filterChain.doFilter(servletRequest,servletResponse);
JDBCUtils.commitAndClose();//提交事务
} catch (Exception e) {
JDBCUtils.rollbackAndClose();//回滚事务
e.printStackTrace();
}
}
@Override
public void destroy() {
}
}
web.xml文件:
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>com.zb.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!-- /* 表示当前工程下所有请求-->
<url-pattern>/*</url-pattern>
</filter-mapping>
一定要记得把 BaseServlet 中的异常往外抛给 Filter 过滤器:
public abstract class BaseServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
try {
//获取action业务鉴别字符串,获取相应的业务(方法反射对象)
Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
//调用目标业务
method.invoke(this,req,resp);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);//把异常抛给Filter过滤器
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
}
9.5 将所有异常都统一交给Tomcat,让Tomcat展示友好的错误信息页面
在 web.xml 中我们可以通过错误页面配置来进行管理
<!--error-page标签配置,服务器出错之后,自动跳转的页面-->
<error-page>
<!--error-code是错误类型-->
<error-code>500</error-code>
<!--location标签表示。要跳转去的页面路径-->
<location>/pages/error/error500.jsp</location>
</error-page>
<!--error-page标签配置,服务器出错之后,自动跳转的页面-->
<error-page>
<!--error-code是错误类型-->
<error-code>404</error-code>
<!--location标签表示。要跳转去的页面路径-->
<location>/pages/error/error404.jsp</location>
</error-page>
注意Filter类中要将异常抛给Tomcat:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest,servletResponse);
JDBCUtils.commitAndClose();//提交事务
} catch (Exception e) {
JDBCUtils.rollbackAndClose();//回滚事务
e.printStackTrace();
throw new RuntimeException(e);//把异常抛给Tomcat管理展示友好的错误页面
}
}
第十阶段——使用AJAX
10.1 使用 AJAX 验证用户名是否可用
regist.jsp 页面中的代码:
//当输入框失去焦点时触发
$("#username").blur(function(){
//获取用户名
var username = this.value;
$.getJSON("http://localhost:8080/07_book/userServlet","action=ajaxExistUsername&username="+username,function(data){
if(data.existUsername){
$("span.errorMsg").text("用户名已存在!");
}else{
$("span.errorMsg").text("用户名可用!");
}
});
});
UserServlet 程序中 ajaxExistsUsername 方法:
protected void ajaxExistUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
boolean existUsername = userService.existsUsername(username);
Map<String,Object> resultMap = new HashMap<>();
resultMap.put("existUsername",existUsername);
Gson gson = new Gson();
String json = gson.toJson(resultMap);
resp.getWriter().write(json);
}
10.2 使用AJAX修改把商品添加到购物车
pages/client/index.jsp 页面代码:
//加入购物车操作
$(".addCartItem").click(function(){
var bookId = $(this).attr("bookId");
$.getJSON("http://localhost:8080/07_book/cartServlet","action=ajaxAddItem&id="+bookId,function(data){
$("#cartTotalCount").text("您的购物车中有"+data.totalCount+"件商品");
$("#cartLastName").text(data.lastName);
})
//之前使用同步的方式处理
//在事件相应的function函数中,有一个this对象,这个this对象,是当前正在响应事件的dom对象
// location.href = "http://localhost:8080/07_book/cartServlet?action=addItem&id="+bookId;
});
CartServlet 程序:
/**
* 使用ajax加入购物车
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void ajaxAddItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数 商品编号
Integer id = WebUtils.parseInt(req.getParameter("id"),0);
// 调用bookService.queryBookById(id):Book得到图书的信息
Book book = bookService.queryBookById(id);
// 把图书信息,转换成为CartItem商品项
CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
// 调用Cart.addItem(CartItem);添加商品项
Cart cart = (Cart)req.getSession().getAttribute("cart");
if(cart == null){
cart = new Cart();
req.getSession().setAttribute("cart",cart);
}
cart.addItem(cartItem);
//将最后一个添加的商品名称加到session中
req.getSession().setAttribute("lastName",cartItem.getName());
//返回购物车总的商品数量和最后一个添加的商品名称
Map<String,Object> resultMap = new HashMap<String,Object>();
resultMap.put("totalCount",cart.getTotalCount());
resultMap.put("lastName",cartItem.getName());
Gson json = new Gson();
String resultMapJsonString = json.toJson(resultMap);
resp.getWriter().write(resultMapJsonString);
}
总结
jQuery和JavaScript事件中的return false
问题的引入:想给a标签添加点击事件,但又不想实现跳转。
解决:
在事件的函数中return false
表示阻止与事件关联的默认动作并停止事件的传播。
JQuery的代码:
JavaScript代码:
错误代码:
正确代码: