书城项目

几个月前刚接触JavaWeb的项目,现在回过头来对所学知识进行总结

第六阶段–购物车

1、购物车模块分析

59939618513

2、购物车模型编写

2.1、购物车模型:

/**
 * 购物车的商品项
 * @outhor Mr.JK
 * @create 2020-04-08  19:50
 */
public class CartItem {
    private Integer id;
    private String name;
    private Integer count;
    private BigDecimal price;
    private BigDecimal totalPrice;
    
    get,set...省略
}

/**
 * 购物车对象
 * @outhor Mr.JK
 * @create 2020-04-08  19:57
 */
public class Cart {

    /**
     * key是商品编号,
     * value是商品信息
     */
    private Map<Integer,CartItem> items = new LinkedHashMap<Integer,CartItem>();

    /**
     * 增加商品项
     * @param cartItem
     */
    public void addItem(CartItem cartItem){
        //先查看购物车是否已经添加过此商品,如果已添加,则数量累加,总金额更新,
        //如果没有添加过,直接放到集合中
        CartItem item = items.get(cartItem.getId());
        if (item == null){
            //之前没有添加过此商品
            items.put(cartItem.getId(),cartItem);
        }else {
            //已经添加过的情况
            item.setCount(item.getCount() + 1);//数量累加
            item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));//得到总金额
        }
    }

    /**
     * 删除商品项
     * @param id
     */
    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 Cart() {
    }

    public Cart(Map<Integer,CartItem> items) {

        this.items = items;
    }

    @Override
    public String toString() {
        return "Cart{" +
                "totalCount=" + getTotalCount() +
                ", totalPrice=" + getTotalPrice() +
                ", items=" + items +
                '}';
    }

    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;
    }


    public Map<Integer,CartItem> getItems() {
        return items;
    }

    public void setItems(Map<Integer,CartItem> items) {
        this.items = items;
    }
}

2.2、购物车的测试:

/**
 * @outhor Mr.JK
 * @create 2020-04-08  20:30
 */
public class CartTest {
    private Cart cart = new Cart();

    @Test
    public void addItem() {
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(2,"数据结构",2,new BigDecimal(100),new BigDecimal(200)));
        System.out.println(cart);
    }

    @Test
    public void delete() {
        Cart cart = new Cart();
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(2,"数据结构",2,new BigDecimal(100),new BigDecimal(200)));
        cart.deleteItem(1);
        System.out.println(cart);
    }

    @Test
    public void clear() {
        Cart cart = new Cart();
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(2,"数据结构",2,new BigDecimal(100),new BigDecimal(200)));
        cart.clear();
        System.out.println(cart);
    }

    @Test
    public void updateCount() {
        Cart cart = new Cart();
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.clear();

        cart.addItem(new CartItem(2,"数据结构",2,new BigDecimal(100),new BigDecimal(200)));
        cart.updateCount(2,8);
        System.out.println(cart);
    }
}

3、加入购物车功能的实现

CartServlet 程序中的代码:

