BookStore项目【商品查询分页添加购物车以及生产订单】

一:商品查询分页展示:

实例如下:,当我们点击上面文学,生活等字样,就会在下面展示出下面的商品。点击发送请求

<a
        href="${pageContext.request.contextPath}/pageServlet?category=wenxue">文学</a>
    <a

 所以啦,咱们得建一个分页的pageServlet并且带着分类的参数去请求。但是之前我们必须新建一个pageBean,这个类虽然属于domain,但是数据库中并没有这个表,他的存在只是存一些我需要的字段,如上面的总页数,当前页,和传递来的参数等.


    private int currentPage; //当前页
	private int pageSize; //每页显示的记录数
	private int count;  //总数
	private int totalPage; //总页数
	private List<Product> products; //商品放入list集合!
	private String category; //筛选参数

先写出来后面有用。

servlet


		//导航按钮的查询条件
		String category = request.getParameter("category");
		if(category==null){
			category="";
		}
		
		
		//初始化每页显示的记录数
		int pageSize = 4;
		
		int currentPage = 1;//当前页
		String currPage = request.getParameter("currentPage");//从上一页或下一页得到的数据
		if(currPage!=null&&!"".equals(currPage)){//第一次访问资源时,currPage可能是null
			currentPage = Integer.parseInt(currPage);
		}
		
		ProductService bs = new ProductService();
		//分页查询,并返回PageBean对象
		PageBean pb = bs.findBooksPage(currentPage,pageSize,category);
		
		request.setAttribute("pb", pb);
		request.getRequestDispatcher("/product_list.jsp").forward(request, response);

这里可以发现,获取的参数会有是否为空的判断,因为当查询所有商品时,就是不传参数,所以在这里给他加上一个空字符。

这里pagesize就是你分的每页多少个,默认当前页是1;因为第一次访问时从currentPage中没有取出当前页(这个值,在点击文学时,不会传递,这个是在点击下一页时传过来的),所以默认给1.如果currentpage里面有值,那么就赋给他,而不是使用1。

service层:

写一个findbookspage方法返回pageBean,将这个pageBean存入域中,那么在前端就可以在域中取出跟页数有关的参数了,

传入的参数有(当前页,一页展示的数量,文学)

