【基础练习】做个Servlet+JDBC+layui 的网上商城

13 篇文章 0 订阅
9 篇文章 0 订阅

这个网上购物商城系统的教程适用于  基础入门的同学,其中前端采用了一些layui的框架的知识,数据库用的也是原生的JDBC做连接。往后学习,大家会碰到很多的框架,例如JDBC的配置类不需要自己写,用mybatis就可以做连接和增删改查,例如servlet也会被spring boot的注解所代替,但是归根结底,它们都是要基于这类知识的。我会把我 拓展了的地方写在文章一开始的地方。

 

1、在前端往后端传值的地方用了layui table 数据传值,其中 总价实时改变和订单价格实时改变的地方 用了layui的模板。

2、servlet做了数据的组装,使得传回的值是可以被layui table所接收的。

3、商城功能:登陆功能+购物车功能+商品展示功能

 

JDBC.util, 用来做数据库连接。

private static final String URL = "jdbc:mysql://localhost:3306/zmall?useSSL=false&serverTimezone=UTC";
	private static final String USERNAME = "root";
	private static final String PWD = "123456";
	
	
	
	private JDBCUtil(){}
	
	public static Connection getConn() throws SQLException, ClassNotFoundException{       //建立数据库的连接
		
		return DriverManager.getConnection(URL,USERNAME,PWD);
	}
	
	public  static void  close(	Connection conn) {  			  //假如连接还存在,则关闭该连接
		if(conn != null) {
			try {
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

 

例如我们先写一个登陆功能,实现的顺序是,登录页输入信息---servlet接收信息---调用service的判断是否存在方法---service调用的dao的查询方法。

 

登录功能:

login.jsp

<body>
     <div class="loginbox">
         <div class="loginbox-header">
             <p>账号登录</p>
         </div>

         <form method="post" action="loginServlet" onsubmit ="return loginCheck()">
			  账号: <input type="text" name="username"  id="username" placeholder="账号"/><br/>
			  密码: <input type="password" name="password" id="password" placeholder="请输入密码"/><br/>
             <input type="submit"  value="登陆"  class="button" />
         </form>
     </div>
</body>

 

loginServlet.java

@WebServlet("/loginServlet")
public class loginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
       
    public loginServlet() {
        super();

    }
    
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	
    	request.setCharacterEncoding("utf-8");
    	String username = request.getParameter("username");
		String password = request.getParameter("password");
		
		
		User user = new User();
		user.setUsername(username);
		user.setPassword(password);
		
		
		UserImpl userService = new UserImpl();
		boolean result = userService.loginUser(user);		
		
		/***
		 * jsp的常见使用对象:out、request、response、session、application
		 * out:			PrintWriter out = response.getWriter();
		 * session:    request.getsession()
		 * appliacation : request.getapplication()		
		 */
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html; charset=UTF-8");
	
		PrintWriter out = response.getWriter();
		

		if(result) {
			System.out.println("登录成功");
			request.getRequestDispatcher("index.jsp").forward(request,response);   //地址栏不会发生变化
		}else {
			System.out.println("登录失败");
			request.getRequestDispatcher("login.jsp").forward(request,response);
		}
    }
	

 

UserImpl.java

public boolean loginUser(User user) {
		LoginDaoImpl loginDao =  new LoginDaoImpl();
		boolean exist = loginDao.isExist(user.getUsername(),user.getPassword());
		
		if (exist) {
			 return true;	
		} else {
			System.out.println("该用户不存在");
			return false;
		}
	}

 

LoginDaoImpl.java

public  boolean  isExist(String username,String password) {      

		
		//     在DAO层放数据逻辑。servlet放业务逻辑。  
		
		
		User loginStatus = queryUserByName(username);
		String pwd = loginStatus.getPassword();
		if(loginStatus.getUsername() == null) {
			return false;
		}
		else if (!password.equals(pwd)) {
			return false;
		}
		else{
			return true;
		} 
		
//	       	
//		//复习一下三目运算
//		return queryUserByName(username)==null?false:true;
		
	
	}
	
	public  User queryUserByName(String username) {
		
		Connection connection = null;              
		PreparedStatement preparedStatement = null;
		ResultSet rs  = null;
		User user = new User();
		
		try {
			
			
			connection = JDBCUtil.getConn();
			String sql = "SELECT * FROM user where user_name=? ";				  //数据库语句
			preparedStatement = connection.prepareStatement(sql);

			
			preparedStatement.setString(1, username);
			
			rs = preparedStatement.executeQuery();
			
			if(rs.next()) {
				int userNo = rs.getInt("user_id");
				String userName = rs.getString("user_name");
				int age = rs.getInt("user_age");
				String password = rs.getString( "user_pwd");
				String gender = rs.getString("user_gender");
								
				user.setAge(age);                                     //封装
				user.setUserId(userNo);
				user.setUsername(userName);
				user.setPassword(password);
				user.setGender(gender);
				
				System.out.println("(登录用户):"+userName);
						
			}
			
			return user;
			
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}catch (Exception e) {
			e.printStackTrace();
			return null;
		}finally {
				try {
					if(rs!=null)rs.close();                                                       //这边需要加上三个抛出异常的东西
					if(preparedStatement!=null) preparedStatement.close();
					if(connection!=null)connection.close();
					
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	
	}
	

 

以上的用户登录功能模块就完成了,用户查询的时候, 所查询的是某一条的数据,此时的返回集rs是一个结果集,rs.next()可以判断是否为返回了为空的结果,如果是null 就返回false ,如果是有结果的,就返回结果。当我们在后面做插入操作的时候,这个时候记得rs的定义要为int,因为此时rs表示的是insert的插入影响了多少行。

 

商品展示功能:

index.jsp

<a href = "login.jsp">登录</a>
<a href = "cart.jsp">购物车</a>

<table class="layui-hide" id="goodsTable" lay-filter="goodsFilter"></table>

<script type="text/html" id="barDemo">
    <a class="layui-btn layui-btn-xs" lay-event="add">添加</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="batchdel">批量删除</a>
    <a class="layui-btn layui-btn-xs" onclick="tips()" id="zz">添加tips</a>
    <a class="layui-btn layui-btn-xs"  lay-event="refresh" id="refresh">刷新</a>
    <a class="layui-btn layui-btn-xs" lay-event="fuck">编辑</a>
</script>

<script type="text/html" id="cellbarDemo">
    <a class="layui-btn layui-btn-xs" lay-event="addGoods">添加</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="checkGoods">查看详情</a>
</script>

 

table的展示是通过js渲染的。传值也是在js完成的。

index.js

layui.use(['layer', 'form','table','jquery'], function(){                                               //用之前一定要先定义
    var layer = layui.layer
        ,form = layui.form,table = layui.table,$=layui.jquery;
    
    

  
    
    var tableIns = table.render({                                                                       //获取table的实例,后面用来做表格的刷新
        elem: '#goodsTable'
        ,url:'/Zmall/goodsListServlet'                    //数据接口                                                                            //表格左上角的功能可以自定义                                                     //表格右上方的三个按钮(导出、打印、筛选)不能编辑,只能选择是否显示
        ,limit:20                                                                                       //每一行的最多可以显示的数量
        ,page:true                                                                                      //是否开启分页
        ,cellMinWidth: 80 								//全局定义常规单元格的最小宽度,layui 2.2.1 新增
        ,cols: [
            [
                {type:'checkbox',width:120}
                ,{field:'goods_id', width:200, title: 'ID', sort: true}
                ,{field:'goods_name', width:375, title: '商品名称'}
                ,{field:'type', width:335, title: '商品类型'}                                   //注意这个edit:true,表示可以被编辑                
                ,{field:'goods_price', width:180, title: '商品价格'}
                ,{width:150,title:'操作',toolbar:'#cellbarDemo'}                                            //用toolbar引入操作按钮
            ]
        ]
    });
    
    
    //监听单元行的事件
    table.on('tool(goodsFilter)', function(obj){ //注:tool是工具条事件名,test是table原始容器的属性 lay-filter="对应的值"
        var data = obj.data; 			//获得当前行数据
        var layEvent = obj.event;	    //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
        var tr = obj.tr; 			    //获得当前行 tr的DOM对象
        
        
        
//      alert(data.goods_id);
        if(layEvent === 'addGoods'){ 
            layer.open({
                type:2,
                title:'添加数量',
                area:["600px","600px"],
                content:"/Zmall/addGoods.jsp",
                success:function(dom){
                    $iframeDom=$(dom[0]).find("iframe").eq(0).contents();
                    $iframeDom.find("#goodsId").val(data.goods_id);
                    $iframeDom.find("#goodsName").val(data.goods_name);
                    $iframeDom.find("#goodsPrice").val(data.goods_price);
                    $iframeDom.find("#goodsType").val(data.type);
                   
                }
            });
  }
    });
    

});

 

goodsListServlet.用于查询 到 数据库的商品信息并转为layui-table的数据格式,什么是layui-table支持的数据格式呢。

{code:   ,msg: , count:    ,data:[] ,}.因此在后台传来了map的数据以后,做一个数据组装,并且使用Gson.toJson(map)将map转为json数据就行。

request.setCharacterEncoding("utf-8");
    	response.setContentType("text/html; charset=UTF-8");

    	GoodsImpl goodsService = new GoodsImpl();
    	List<Map<String, Object>> goodsList = goodsService.getGoodsList();								
    	
    	Map<String, Object> map = new HashMap<String, Object>();
    	/**
    	 * 进行数据组装,将所有的list的数据变layui-table的所需要的 格式
    	 */
    
    	
    	if(goodsList != null) {
    		map.put("code", 0);
            map.put("msg", "");
            map.put("count",goodsList.size());
            map.put("data", goodsList);  		
    	} 
    	
    	  Gson gson = new Gson();                                    //将map数据类型转化为Json数据类型
//    	  System.out.println(gson.toJson(map));
    	  
    	  PrintWriter out = response.getWriter(); 
    	  out.write(gson.toJson(map));
    	  out.flush();
          out.close();  

这边要注意out这个内置对象可以用来向客户端发送数据,发送完之后要记得清空缓冲并关闭流。

在这里,service层没有做业务逻辑的判断了,就单单是调用dao层进行查询。然后要注意到dao层的写法会和上面有些不一样,对,上面也提到了,主要是结果集的类型是 int。

try {
			connection = JDBCUtil.getConn();
			String sql = "SELECT * FROM goods ";	
			preparedStatement = connection.prepareStatement(sql);
			rs = preparedStatement.executeQuery();
			
			ResultSetMetaData resultSetMetaData = (ResultSetMetaData) preparedStatement.getMetaData();
			int column =  resultSetMetaData.getColumnCount();
			
			
			while(rs.next()) {
				data = new HashMap<String, Object>();
				for (int i =1;i<=column;i++) {
					data.put(resultSetMetaData.getColumnLabel(i), rs.getObject(resultSetMetaData.getColumnLabel(i)));
					
				}
				goodsList.add(data);

			}
			
			return goodsList;
			
		} catch (SQLException e) {
			e.printStackTrace();
		}catch (Exception e) {
			e.printStackTrace();
		}finally {
            try {
				if(rs!=null)rs.close();                                                       //这边需要加上三个抛出异常的东西
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				try {
					if(preparedStatement !=null) preparedStatement.close();
				} catch (Exception e) {
					e.printStackTrace();
				}finally {
					try {
						if(connection !=null) connection.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
        }

这边后面为什么要写三个try catch 呢,因为Result 和 PrepareStatement 是关闭的过程也可能产生异常,那为什么要关闭他们呢,因为虽然说我这个项目没有用到数据库连接池,GC会保证在项目结束的时候清空掉这些资源,但是有的项目(而且是大部分项目)是会用到连接池的,这时候断开连接不是 清空资源,而是将Result 和 PrepareStatement归还给 数据库,那东西积少成多,就会占用数据库的资源,最后导致数据库无法加载。

写完了这两个以后,我们前端的页面是这样的。

 

右边定义的添加按钮,添加了一个点击事件,使用iframe传值的方法将这个父页面的值传到子页面上,然后再通过表格输入数量,点击提交信息,将订单结果存储到数据库当中。

这是Create.jsp.

表单的编写方法大家应该都会,我们重点来说,首页的商品信息,是怎么传送到这个页面里头去的,

使用layevet监听添加的按钮时间,因为这个添加是写在表格一行的末尾的,所有我们在index.js那部分是这样写的:

index.js(增加订单部分)

  table.on('tool(goodsFilter)', function(obj){ //注:tool是工具条事件名,test是table原始容器的属性 lay-filter="对应的值"
        var data = obj.data; 			//获得当前行数据
        var layEvent = obj.event;	    //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
        var tr = obj.tr; 			    //获得当前行 tr的DOM对象
        
        
        
//      alert(data.goods_id);
        if(layEvent === 'addGoods'){ 
            layer.open({
                type:2,
                title:'添加数量',
                area:["600px","600px"],
                content:"/Zmall/addGoods.jsp",
                success:function(dom){
                    $iframeDom=$(dom[0]).find("iframe").eq(0).contents();
                    $iframeDom.find("#goodsId").val(data.goods_id);
                    $iframeDom.find("#goodsName").val(data.goods_name);
                    $iframeDom.find("#goodsPrice").val(data.goods_price);
                    $iframeDom.find("#goodsType").val(data.type);
                   
                }
            });
  }
    });

addGoods是按钮监听事件,点击这个按钮之后,layer.open弹出jsp窗口,并同时定义iframe,                   

$iframeDom=$(dom[0]).find("iframe").eq(0).contents();

这段代码的意思是 第0层元素(有可能出现页面嵌套)的页面作为iframe,然后如果有多个iframe(iframe和div的名字一样,可能出现重名),我们取第一个索引为eq(0)的iframe,也就是第一个iframe,再获取它的内容,此时contents就是这个页面的内容,也就是我们的addGoods.jsp页面。

data获取的是当前这一行的数据值,他是一个对象(应该是),再通过data.id这样的写法给addGoods.jsp页面里面的元素赋值。

然后增加订单又是一个船新的类似的功能,唯一不一样的是,我们在dao层的insert的时候,用的是excuteUpdate,代码如下

AddGoodsDaoImpl.java

String sql = "insert into cartItem value (?,?,?,?,?) ";	
			
			int id = cartItem.getId();
			String goodsId = cartItem.getGoodsId();
			String goodsName = cartItem.getGoodsName();
			String goodsPrice = cartItem.getGoodsPrice();
			String amount = cartItem.getAmount();
			
			
			preparedStatement = connection.prepareStatement(sql);
			
			preparedStatement.setInt(1,0);
			preparedStatement.setString(2, goodsId);
			preparedStatement.setString(3, goodsName);
			preparedStatement.setString(4, goodsPrice);
			preparedStatement.setString(5, amount);
			
			
			int count = preparedStatement.executeUpdate();
			
	
			if (count>0) {
				System.out.println("操作成功");
				return true;
			}else {
				System.out.println("操作失败");
				return false;
			}

 

这个功能实现以后,就代表订单数据表有数据了。现在我们再写另一个页面(原理和展示商品页面一样),将购物车的页面展示出来就行。

 

细心的人,已经发现到了,底下做了一个实时统计总价的功能,这个我们是怎么做到的呢。

 var tableIns = table.render({                                                                       //获取table的实例,后面用来做表格的刷新
        elem: '#cartTable'
        ,url:'/Zmall/cartItemListServlet'                    //数据接口                                                                            //表格左上角的功能可以自定义                                                     //表格右上方的三个按钮(导出、打印、筛选)不能编辑,只能选择是否显示
        ,limit:20                                                                                       //每一行的最多可以显示的数量
        ,page:true                                                                                      //是否开启分页
        ,cellMinWidth: 80 								//全局定义常规单元格的最小宽度,layui 2.2.1 新增
        ,cols: [
            [
                 {type:'checkbox',width:120}
                ,{field:'id', width:200, title: '订单编号', sort: true}
                ,{field:'goods_name', width:375, title: '商品名称', id:"goods_name"}
                ,{field:'goods_price', width:200, title: '单价'}                                   //注意这个edit:true,表示可以被编辑                
                ,{field:'amount', width:180, title: '数量', edit:"true"}
                ,{field:'orderPrice',width:135 , title:'订单价格' ,templet: function(d){
                    return  parseInt(d.goods_price)*parseInt(d.amount)}}
                ,{width:150 , title:'操作',toolbar:'#cellbarDemo'}                                            //用toolbar引入操作按钮
            ]
        ]
    	,done: function(res, curr, count){
    	    //如果是异步请求数据方式,res即为你接口返回的信息。
    	    //如果是直接赋值的方式,res即为:{data: [], count: 99} data为当前页数据、count为数据总长度
    		var total_price = 0;
    		for( i=0;i<count;i++){    			
    			total_price = parseInt(res.data[i].goods_price)*parseInt(res.data[i].amount)+total_price;
    		}
    		
    	    layer.msg(total_price);
    	    $("#total_price").val(total_price);
    	    
    	    //得到当前页码
//    	    console.log(curr); 
    	    
    	    //得到数据总量
//    	    layer.msg(count);
    	  }
    });

 

在我们渲染layui-table的时候,有一个done的col 属性,它可以帮助我们拿到传过来的data(就是那个组装好的json)的东西,res.data[i].amount 就是里头 某一个单元格的值,我们用一个for 循环,就可以遍历所有值,取到任何一个单元格里头的东西,那总价当然是通过单价乘以数量再加起来算到的值咯。这里有一个先前提到的难点,就是文章一开头提到的,怎么实时改变layui某一列单元格的值,以我们这个系统为例,我这里的订单价格是实时监听得到的,不是从数据库传来的。这里就用到了layui-table的自定义模板。

,cols: [
            [
                 {type:'checkbox',width:120}
                ,{field:'id', width:200, title: '订单编号', sort: true}
                ,{field:'goods_name', width:375, title: '商品名称', id:"goods_name"}
                ,{field:'goods_price', width:200, title: '单价'}                                   //注意这个edit:true,表示可以被编辑                
                ,{field:'amount', width:180, title: '数量', edit:"true"}
                ,{field:'orderPrice',width:135 , title:'订单价格' ,templet: function(d){
                    return  parseInt(d.goods_price)*parseInt(d.amount)}}
                ,{width:150 , title:'操作',toolbar:'#cellbarDemo'}                                            //用toolbar引入操作按钮
            ]
        ]

看以看到最后一个field里头的值是通过函数计算得到的。这里一定要注意col里头的两个[ [ 要分开写,你连在一起写会被认为是“[[”符号而无法识别。

以上都做完了,那如果想购买的数量变了,应该怎么做呢(包括想删除订单是一样的道理)

我把订单表格的数量那一列设为可编辑的,具体的做法就是在field后面添加属性为edit;true,然后在js中监听这一行的数据是否有做改变,如果做了改变,就出发updateServlet,往数据库表格更新一条数据,具体做法如下:

cart.js(更新购物车部分)

    table.on('edit(cartFilter)', function(obj){
        var data = obj.data; 			//获得当前行数据
        var layEvent = obj.event;	    //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
        var tr = obj.tr; 			    //获得当前行 tr的DOM对象
        
        var inputAmount = obj.value;
        
        $.ajax({
            type: "GET",
            url: "/Zmall/updateCartItemServlet",
            data: {
                "inputAmount": obj.value,
                "id":data.id
            },
            dataType: "text",
            success: function(data){
                        if (data.val() == "200"){
                        	layer.msg("修改成功");
                        }else{
                        	layer.msg("修改失败");
                        }
                        	
                     }
        });
        
        setTimeout('window.location.reload()',1000);
    	
    });

UpdateCartItemDaoImpl.java(我这个起名真的是有毒)

String sql = "UPDATE cartitem SET amount=? WHERE id=? ";	
					
			preparedStatement = connection.prepareStatement(sql);
			
			preparedStatement.setString(1,inputAmount);
			preparedStatement.setInt(2, id);

			
			
			int count = preparedStatement.executeUpdate();
			
	
			if (count>0) {
				System.out.println("操作成功");
				return true;
			}else {
				System.out.println("操作失败");
				return false;
			}

然后这里的也是excuteUpdate,对,除了查询的时候用的是excuteQuery,其他的插入,插叙,删除都是excuteUpdate。

修改完成之后,传回一个状态值,这个状态200表示修改成功,然后刷新页面,此时总价和订单总价都会被改变。

 

以上就是这一个网上商城购物系统。总结一下:

基本的功能都有实现,不足的地方是:订单删除功能还没做,页面应该带有登录状态,这样在提交最终订单的时候,才会带有用户id和用户名,再往前推一步,一个人只能显示自己的订单才可以。另外是同事给我提的建议,

  1)lib目录存放jar包,但是你的mysql驱动包并未在这个下面。
   2)WebContent目录下的WEB-INF没有classes文件夹,应当创建文件夹,并设置编译后的字节码文件全在classes文件夹下。
   3)WebContent下的scripts和WEB-INF下的static/的相关目录里面的类型重复
   4)页面一般除了欢迎界面在WebContent(一般都会取名webapp)下,其他页面都会直接放在WEB-INF中的一个目录下,比如views目录

以上。
 

  • 4
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值