/**
     * 加入购物车
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//        System.out.println("加入购物车");
//        System.out.println("商品编号:" + req.getParameter("id"));

        //获取请求的参数 商品编号
        int id = WebUtils.parseInt(req.getParameter("id"),0);
        //调用bookServlet.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);

        System.out.println(cart);
        System.out.println("请求头Referer的值:" + req.getHeader("Referer"));

        //最后一个添加的商品的名称
        req.getSession().setAttribute("lastName",cartItem.getName());

        //重定向回商品列表页面
        resp.sendRedirect(req.getHeader("Referer"));


    }

index.jsp 页面 js 的代码:

59939675275

<script type="text/javascript">
		$(function () {
			$("button.addToCart").click(function () {

				var bookId = $(this).attr("bookId");
				location.href = "http://localhost:8080/book/cartServlet?action=addItem&id=" + bookId;				
			});
		});

	</script>

图解说明,如何跳回添加商品的页面:

59939696291

4、购物车的展示

<%@ 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"%>
        <script type="text/javascript">
            $(function () {
                //给【删除】绑定单击事件
                $("a.deleteItem").click(function () {
                    return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() +"】吗?")
                });

                //给清空购物车绑定单击事件
                $("#clearCart").click(function () {
                    return confirm("你确定要清空购物车嘛?");
                });

                //给输入框绑定失去焦点事件 ==change内容发生改变事件
                $(".updateCount").change(function () {

                    //获取商品名称
                    var name = $(this).parent().parent().find("td:first").text();
                    var id  = $(this).attr('bookId');
                    //获取商品数量
                    var count = this.value;

                    if(confirm("你确定要将【" + name + "】商品修改数量为:" + count + "嘛?")){
                        //发起请求,给服务器保存修改
						location.href = "http://localhost:8080/book/cartServlet?action=updateCount&count=" + count +"&id=" + id;

                    }else {
                        //defaultValue属性是表单项Dom对象的属性值,他表示默认的value属性值
                        this.value = this.defaultValue;
                    }
                });
            });
        </script>

	</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" style="width: 50px"
                                   bookId="${entry.value.id}"
                                   type="text" value="${entry.value.count}">
                        </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="cart_span"><a id="clearCart" href="cartServlet?action=clear">清空购物车</a></span>
				<span class="cart_span"><a href="orderServlet?action=createorder">去结账</a></span>
			</div>
		</c:if>
	
	</div>


	<%--	静态包含页脚内容--%>
	<%@include file="/pages/common/footer.jsp"%>


</body>
</html>

5、删除购物车商品项

CartServlet 程序:

/**
 * 删除商品项
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void deleteItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //获取商品编号
    int id = WebUtils.parseInt(req.getParameter("id"),0);
    //获取购物车对象
    Cart cart = (Cart)req.getSession().getAttribute("cart");

    if (cart != null){
        //删除了购物车商品项
        cart.deleteItem(id);
        //重定向回购物车展示页面
        resp.sendRedirect(req.getHeader("Referer"));
    }
    
}

购物车/pages/cart/cart.jsp 页面的代码:

删除的请求地址:

59939712559

删除的确认提示操作:

<script type="text/javascript">
    $(function () {
        //给【删除】绑定单击事件
        $("a.deleteItem").click(function () {
            return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() +"】吗?")
        });
        
</script>

6、清空购物车

/**
 * 清空购物车
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void clear(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1.获取购物车对象
    Cart cart = (Cart) req.getSession().getAttribute("cart");
    if (cart != null){
        //清空购物车
        cart.clear();
        //重定向回购物车展示页面
        resp.sendRedirect(req.getHeader("Referer"));
    }

}

cart.jsp 页面的内容

给清空购物车添加请求地址,和添加 id 属性:

59939725802

清空的确认提示操作:

// 给清空购物车绑定单击事件
$("#clearCart").click(function () {
	return confirm("你确定要清空购物车吗?");
})

7、修改购物车商品数量

/**
 * 修改商品数量
 * @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"));
    }
}

修改 pages/cart/cart.jsp 购物车页面:

59939734039

修改商品数量 js 代码:

//给输入框绑定失去焦点事件 ==change内容发生改变事件
          $(".updateCount").change(function () {

              //获取商品名称
              var name = $(this).parent().parent().find("td:first").text();
              var id  = $(this).attr('bookId');
              //获取商品数量
              var count = this.value;

              if(confirm("你确定要将【" + name + "】商品修改数量为:" + count + "嘛?")){
                  //发起请求,给服务器保存修改
location.href = "http://localhost:8080/book/cartServlet?action=updateCount&count=" + count +"&id=" + id;

              }else {
                  //defaultValue属性是表单项Dom对象的属性值,他表示默认的value属性值
                  this.value = this.defaultValue;
              }
          });

8、首页,购物车数据回显

在添加商品到购物车的时候,保存最后一个添加的商品名称:

59939741174

在 pages/client/index.jsp 页面中输出购物车信息:

<div style="text-align: center">
            <c:if test="${not empty sessionScope.cart.items}">
               <%--                    购物车非空--%>
               <span id="cartTotalCount">您的购物车中有${sessionScope.cart.totalCount}件商品</span>
               <div>
                  您刚刚将<span style="color: red" id="cartLastName">${sessionScope.lastName}</span>加入到了购物车中
               </div>
            </c:if>
                <c:if test="${empty sessionScope.cart.items}">
<%--                    购物车为空--%>
                    <span id="cartTotalCount"></span>
                    <div>
                        <span  style="color: red" id="cartLastName" >当前购物车为空</span>
                    </div>

                </c:if>


         </div>

第七阶段–订单

1、订单模块的分析:

59939779697

2、订单模块的实现

2.1、创建订单模块的数据库表

use book;

create table t_order(
	`order_id` varchar(50) primary key, `create_time` datetime,
	`price` decimal(11,2), `status` int,
	`user_id` int,
	foreign key(`user_id`) references t_user(`id`)
);

create table t_order_item(
	`id` int primary key auto_increment, `name` varchar(100),
	`count` int,
	`price` decimal(11,2), `total_price` decimal(11,2),
	`order_id` varchar(50),
	foreign key(`order_id`) references t_order(`order_id`)
);

2.2、创建订单模块的数据模型

/**
 * 订单
 * @outhor Mr.JK
 * @create 2020-04-09  14:21
 */