//分页查询
	public PageBean findBooksPage(int currentPage, int pageSize,String category) {
		
		try {
			int count  = productDao.count(category);//得到总记录数
			int totalPage = (int)Math.ceil(count*1.0/pageSize); //求出总页数
			List<Product> products= productDao.findBooks(currentPage,pageSize,category);
			
			//把5个变量封装到PageBean中,做为返回值
			PageBean pb = new PageBean();
			pb.setProducts(products);
			pb.setCount(count);
			pb.setCurrentPage(currentPage);
			pb.setPageSize(pageSize);
			pb.setTotalPage(totalPage);
			//在pageBean中添加属性,用于点击上一页或下一页时使用
			pb.setCategory(category);
			
			return pb;
		} catch (SQLException e) {
			e.printStackTrace();

先利用要category查找出所有这类的商品。得到总数。然后求出总页数。并通过当前页,页数,文学三个参数找出第一页的所有商品,并把他们放入list集合中。这样的话,pageBean中的所有变量都齐了,可以封装成一个pageBean返回了。因为servlet就是要这个,他把这个存入域中,前端取出来显示。

dao层:productDao

/**
	 * 得到总记录数
	 * @return
	 * @throws SQLException
	 */
	public int count(String category) throws SQLException {
		QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
		String sql ="select count(*) from products";
		//如果category不是空,就把条件加上
		if(!"".equals(category)){
			sql+=" where category='"+category+"'";
		}
		long l =  (Long)qr.query(sql, new ScalarHandler(1));
		return (int)l;
	}
	
	/**
	 * 查找分页数据
	 * @param currentPage
	 * @param pageSize
	 * @return
	 * @throws SQLException 
	 */
	public List<Product> findBooks(int currentPage, int pageSize,String category) throws SQLException {
		QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
		
		String sql = "select * from products where 1=1";
		List list = new ArrayList();
		if(!"".equals(category)){
			sql+=" and category=?";
			list.add(category);
		}
		sql+=" limit ?,?";
		
		// select * from products where 1=1 and category=? limit ?,?;
		list.add((currentPage-1)*pageSize);
		list.add(pageSize);
		
		return qr.query(sql, new BeanListHandler<Product>(Product.class),list.toArray());
	}

就count中很奇怪,用的是qr.query(sql, new ScalarHandler(1));所以就记住吧。

然后findBooks这个sql语句有点厉害,where 1 =1 ,这样做其实很牛逼。可以防止关键字冲突。就比如这里,如果category为空

那么直接加上limit了。这肯定不行的,所以加上where 1=1就可以,具体讲解如下。

在不使用where 1=1的情况下
if(params.containsKey("name")){
    String key = params.get("name").toString();
    sql+="where a.name='"+key +"'";
}
if(params.containsKey("age")){
    String key = params.get("age").toString();
    sql+="where a.age='"+key +"'";
}
if(params.containsKey("class ")){
    String key = params.get("class ").toString();
    sql+="where a.class ='"+key +"'";
}
这样同时存在两个属性及以上是就会发生冲突

当时用where 1=1 的时候
String sql = “select * from table a where 1=1”;

if(params.containsKey("name")){
    String key = params.get("name").toString();
    sql+=" and a.name='"+key +"'";
}
if(params.containsKey("age")){
    String key = params.get("age").toString();
    sql+=" and a.age='"+key +"'";
}
if(params.containsKey("class ")){
    String key = params.get("class ").toString();
    sql+=" and a.class ='"+key +"'";
}

注意啊,这个方法中,是用list封装了要传入的参数。记下这个方法。返回值的是一个list <product>。正是封装pageBean中需要的。至此,封装的pageBean完成了。那么servlet将它存入了request域中。且页面跳转到了展示商品的页面。

<c:forEach items="${pb.products }" var="b">
											<td>

												<div class="divbookpic">
													<p>
														<a href="#"><img
															src="${pageContext.request.contextPath }/upload/${b.img_url}"
															width="115" height="129" border="0" /> </a>
													</p>
												</div>

												<div class="divlisttitle">
													<a
														href="${pageContext.request.contextPath }/findBookInfoServlet?id=${b.id}">书名:${b.name
														}<br />售价:${b.price } </a>
												</div>
											</td>
										</c:forEach>

因为之前,我们存入的pageBean中含有list集合,我们可以取通过foreach标签取出来。

上下页,跳转有讲究。只传两个参数。当前页和筛选条件(文学,这个不就是咱们开始查询分页的条件吗?只不过这次带了当前页,还是请求原来的servlet。)这页数也是有讲究,

上页:${pb.currentPage==1?1:pb.currentPage-1}&category=${pb.category}"如果当前页是1,那么上页还是1,否则就减一

下页:${pb.currentPage==pb.totalPage?pb.totalPage:pb.currentPage+1}如果到了顶页,那么下页还是当前页,否则加一。

<li class="disablepage"><a
											href="${pageContext.request.contextPath  }/pageServlet?currentPage=${pb.currentPage==1?1:pb.currentPage-1}&category=${pb.category}">&lt;&lt;上一页</a>
										</li>


										<li>第${pb.currentPage }页/共${pb.totalPage }页</li>

										<li class="nextPage"><a
											href="${pageContext.request.contextPath  }/pageServlet?currentPage=${pb.currentPage==pb.totalPage?pb.totalPage:pb.currentPage+1}&category=${pb.category}">&lt;&lt;下一页</a>
										</li>

加入购物车:

当我们点击查看物品详情时,拿着商品id去查找。找出来,存入域中,然后页面回显出来。这个过程就不写了。

商品详情下面就是加入购物车功能。当点击加入购物车时,带着商品id,发送请求

public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		
		String id = request.getParameter("id");
		
		ProductService bs = new ProductService();
		
		Product b = bs.findBookById(id);
		
		//从session中的购物车取出 来
		HttpSession session = request.getSession();
		Map<Product, String> cart = (Map<Product, String>) session.getAttribute("cart");
		int num = 1;
		//如何是第一次访问,没有购物车对象,我们就创建 一个购物车对象
		if(cart==null){
			cart = new HashMap<Product, String>();
			
		}
		//查看当前集合中是否存在b这本书,如果有就把数据取出来加1;
		if(cart.containsKey(b)){
			num=Integer.parseInt(cart.get(b))+1;
		}
		//把图书放入购物车
		cart.put(b, num+"");
		
		//把cart对象放回到session作用域中
		session.setAttribute("cart", cart);
		
		out.print("<a href='"+request.getContextPath()+"/pageServlet'>继续购物</a>,<a href='"+request.getContextPath()+"/cart.jsp'>查看购物车</a>");
	}

注意我怎么存下购物车的商品呢?使用map最好。先通过id查出商品,然后将商品加入map中。但是加入前有许多步骤。

1.这个map咱们肯定不能新创啊,必须从request域中找啊。因为还有可能存在其他的商品被添加了,咱们要在原来的基础上进行添加啊。

2.新建个num默认=1(后面如果发现这个商品在购物车中,就用购物车的num+1.这个是数量)如果为空,就是第一次加入购物车,new个新的map<Product,Stirng>,将product作为key,num做为值。

3.如果取出来的map,包含即将要添加的商品,就把num在原基础上+1.

OK,存入map后,加入到request域中,然后输出一个简单的简单页面,用于继续购买和查看购物车(内含下订单).

继续购买,就是回到原来的页面。如果查看购物车,那么就要把map遍历出来显示在前端了。

<c:set var="sum" value="0" > </c:set>
											<c:forEach items="${cart }" var="entry" varStatus="vs">
												<table width="100%" border="0" cellspacing="0">
													<tr>
														<td width="10%">${vs.count }</td>
														<td width="30%">${entry.key.name }</td>

														<td width="10%">${entry.key.price }</td>
														<td width="20%"><input type="button" value='-'
															style="width:20px"
															onclick="changeNum('${entry.key.id}','${entry.value-1 }','${entry.key.pnum }')">

															<input name="text" type="text" value="${entry.value }"
															style="width:40px;text-align:center" /> <input
															type="button" value='+' style="width:20px"
															onclick="changeNum('${entry.key.id}','${entry.value+1 }','${entry.key.pnum }')">

														</td>
														<td width="10%">${entry.key.pnum }</td>
														<td width="10%">${entry.value*entry.key.price }</td>

														<td width="10%"><a href="${pageContext.request.contextPath}/changeNumServlet?id=${entry.key.id}&num=0"
															style="color:#FF0000; font-weight:bold">X</a></td>
													</tr>
												</table>
												<c:set var="sum" value="${sum+entry.value*entry.key.price }"> </c:set>
											</c:forEach>

map的遍历好好看看下。其中<c:forEach items="${cart }" var="entry" varStatus="vs">这个varStatus怎么用的还是不懂。

当点击结账时,那么就关于生成订单的事了。

生成订单:

这个就设计了三张表。订单表,订单关系表,商品表。

一条订单对应多个订单关系表,

一个商品同时也对应着多个订单关系表。

这里需要说明下,这是java中多对多的写法,数据库中存的都是orderid和productid。

那为啥这里存的是对象呢?因为以后可以随时取出来对象,并且这样并不影响对数据库的操作。直接在dao层通过order

的id也可以操作,更加有扩展性。

订单关系:orderitem

	private Order order;// 订单
	private Product p; // 商品
	private int buynum; // 购物数量

订单:因为是一个订单有多个orderitem,所以这里用list集合存,也可以用set。

private String id; // 订单编号
	private double money; // 订单总价
	private String receiverAddress; // 送货地址
	private String receiverName; // 收货人姓名
	private String receiverPhone; // 收货人电话
	private int paystate; // 订单状态
	private Date ordertime; // 下单时间
	private User user;
	private List<OrderItem> orderItems;//表示一个order对象,对应多个orderitem

商品:那么商品也可以有list啊?为啥没有,额,因为我们是把订单作为主表了,所以这张表可以不要,当然你可以要,但是一般都是这样的,根据需求来。

private String id;
	private String name;
	private double price;
	private int pnum;
	private String category;
	private String description;
	private String img_url;

定交订单时:只有订单的总价,地址,姓名电话等....,订单的状态不需要改动,用默认的就好,因为他还没付钱。然后发出请求

public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//1、封装Order对象
		Order order = new Order();
		try {
			BeanUtils.populate(order, request.getParameterMap());
			order.setId(UUID.randomUUID().toString());
			order.setUser((User)request.getSession().getAttribute("user"));//把session对象中的用户信息保存到order对象中
		} catch (Exception e) {
			e.printStackTrace();
		}
		//2、获取session对象中的购物车数据
		Map<Product, String> cart = (Map<Product, String>) request.getSession().getAttribute("cart");
		
		//3、遍历购物车中的数据,添加到orderItem对象中,同时把多个orderItem添加到list集合中
		List<OrderItem> list = new ArrayList<OrderItem>();
		for (Product p : cart.keySet()) {
			OrderItem oi = new OrderItem();
			oi.setOrder(order);//把Order对象添加到OrderItem中
			oi.setP(p);   //把购物车中的商品对象添加到OrderItem中
			oi.setBuynum(Integer.parseInt(cart.get(p)));//购物车中的商品数量
			
			list.add(oi);//把每个定单项添加到集合中
		}
		
		//4、把集合放入到Order对象 中
		order.setOrderItems(list);
		
		//调用 业务逻辑
		OrderService os = new OrderService();
		os.addOrder(order);
		
		//
		request.getRequestDispatcher("/pay.jsp").forward(request, response);
	}