public class Order {

    private String orderId;
    private Date createTime;
    private BigDecimal price;
    //0未发货,1已发货,2表示已签收
    private Integer status = 0;
    private Integer userId;
    get,set省略
}

/**
 * 订单项
 * @outhor Mr.JK
 * @create 2020-04-09  14:23
 */
public class OrderItem {

    private Integer id;
    private String name;
    private Integer count;
    private BigDecimal price;
    private BigDecimal totalPrice;
    private String orderId;

}

2.3、编写订单模块的 Dao 程序和测试

OrderDao 接口

public interface OrderDao {
	public int saveOrder(Order order);
}

OrderDao 实现

public class OrderDaoImpl extends BaseDao implements OrderDao {
    @Override
    public int saveOrder(Order order) {
    String sql = "insert into t_order(`order_id`,`create_time`,`price`,`status`,`user_id`)
    values(?,?,?,?,?)";
 	return update(sql,order.getOrderId(),order.getCreateTime(),order.getPrice(),order.getStatus(),order.getUs erId());
    }
}

OrderItemDao 接口

public interface OrderItemDao {
	public int saveOrderItem(OrderItem orderItem);
}

OrderItemDao 实现

public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
    @Override
    public int saveOrderItem(OrderItem orderItem) {
    String sql = "insert into t_order_item(`name`,`count`,`price`,`total_price`,`order_id`)
    values(?,?,?,?,?)";
    return update(sql,orderItem.getName(),orderItem.getCount(),orderItem.getPrice(),orderItem.getTotalPrice(), orderItem.getOrderId());
    }
}

测试

/**
 * @outhor Mr.JK
 * @create 2020-04-09  15:18
 */
public class OrderDaoImplTest {
    OrderDao orderDao = new OrderDaoImpl();

    @Test
    public void saveOrder() {

        orderDao.saveOrder(new Order("123456789",new Date(),new BigDecimal(100),0,1));
    }

}

/**
 * @outhor Mr.JK
 * @create 2020-04-09  15:22
 */
public class OrderItemDaoImplTest {
    OrderItemDao orderItemDao = new OrderItemDaoImpl();

    @Test
    public void saveOrderItem() {

        orderItemDao.saveOrderItem(new OrderItem(null,"java从入门到精通",1,new BigDecimal(100),new BigDecimal(100),"123456789"));
        orderItemDao.saveOrderItem(new OrderItem(null,"c从入门到精通",2,new BigDecimal(100),new BigDecimal(200),"123456789"));
        orderItemDao.saveOrderItem(new OrderItem(null,"c++从入门到精通",1,new BigDecimal(100),new BigDecimal(100),"123456789"));
    }

}

2.4、编写订单模块的 Service 和测试

OrderService 接口

public interface OrderService {
	public String createOrder(Cart cart,Integer userId);
}

OrderService 实现类

/**
 * @outhor Mr.JK
 * @create 2020-04-09  15:31
 */
public class OrderServiceImpl implements OrderService {

    private OrderDao orderDao = new OrderDaoImpl();
    private OrderItemDao orderItemDao = new OrderItemDaoImpl();

    @Override
    public String createOrder(Cart cart, Integer userId) {
        //订单号==唯一性
        String orderId = System.currentTimeMillis() + "" + userId;
        //创建一个订单对象
        Order order = new Order(orderId,new Date(),cart.getTotalPrice(),0,userId);

        //保存订单
        orderDao.saveOrder(order);

        //制造错误500
        //int i = 12 / 0;

        //遍历购物车中每一个商品项转换称为订单项保存到数据库
        for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()){
            //获取每一个购物车中的商品项
            CartItem cartItem = entry.getValue();
            //转换为每一个订单项
            OrderItem orderItem = new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId);
            //保存订单项到数据库
            orderItemDao.saveOrderItem(orderItem);
        }

        //清空购物车
        cart.clear();

        return orderId;
    }
}

测试

/**
 * @outhor Mr.JK
 * @create 2020-04-09  15:43
 */
public class OrderServiceImplTest {

    @Test
    public void createOrder() {
        Cart cart = new Cart();
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(1,"java",1,new BigDecimal(100),new BigDecimal(100)));
        cart.addItem(new CartItem(2,"数据结构",2,new BigDecimal(100),new BigDecimal(200)));

        OrderService orderService = new OrderServiceImpl();
        System.out.println("订单号为:" + orderService.createOrder(cart, 1));
    }
}

2.5、编写订单模块的 web 层和页面联调

修改 OrderService 程序:

/**
 * @outhor Mr.JK
 * @create 2020-04-09  15:31
 */
public class OrderServiceImpl implements OrderService {

    private OrderDao orderDao = new OrderDaoImpl();
    private OrderItemDao orderItemDao = new OrderItemDaoImpl();
    private BookDao bookDao = new BookDaoImpl();

    @Override
    public String createOrder(Cart cart, Integer userId) {
        //订单号==唯一性
        String orderId = System.currentTimeMillis() + "" + userId;
        //创建一个订单对象
        Order order = new Order(orderId,new Date(),cart.getTotalPrice(),0,userId);

        //保存订单
        orderDao.saveOrder(order);

        //制造错误500
        //int i = 12 / 0;

        //遍历购物车中每一个商品项转换称为订单项保存到数据库
        for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()){
            //获取每一个购物车中的商品项
            CartItem cartItem = entry.getValue();
            //转换为每一个订单项
            OrderItem orderItem = new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId);
            //保存订单项到数据库
            orderItemDao.saveOrderItem(orderItem);

            //更新库存和销量
            Book book = bookDao.queryBookById(cartItem.getId());
            book.setSales(book.getSales() + cartItem.getCount());
            book.setStock(book.getStock() - cartItem.getCount());
            bookDao.updateBook(book);
        }

        //清空购物车
        cart.clear();

        return orderId;
    }
}

OrderServlet 程序:

/**
 * @outhor Mr.JK
 * @create 2020-04-09  15:47
 */
public class OrderServlet extends BaseServlet {
    
    private OrderService orderService = new OrderServiceImpl();
    
    /**
     * 生产订单
     * @param req
     * @param resp
     */
    protected void createorder(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        //先获取Cart购物车对象
        Cart cart = (Cart)req.getSession().getAttribute("cart");
        //获取用户信息,获取UserId
        User loginuser = (User) req.getSession().getAttribute("user");

        if (loginuser == null){
            req.getRequestDispatcher("/pages/user/login.jsp").forward(req,resp);
            return;
        }

        Integer userId = loginuser.getId();
        //调用orderService.createOrder(Cart,Userid);,生产订单
        String orderId = orderService.createOrder(cart, userId);



        //req.setAttribute("orderId",orderId);
        //请求转发
        //req.getRequestDispatcher("/pages/cart/checkout.jsp").forward(req,resp);

        req.getSession().setAttribute("orderId",orderId);

        resp.sendRedirect(req.getContextPath()+"/pages/cart/checkout.jsp");
    }
}