由于提交的表单没有,订单号和用户,我们可以手动设置,订单号:uuid,用户,从session取。

注意我们是要做什么?是为了封装一条订单,但是封装一条订单,需要封装多个orderitem(因为这是个list集合)。同时,还设计到多个商品的改动,为啥呢?因为,生成订单的我们需要更新商品的数量,所以orderitem里面还有个num属性。

一发而动全身,一条订单,批量添加ordreitem,批量修改product。

那么接下来,就是把list封装好就行了,咱们新建一个list<orderitem>集合,这个封装的方法记下来。我们将购物车的map全部的key取出来(因为key存的是product),通过遍历key值,把所有product取出来。将product放进去,然后把当前order放进去(此时的order不是完整的,因为这个order没有list属性,但是也的确不能有,因为我们现在做的就是封装好list。),然后将购买商品数量放进去。(这些终于知道我们为啥存对象,而不存id了吧,对象直接放进去就好了,因为域中存的也是对象)这就完成了一个orderitem,把他放进list中。一直循环。

然后这个list就封装好了,直接丢进order,就OK。

然后就是addOrder了。这个过程,就需要改动三张表了。

OrderDao orderDao = new OrderDao();
	OrderItemDao orderItemDao = new OrderItemDao();
	ProductDao productDao = new ProductDao();
	
	public void addOrder(Order order){
		try {
			ManagerThreadLocal.startTransacation();
			orderDao.addOrder(order);
			orderItemDao.addOrderItem(order);
			productDao.updateProductNum(order);
			
			ManagerThreadLocal.commit();
		} catch (SQLException e) {
			e.printStackTrace();
			ManagerThreadLocal.rollback();
		}
	}

发现没有,这里用到了事务。调用事务。而且,三个表的修改都是通过一个参数order,这个有点牛逼。

那就详细看看这三个方法吧。

注意,这里获取数据库连接不能像开始那样,通过C3P0Utils获取数据库连接,这里要用事务的数据库链接。知道为啥要存User

了吧,因为,数据库中有UserID,所以他java对象就直接存了个User对象,所以以后看见要存别的表的ID,我们在java中我们

就直接存对象好了。

// 添加定单
	public void addOrder(Order order) throws SQLException {
		QueryRunner qr = new QueryRunner();
		qr.update(ManagerThreadLocal.getConnection(),
				"INSERT INTO orders VALUES(?,?,?,?,?,?,?,?)", order.getId(),
				order.getMoney(), order.getReceiverAddress(), order
						.getReceiverName(), order.getReceiverPhone(), order
						.getPaystate(), order.getOrdertime(), order.getUser()
						.getId());
	}

 存orderitem时,注意这里是批量插入,我们不知道有多少个要插入,取决于list的长度。所以这里插入的方法也不一样。

定义了二维数组,一维的长度正是我们要插入的数量。通过循环然后确定另外二维数组。这个二维里面放的就是真正的值。

也很简单。这里用的也是事务的数据库链接。

//添加定单项
	public void addOrderItem(Order order) throws SQLException{
		List<OrderItem> orderItems = order.getOrderItems();//得到所有定单项的集合
		QueryRunner qr = new QueryRunner();
		Object[][] params = new Object[orderItems.size()][];
		
		for (int i = 0; i < params.length; i++) {
			//数组中的第一个参数代表主单id, 第二个参数:商品id 第三个参数 :商品的购买数量
			params[i] = new Object[]{order.getId(),orderItems.get(i).getP().getId(),orderItems.get(i).getBuynum()};
		}
		qr.batch(ManagerThreadLocal.getConnection(),"INSERT INTO orderitem VALUES(?,?,?)", params );
	}

还是原来的方法,封装。

//修改商品数量
	public void updateProductNum(Order order) throws SQLException{
		List<OrderItem> orderItems = order.getOrderItems();
		QueryRunner qr = new QueryRunner();
		
		Object[][] params = new Object[orderItems.size()][];
		for (int i = 0; i < params.length; i++) {
			params[i] = new Object[]{orderItems.get(i).getBuynum(),orderItems.get(i).getP().getId()};
		}
		qr.batch(ManagerThreadLocal.getConnection(),"UPDATE products SET pnum=pnum-? WHERE id=?", params);
	}

OK,一条订单就插入完成了。将这个订单存在域中。接下来就支付的事了.......

完!!!!

猜你喜欢

转载自blog.csdn.net/BadRibbit/article/details/81811958