修改 pages/cart/cart.jsp 页面,结账的请求地址:

59939852131

修改 pages/cart/checkout.jsp 页面,输出订单号:

59939853634

第八阶段–过滤器

1、使用 Filter 过滤器拦截/pages/manager/所有内容,实现权限检查

Filter 代码:

/**
 * @outhor Mr.JK
 * @create 2020-04-10  9:26
 * 拦截未登录进入后台
 */
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);
        }else {
            //放行
            filterChain.doFilter(servletRequest,servletResponse);
        }


    }

    @Override
    public void destroy() {

    }
}

web.xml中的配置

web.xml 中的配置:

<filter>
	<filter-name>ManagerFilter</filter-name> 
    <filter-class>com.atguigu.filter.ManagerFilter</filter-class>
</filter>
<filter-mapping> 
    <filter-name>ManagerFilter</filter-name>
	<url-pattern>/pages/manager/*</url-pattern>
	<url-pattern>/manager/bookServlet</url-pattern>
</filter-mapping>

2、ThreadLocal 的使用

ThreadLocal 的作用,它可以解决多线程的数据安全问题。

ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)

ThreadLocal 的特点:

1、ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)

2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例。

3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型

4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放。

测试类:

public class OrderService {

public void createOrder(){
    String name = Thread.currentThread().getName(); System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" +
    ThreadLocalTest.threadLocal.get());
    new OrderDao().saveOrder();
    }
}

public class OrderDao {

    public void saveOrder(){
    String name = Thread.currentThread().getName(); System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是:" +
    ThreadLocalTest.threadLocal.get());
    }
}

public class ThreadLocalTest {
 
//public static Map<String,Object> data = new Hashtable<String,Object>(); 
    public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
	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();
        }
	}
 
}

3、使用 Filter 和 ThreadLocal 组合管理事务

3.1、使用 ThreadLocal来确保所有 dao 操作都在同一个 Connection 连接对象中完成

原理分析图:

59944393706

JdbcUtils 工具类的修改:

/**
 * @outhor Mr.JK
 * @create 2020-04-03  20:52
 */
public class JdbcUtils {

    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns = new ThreadLocal<>();

    static {

        try {
            Properties properties = new Properties();
            //读取jdbc.properties属性配置文件
            InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //从流中加载数据
            properties.load(inputStream);
            //创建 数据库连接池
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

            //测试代码
            //System.out.println(dataSource.getConnection());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取数据库连接池中的连接
     *
     * @return 返回null, 说明获取连接失败,有值就是获取连接成功
     */
    public static Connection getConnection() {
        //同一个线程获取
        Connection conn = conns.get();

        if (conn == null) {
            try {
                //从数据库连接池获取连接
                conn = dataSource.getConnection();
                conns.set(conn);//保存到ThreadLocal对象中,供后面jdbc使用

                conn.setAutoCommit(false);//设置为手动管理
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;


        //        Connection conn = null;

//        try {
//            conn = dataSource.getConnection();
//        } catch (SQLException e) {
//            e.printStackTrace();
//        } finally {
//        }
//        return conn;
    }

    /**
     * 提交事务,并关闭释放连接
     */
    public static void commitAndClose(){
        Connection conn = conns.get();
        if (conn != null){//如果不等于null,说明之前使用过数据库
            try {
                conn.commit();//提交事务

            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    conn.close();//关闭连接,释放资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
        //一定要执行remove操作,否则就会出错,(因为Tomcat底层使用了线程池技术
        conns.remove();
    }

    /**
     * 回滚事务,并关闭释放连接
     */
    public static void rollbackAdnClose(){
        Connection conn = conns.get();
        if (conn != null){//如果不等于null,说明之前使用过数据库
            try {
                conn.rollback();//回滚事务

            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    conn.close();//关闭连接,释放资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
        //一定要执行remove操作,否则就会出错,(因为Tomcat底层使用了线程池技术
        conns.remove();
    }



    /**
     * 关闭连接,放回数据库连接池

    public static void close(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
     */
}

修改 BaseDao

package com.dao.impl;

import com.utils.JdbcUtils;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * @outhor Mr.JK
 * @create 2020-04-03  21:17
 */
public abstract class BaseDao {

    //使用DbUtils操作数据库
    private QueryRunner queryRunner = new QueryRunner();

    /**
     * update()方法用来执行,Insert/Update/Delete语句
     * @return 如果返回-1,说明执行失败 返回其他表示影响的行数
     */
    public int update(String sql,Object ...args){
        Connection connection = JdbcUtils.getConnection();

        try {
            return queryRunner.update(connection,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 connection = JdbcUtils.getConnection();
        try {
            return queryRunner.query(connection,sql,new BeanHandler<T>(type),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> List<T> queryForList(Class<T> type,String sql,Object ...args) {
        Connection connection = JdbcUtils.getConnection();
        try {
            return queryRunner.query(connection, sql, new BeanListHandler<T>(type), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 执行返回一行一列的sql语句
     * @param sql   执行的sql语句
     * @param args  sql对应的参数值
     * @return
     */
    public Object queryForSingleValue(String sql,Object ...args){
        Connection connection = JdbcUtils.getConnection();
        try {
            return queryRunner.query(connection,sql,new ScalarHandler(),args);
        } catch (SQLException e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


}

3.2、使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。来进行实现的管理

原理分析图:

59944402472

Filter 类代码:

/**
 * 为所有工程统一加上try catch
 * 并把异常都抛给Tomcat服务器处理
 * @outhor Mr.JK
 * @create 2020-04-10  13:25
 */
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.rollbackAdnClose();//回滚事务
            e.printStackTrace();
            throw new RuntimeException(e);//把异常抛给Tomcat管理展示友好错误页面
        }
    }

    @Override
    public void destroy() {

    }
}

在 web.xml 中的配置:

<filter>
	<filter-name>TransactionFilter</filter-name>
	<filter-class>com.atguigu.filter.TransactionFilter</filter-class>
</filter> 
<filter-mapping>
	<filter-name>TransactionFilter</filter-name> <!-- /* 表示当前工程下所有请求 -->
	<url-pattern>/*</url-pattern>
</filter-mapping>

一定要记得把 BaseServlet 中的异常往外抛给 Filter 过滤器

/**
 * @outhor Mr.JK
 * @create 2020-04-05  18:01
 */
public abstract class BaseServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");//关键 解决post乱码问题
        //解决响应的中文乱码
        resp.setContentType("text/html; charset=UTF-8");
        String action = req.getParameter("action");

        try {
            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过滤器
    }

    }
}

3.3、将所有异常都统一交给 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>

第九阶段–Ajax

1、Ajax 验证用户名是否可用

使用 Ajax 验证用户名是否可用。我们需要在页面端,给用户名输入框添加一个失去焦点事件。当用户名输入框失去

焦点的时候,触发事件。获取输入的用户名。然后发送 Ajax 请求到服务器諯去验证。

然后服务器通过 json 数据,返回是否存在,result 为 0 表示 不存在,result 为 1 表示存在。当然我们还要做一个用户名不为空的简单验证。才能让请求发送到服务器端。

1.1、修改pages/user/regist.jsp 页面。给用户名输入框添加失去焦点事件

//页面加载完成后
        $(function () {

            $("#username").blur(function () {
               //1 获取用户名
               var username = this.value;

               $.getJSON("http://localhost:8080/book/userServlet","action=ajaxExistsUsername&username=" + username,function (data) {
                    // console.log(data)
                   if (data.existUsername){
                       $("span.errorMsg").text("用户名已存在!");
                   }else {
                       $("span.errorMsg").text("用户名可用!");
                   }
               });

            });

1.2、修改 UserServlet 类,添加检查用户名是否存在的方法:

/**
 * 用ajax验证用户是否存在,并将数据传输到客户端
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void ajaxExistsUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //获取请求的参数username
    String username = req.getParameter("username");
    //调用userService.existUsername
    boolean existUsername = userService.existUsername(username);
    //把返回的结果封装成为map对象
    Map<String,Object> resultMap = new HashMap<>();
    resultMap.put("existUsername",existUsername);

    Gson gson = new Gson();
    String json = gson.toJson(resultMap);
    resp.getWriter().write(json);
}

2、Ajax 修改购物车模块—添加商品—修改数量

以 Ajax 请求的方式修改购物车的模块。我们修改的功能有,添加到购物车,修改数量,以及删除商品,和清空购物车。我们以添加购物车和修改商品数量为例给大家演示

2.1、添加商品

添加商品到购物车。首先我们要把商品的编号,以 Ajax 的方式传到服务器。然后器添加成功后把购物车的数量,最后一本书的名字返回,给用户显示。

2.1.1、修改

pages/client/index.jsp 页面,添加购物车的 a 标签代码

<div class="book_add">
   <button bookId="${book.id}" class="addToCart">加入购物车</button>
</div>

2.1.2、添加 Ajax 请求的 js 代码

$(function () {
   $("button.addToCart").click(function () {

      var bookId = $(this).attr("bookId");
      //location.href = "http://localhost:8080/book/cartServlet?action=addItem&id=" + bookId;

      $.getJSON("http://localhost:8080/book/cartServlet","action=ajaxAddItem&id=" + bookId,function(data) {
         $("#cartTotalCount").text("您的购物车中有"+ data.totalCount +"件商品");
         $("#cartLastName").text(data.lastName);
      });
      //未解之谜,无刷新出问题,刷新一次俩次出问题,刷新三次正常
      window.location.reload();
      window.location.reload();
      window.location.reload();
   });
   //window.location.reload();
});

2.1.3、修改添加购物车后。搜索下方的购物车显示

<div style="text-align: center">
    <c:if test="${not empty sessionScope.cart.items}">
        <%--                    购物车非空--%>
            <span id="cartTotalCount">您的购物车中有${sessionScope.cart.totalCount}件商品</span>
            <div>
                您刚刚将<span style="color: red" id="cartLastName">${sessionScope.lastName}</span>加入到了购物车中
            </div>
     </c:if>
        <c:if test="${empty sessionScope.cart.items}">
            <%--                    购物车为空--%>
                <span id="cartTotalCount"></span>
                <div>
                    <span  style="color: red" id="cartLastName" >当前购物车为空</span>
                </div>

        </c:if>
</div>

2.1.4、CartServlet 中添加 Ajax 版的添加购物车代码

/**
     * 使用ajax异步添加购物车
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void ajaxAddItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//        System.out.println("加入购物车");
//        System.out.println("商品编号:" + req.getParameter("id"));

        //获取请求的参数 商品编号
        int id = WebUtils.parseInt(req.getParameter("id"),0);
        //调用bookServlet.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);

        System.out.println(cart);
        System.out.println("请求头Referer的值:" + req.getHeader("Referer"));

        //最后一个添加的商品的名称
        req.getSession().setAttribute("lastName",cartItem.getName());

       Map<String,Object> resultMap = new HashMap<>();

       resultMap.put("totalCount",cart.getTotalCount());
       resultMap.put("lastName",cartItem.getName());

        Gson gson = new Gson();
        String resultMapJsonString = gson.toJson(resultMap);
        resp.getWriter().write(resultMapJsonString);
    }

2.2、修改数量

修改购物车数量,我们要把修改的商品编号和数量发送到服务器。然后服务器把修改后商品的总价 item_totalMoney,购物车的总数量 cart_totalCount,以及购物车的总金额 cart_totalMoney 返回用于前端的修改

2.2.1、修改原来购物车更新数量的方法,返回修改后商品的总金额

/**
 *  更新商品数量
 * @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())));//得到总金额

    }
}

修改为:

/**
 *  更新商品数量,带返回值
 * @param id
 * @param count
 */
public BigDecimal 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())));//得到总金额
        return cartItem.getTotalPrice();
    }
    return new BigDecimal(0);
}

2.2.2、修改 pages/cart/cart.jsp 页面中的内容

<%@ 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"%>
        <script type="text/javascript">
            $(function () {
                //给【删除】绑定单击事件
                $("a.deleteItem").click(function () {
                    return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() +"】吗?")
                });

                //给清空购物车绑定单击事件
                $("#clearCart").click(function () {
                    return confirm("你确定要清空购物车嘛?");
                });

                //给输入框绑定失去焦点事件 ==change内容发生改变事件
                $(".updateCount").change(function () {

                    //获取商品名称
                    var name = $(this).parent().parent().find("td:first").text();
                    var id  = $(this).attr('bookId');
                    //获取商品数量
                    var count = this.value;

                    if(confirm("你确定要将【" + name + "】商品修改数量为:" + count + "嘛?")){
                        //发起请求,给服务器保存修改
                  //location.href = "http://localhost:8080/book/cartServlet?action=updateCount&count=" + count +"&id=" + id;

                  var id = $(this).attr("data"); // 修改请求为 Ajax
                  $.getJSON("cartServlet?action=ajaxUpdateItem&id=" + id +
                        "&count=" + count,
                        function(data){

                           alert( JSON.stringify(data) );
                           //$("#item_totalMoney_" + id).html(data.item_totalMoney);
                           //$("#cart_totalCount").html(data.cart_totalCount);
                           //$("#cart_totalMoney").html(data.cart_totalMoney);

                        });


               }else {
                        //defaultValue属性是表单项Dom对象的属性值,他表示默认的value属性值
                        this.value = this.defaultValue;
                    }
                });
            });
        </script>

   </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" style="width: 50px"
                                   bookId="${entry.value.id}"
                                   type="text" value="${entry.value.count}">
                        </td>
                  <td>${entry.value.price}</td>
                  <td id="item_totalMoney_${ cartItem.id }">${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 id="cart_totalCount" class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
            <span class="cart_span">总金额<span id="cart_totalMoney" class="b_price">${sessionScope.cart.totalPrice}</span>元</span>
            <span class="cart_span"><a id="clearCart" href="cartServlet?action=clear">清空购物车</a></span>
            <span class="cart_span"><a href="orderServlet?action=createorder">去结账</a></span>
         </div>
      </c:if>
   
   </div>


   <%--   静态包含页脚内容--%>
   <%@include file="/pages/common/footer.jsp"%>


</body>
</html>

2.2.3、添加 CartServlet 中 ajaxUpdateItem 方法实现 Ajax 请求的修改购物车商品数量

/**
     * ajax版 -- 修改商品数量
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void ajaxUpdateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int id = WebUtils.parseInt(req.getParameter("id"),0);
        int count = WebUtils.parseInt(req.getParameter("count"),1);
        //获取Cart购物车对象
        Cart cart = (Cart) req.getSession().getAttribute("cart");
        if (cart == null){
            //生成一个新的购物车存放到Session对象中
            cart = new Cart();
            req.getSession().setAttribute("cart",cart);
        }
        //更新商品
        BigDecimal totalMoney = cart.updateCount(id, count);
        //创建一个Map返回要显示的内容
        Map<String,Object> result = new HashMap<>();
        result.put("item_totalMoney","" + totalMoney);
        result.put("cart_totalMoney",cart.getTotalPrice());
        result.put("cart_totalCount",cart.getTotalCount());

        Gson gson = new Gson();
        resp.getWriter().write(gson.toJson(result));


        //重定向回购物车展示页面
//        resp.sendRedirect(req.getHeader("Referer"));

    }

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

SVN概述 Previous
书城项目前篇 Next