1. 综合练习
1) 简单功能
1. 列表查询
2. 登录
3. 添加
4. 删除
5. 修改
2) 复杂功能
1. 删除选中
2. 分页查询
* 好处:
1. 减轻服务器内存的开销
2. 提升用户体验
3. 复杂条件查询
在之前用户信息查询案例的基础上,添加新的功能。之前只有列表查询的功能,现在将其他功能补充上。
注意,如果我们导入项目的时候,相应的web文件夹只是普通的文件夹,而不是带一个蓝色点的web文件夹,而且在添加Artifacts的时候没有对应module的Artifacts,我们在File-Project Structure-Facts 下为当前的项目添加web,这样web文件夹就变成真正的web文件夹,而且就可以给项目添加Artifacts,项目的Deployment可以正常部属Artifacts,项目可以正常运行。
导入项目的时候,如果发现Servlet相关类都是红色的,说明我们需要给当前项目的Dependencies添加Tomcat相关的包,参考文章:添加链接描述
1、页面调整
我们将用户信息展示页面list.jsp页面调整为我们需要的样式。其实将来开发中页面的调整一般不需要我们来完成,但是我们可以尝试完成。(具体参考视频2解析)
原来的样式为
调整为如下样式
修改后,相应的样式就与我们要求的类似,如下图,代码也在下方。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>用户信息管理系统title>
<link href="css/bootstrap.min.css" rel="stylesheet">
<script src="js/jquery-2.1.0.min.js">script>
<script src="js/bootstrap.min.js">script>
<style type="text/css">
td, th {
text-align: center;
}
style>
head>
<body>
<div class="container">
<h3 style="text-align: center">用户信息列表h3>
<%--
2、接下来我们要设置有三个文本框的表单。这里直接使用Bootstrap封装的样式即可。
直接在BootStrap中文网-文档-全局CSS样式查询相应的样式,其中“内联表单”样式就是我们需要的。
我们将这个样式复制下来修改为我们需要的样式即可。
首先,同样为了方便控制样式,用一个div将这个“内联表单括起来”。
首先让表单与按钮在同一行,我们设置表单左浮动,而2个按钮是右浮动,这样看起来就是在同一行。
--%>
<div style="float: left">
<form class="form-inline">
<div class="form-group">
<label for="exampleInputName2">姓名label>
<input type="text" class="form-control" id="exampleInputName2">
div>
<%--
这里将姓名的代码复制一份,修改id的值,并将label for="exampleInputName3"的值修改为与id的值相同即可。
这个按钮的功能是点击“籍贯”,就可以获取相应文本区域的光标。
--%>
<div class="form-group">
<label for="exampleInputName3">籍贯label>
<input type="text" class="form-control" id="exampleInputName3">
div>
<div class="form-group">
<label for="exampleInputEmail2">邮箱label>
<input type="email" class="form-control" id="exampleInputEmail2">
div>
<%--将按钮的文本修改为:“查询”--%>
<button type="submit" class="btn btn-default">查询button>
form>
div>
<%--
1、首先,添加右侧2个按钮:“添加联系人”、“删除选中”
使用一个div区域将按钮包围起来,方便对样式进行控制。
我们将div区域设置CSS样式,直接使用标签的style属性设置即可,注意style属性的各个样式之间用;隔开。
右侧浮动,并且设置一下外边距,设置外边距,调整2个按钮与表格之间的距离。
--%>
<div style="float: right; margin: 8px;">
<a class="btn btn-primary" href="add.html">添加联系人a>td>
<a class="btn btn-primary" href="add.html">删除选中a>td>
div>
<table border="1" class="table table-bordered table-hover">
<tr class="success">
<%--
4、最后,再给表格前面加一行复选框的行
--%>
<th><input type="checkbox">th>
<th>编号th>
<th>姓名th>
<th>性别th>
<th>年龄th>
<th>籍贯th>
<th>QQth>
<th>邮箱th>
<th>操作th>
tr>
<%--
我们将静态页面的虚假信息静态删除,使用JSTL标签遍历request域转发过来的List<User>集合。
request域存储的user键值对如下:
request.setAttribute("user" , user);
--%>
<c:forEach items="${requestScope.user}" var="user" varStatus="s">
<tr>
<%--
4、我们在循环遍历的tr中也给每一行的最前面加一个复选框。
--%>
<td><input type="checkbox">td>
<td>${s.count}td> <%--用循环次数代表编号--%>
<td>${user.name}td> <%--用逻辑视图的方法,在EL表达式中使用User的getter取出属性的值--%>
<td>${user.gender}td>
<td>${user.age}td>
<td>${user.address}td>
<td>${user.qq}td>
<td>${user.email}td>
<td><a class="btn btn-default btn-sm" href="update.html">修改a> <a class="btn btn-default btn-sm" href="">删除a>td>
tr>
c:forEach>
table>
<%--
3、接下来是分页工具条,注意将这部分放到表格后面。这部分我们同样可以借助BootStrap的样式来完成。
直接在BootStrap中文网-文档-组件-分页中查找,同样将相应的代码黏贴过来修改即可.
同样为了方便控制样式,用一个div将这个“分页”括起来
--%>
<div>
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«span>
a>
li>
<li><a href="#">1a>li>
<li><a href="#">2a>li>
<li><a href="#">3a>li>
<li><a href="#">4a>li>
<li><a href="#">5a>li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»span>
a>
li>
<%--
我们这里加一个sapn来填写相应的提示文字。这部分放在无序列表ul的内部,这样页面才不会乱。
目前这里分页的数据是假的,将来换做真的。
这里设置一下字体为25px,文字左外边距为5px
--%>
<span style="font-size:25px; margin-left: 5px;">共16条记录,共4页span>
ul>
nav>
div>
div>
body>
html>
2、登录功能
我们的资料提供了login.html页面,我们在这个页面的基础上修改即可。
首先,调整页面,新建一个login.jsp,并将之前的login.html复制到login.jsp,保留JSP第一行的定义。然后添加验证码,使用之前编写的CheckCodeServlet,稍微修改一下即可,CheckCodeServlet代码如下:
package lkj.web.servlet;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 验证码
*/
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//服务器通知浏览器不要缓存
response.setHeader("pragma","no-cache");
response.setHeader("cache-control","no-cache");
response.setHeader("expires","0");
//在内存中创建一个长80,宽30的图片,默认黑色背景
//参数一:长
//参数二:宽
//参数三:颜色
int width = 80;
int height = 30;
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//获取画笔
Graphics g = image.getGraphics();
//设置画笔颜色为灰色
g.setColor(Color.GRAY);
//填充图片
g.fillRect(0,0, width,height);
//产生4个随机验证码,12Ey
String checkCode = getCheckCode();
//将验证码放入HttpSession中
request.getSession().setAttribute("CHECKCODE_SERVER",checkCode);
//设置画笔颜色为黄色
g.setColor(Color.YELLOW);
//设置字体的小大
g.setFont(new Font("黑体",Font.BOLD,24));
//向图片上写入验证码
g.drawString(checkCode,15,25);
//将内存中的图片输出到浏览器
//参数一:图片对象
//参数二:图片的格式,如PNG,JPG,GIF
//参数三:图片输出到哪里去
ImageIO.write(image,"PNG",response.getOutputStream());
}
/**
* 产生4位随机字符串
*/
private String getCheckCode() {
String base = "0123456789ABCDEFGabcdefg";
int size = base.length();
Random r = new Random();
StringBuffer sb = new StringBuffer();
for(int i=1;i<=4;i++){
//产生0到size-1的随机值
int index = r.nextInt(size);
//在base字符串中获取下标为index的字符
char c = base.charAt(index);
//将c放入到StringBuffer中去
sb.append(c);
}
return sb.toString();
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
接下来给login.jsp添加点击验证码可以切换图片的功能。然后再给数据库的user表添加username与password2个属性,同时添加值。这样数据库就修改完毕
接下来我们就可以编写完成登录功能的Servlet类LoginServlet。我们在web.servlet目录下面创建一个LoginServlet.java类,这个LoginServlet.java会调用到service业务逻辑层的login()方法去查询数据库对比,因此在service包中的UserService接口添加一个login()的抽象方法,在UserServiceImpl类实现UserService的login()方法,实现的方法中调用dao层的findUserByUsernameAndPassword()方法,去真正查询数据库。那么在dao包的UserDao接口中添加findUserByUsernameAndPassword()方法,同时在实现类UserDaoImpl中实现这个类。同时需要修改User类,添加相应的username与password属性,User类的代码如下:
package lkj.domain;
public class User
{
private int id;
private String name;
private String gender;
private int age;
private String address;
private String qq;
private String email;
private String username;
private String password;
//空构造器
public User()
{ }
//带参构造器
public User(int id, String name, String gender, int age, String address, String qq, String email, String username, String password)
{
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
this.address = address;
this.qq = qq;
this.email = email;
this.username = username;
this.password = password;
}
//getter与setter方法
public int getId()
{
return id;
}
public String getName()
{
return name;
}
public String getGender()
{
return gender;
}
public int getAge()
{
return age;
}
public String getAddress()
{
return address;
}
public String getQq()
{
return qq;
}
public String getEmail()
{
return email;
}
public String getUsername()
{
return username;
}
public String getPassword()
{
return password;
}
public void setId(int id)
{
this.id = id;
}
public void setName(String name)
{
this.name = name;
}
public void setGender(String gender)
{
this.gender = gender;
}
public void setAge(int age)
{
this.age = age;
}
public void setAddress(String address)
{
this.address = address;
}
public void setQq(String qq)
{
this.qq = qq;
}
public void setEmail(String email)
{
this.email = email;
}
public void setUsername(String username)
{
this.username = username;
}
public void setPassword(String password)
{
this.password = password;
}
//toString方法
@Override
public String toString()
{
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
", address='" + address + '\'' +
", qq='" + qq + '\'' +
", email='" + email + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
在LoginServlet查询后,会根据查询结果跳转到不同的页面。如果在数据库中查询到相应的用户,将用户信息重定向到index.jsp页面,这样就可以在index.jsp页面去跳转查询所有用户数据。如果验证码出错或者用户名、密码出错,就请求跳转到login.jsp页面,输出提示信息并刷新页面重新登录。
(其他代码等后面的例子一起操作完毕后再贴)
1、浏览器访问登录页面:login.jsp。我们需要添加验证码,将验证码的src指向提供验证码的Servlet类:checkCodeServlet;随后编写refreshCode类实现验证码点击刷新功能;设置表单提交处理的Servlet类——loginServlet;最后,如果验证码错误或者用户名密码错误就会返回login.jsp,我们设置接收返回错误信息的出错显示的信息框区域;
2、提供验证码的Servlet:checkCodeServlet类,与之前编写的相同,稍微修改即可;
3、表单提交到的Servlet类:判断验证码,错误返回“验证码错误信息”并请求转发到login.jsp展示;验证码正确,先为User类添加username与password及其相关方法,并将用户提交的数据封装到User对象,调用service的UserServiceImpl类的login(User),登录返回一个User对象,判断User对象是否为空,为空则用户名或者密码错误,请求转发到login.jsp页面,并通过request域添加错误信息展示在login.jsp;不为空则输入的信息正确,将正确信息存储到Session对象,重定向到index.jsp页面,通过这个页面可以去查看所有的用户信息;
4、UserServiceImpl类,实现UserService接口的login方法,login方法调用dao中的findUserByUsernameAndPassword()方法,这个方法可以查询数据库内容并对比我们传入的User对象的信息,返回一个新的User对象;
5、UserDaoImpl类,实现UserDao接口的findUserByUsernameAndPassword()方法,这个方法查询数据库,使用JDBCTemplate的queryForObject,查询对比我们传入的User对象的用户名与密码信息与数据库中的信息是否有对应的,返回一个User对象,有对应则返回一个包含对应username与password所有信息的User对象,没有对应则返回null;
6、index.jsp:接收登录成功的重定向信息并展示,同时使用一个超链接,跳转到处理展示所有用户信息的UserListServlet类。
一共涉及login.jsp、CheckCodeServlet.java、LoginServlet.java、UserServiceImpl.java(UserService)、UserDaoImpl.java(UserDao)、index.jsp页面
3、添加功能
首先我们在用户信息展示页面:list.jsp,我们点击添加联系人按钮之后,会跳转到添加联系人的页面add.jsp,然后再这个add.jsp填写相应的数据后,点击提交,相应的数据会提交到对应的AddUserServlet类执行添加操作。
首先,添加功能的逻辑如下(见视频5分析)
其次,要说明的是,我们当前的数据库设计并不是很合理,这个数据库既完成查询登录的功能,登录后这张表又要完成信息展示的功能。将来我们应该有2张表,一张管理员的表,用于管理员查询登录;另一张是客户信息的表,用于管理员登录后进行信息的展示。而我们添加的是客户的信息,客户是无法登录的。现在为了简化,我们添加联系人的时候没有添加用户名和密码,只要能展示功能即可。
在list.jsp页面,点击“添加联系人”按钮会跳转到add.jsp进行信息的添加。
我们根据add.html创建一个add.jsp页面,跳转到add.jsp页面后,进行信息的添加。将信息添加表单的action修改为跳转到用户信息添加的Servlet:addUserServlet,检查表单是否正确。返回的功能与重置功能暂时不做。
接下来写AddUserServlet.java类,将add.jsp提交的数据封装为User对象,并调用service的addUser方法对数据库进行添加。最后重定向回UserListServlet类,UserListServlet类会自动调用findAll()方法查询更新的数据库并跳转到list.jsp进行展示。
在service包的UserService接口下添加addUser()方法,同时在UserServiceImpl方法实现addUser()方法,调用dao中的add方法,对数据库的信息进行添加。
在dao包的UserDao接口下添加add()方法,同时在UserDaoImpl方法实现add()方法,真正对数据库的信息进行添加。使用JDBCTemplate的update方法进行数据的添加。
完成代码的编写,我们进行测试。首先我们访问index.jsp,点击“查询所有用户信息”,跳转回list.jsp,点击“添加联系人”按钮,跳转到add.jsp。添加数据,点击提交,然后查看结果即可。
作业1:通过javascript对表单的数据进行校验,不合法提示无法提交。
作业2:给add.jsp页面添加一个返回功能
这一部分参考:1、自己的文章《黑马就业班(02.JavaWeb+项目实战\02.JavaScript)——part2:BOM&DOM》——案例2:表单验证提交;2、自己文章《黑马毕向东Java课程笔记(35天day35-1——35-10)DOM(文档对象模型)+BOM(浏览器对象模型):part3》——6、DOM示例——表单校验。主要参考第二个。
步骤如下:
1、首先,给表单的每一个标签添加失去焦点就触发验证的动作,失去焦点的动作是onblur。name、age、qq、email需要检测;
2、其次,给表单的每一个标签添加一个区域,用于显示提示信息;
3、在JS区域,添加name、age、qq、email的检测方法;
4、设置表单的onsubmit动作,只有返回true的时候表单才会提交,那么设置一个checkForm()方法, 检测所有的检测项目是否都返回true,所有项目都检测正确才返回true,表单才能提交。
同样访问http://localhost/day09case/list.jsp,点击“添加联系人”,对各项进行检测。如下,完成表单检测。
在add.jsp页面,给“返回”按钮添加点击功能,指向一个back()函数,这里不需要传递参数。在JS代码区域中创建一个back()方法。back方法中将地址栏的路径location.href指向UserListServlet类进行用户数据的获取展示。
测试:访问http://localhost/day09case/list.jsp,点击“添加联系人”,点击返回可以返回list.jsp.
4、删除功能
注意,这里只是“删除”功能,并没有删除选中功能,删除选中功能在后面。我们点击“删除”按钮之后,会跳转到一个“DelUserServlet”类,这个类获取我们点击行的参数,并调用service的deleteUser方法进行删除,而deleteUser()方法调用DAO中的delete()方法真正的对数据库进行删除。最后,“DelUserServlet”类同样跳转回查询所有用户信息的UserListServlet类,这个类同样会自己调用findAll()方法对删除后的数据库进行展示。
在list.jsp,给删除按钮添加href,跳转到DelUserServlet并携带对应当前遍历到的User对应的id:
href="${pageContext.request.contextPath}/delUserServlet?id=${user.id}"
创建一个处理删除功能的Servlet:DelUserServlet。根据id,调用service的deleteUser方法对数据库进行删除。最后重定向回UserListServlet类,UserListServlet类会自动调用findAll()方法查询更新的数据库并跳转到list.jsp进行展示。
在service包的UserService接口下添加deleteUser()方法,同时在UserServiceImpl方法实现deleteUser()方法,调用dao中的delete()方法,对数据库的信息进行删除。
在dao包的UserDao接口下添加delete()方法,同时在UserDaoImpl方法实现delete()方法,真正对数据库的信息进行删除。使用JDBCTemplate的update方法进行数据的删除。
完成代码的编写,我们进行测试。首先我们访问index.jsp,点击“查询所有用户信息”,跳转回list.jsp,点击“删除”就可以删除这一条用户的信息。
问题:可能用户一不小心点击“删除”,就会删除重要的数据,我们应该在用户点击删除的时候添加提示。
我们将“删除”按钮的href修改为:
href="javascript:deleteUser(${user.id})
通过deleteUser方法,在里面进行confirm判断,确定再修改location对象的href为
"${pageContext.request.contextPath}/delUserServlet?id="+id;
这样便可以给出删除提示。
我们完成后面的代码后,list.jsp页面遍历的是${requestScope.pb.list},而不是之前传递过来的user,因此我们删除后或者在添加的时候返回时,都会重定向回UserListServlet展示数据,因此会出现下面的情况。我们只需要将删除后重定向回UserListServlet全部改为重定向回FindUserByPageServlet分页查询,那这样便不会出错。其他返回UserListServlet显示的代码全部改为返回FindUserByPageServlet分页显示,便可以兼容后面的代码。
在list.jsp,给按钮添加一个href,点击“修改”超链接,会跳转到一个Servlet:findUserServlet。同时,我们针对的是某一行的修改,这个时候应该将当前行的id通过href的参数带过去。
创建一个FindUserServlet类,这个类接收list.jsp传递过来的id,调用service的findUserById方法对数据库进行查询。最后重定向回update.jsp,update.jsp会将相应id查询出来的数据展示在修改的表上,这些数据表示修改之前的原值。
在service包的UserService接口下添加findUserById()方法,同时在UserServiceImpl方法实现findUserById()方法,调用dao中的findById()方法,对数据库的信息进行查询。
根据update,html,创建一个update.jsp。这个update.jsp接收FindUserServlet通过request传递过来的User对象,我们将User对象里面的修改之前的数据展示到update,jsp上面,并且修改后的数据会提交到UpdateUserServlet 进行处理。
创建一个修改功能的Servlet:UpdateUserServlet。根据update.jsp提交过来的数据,封装为一个User对象,调用service的updateUser(User)方法对数据库进行修改。最后重定向回UserListServlet类,UserListServlet类会自动调用findAll()方法查询更新的数据库并跳转到list.jsp进行展示。
在service包的UserService接口下添加updateUser()方法,同时在UserServiceImpl方法实现updateUser()方法,调用dao中的update()方法,对数据库的信息进行修改。
在dao包的UserDao接口下添加update()方法,同时在UserDaoImpl方法实现update()方法,真正对数据库的信息进行修改。使用JDBCTemplate的update方法进行数据的修改。
测试:访问index.jsp,点击“查询所有用户信息”,点击“修改”,跳转到update.jsp页面,我们修改然后提交,跳转到UserListServlet显示修改后的数据。
6、删除选中功能
为了演示,我们在数据库中多添加一些记录。
删除选中功能的逻辑如下:
首先,重点是如何获取我们选中行的id值。有几个思路:
1、我们勾选了复选框,我们可以遍历所有的复选框,判断哪一个复选框勾选了,然后就可以将选中行对应的id获取出来放入一个字符串,然后我们再对字符串进行操作,取出id,就可以对id对应的行进行操作。但是这样需要编写很多JS代码;
2、复选框本身就支持提交,我们可以用一个表单form将复选框围起来,然后在复选框中设置name属性,将每一次遍历到的用户的id放入复选框的value中,这样复选框提交到DelSelectedServlet类的时候,可以通过复选框的name属性获取到复选框的value,也就是复选框对应的用户的id值。
在复选框中设置name属性(使得复选框能够提交),将每一次遍历到的用户的id放入复选框的value中。 为了将复选框的数据提交到DelSelectedServlet类,我们使用form表单将这个表格整体包围起来。
我们点击删除选中后,应该将表单提交,这样才可以将选择的复选框对应的id提交到DelSelectedServlet类。
我们给删除选中超链接添加一个javascript:void(0),取消这个超链接的默认跳转。不这样设置我们点击超链接超链接会跳转到href指定的资源,而我们点击超链接的目的是想让表单提交,因此必须取消超链接的默认跳转。 取消超链接的默认跳转后,我们设置超链接点击事件,点击会判断提交表单。 设置一个id,在页面加载完毕后通过表单id获取超链接对象,设置超链接的点击事件,点击超链接就会弹出是否删除所选。 如果选择是,就会判断是否有选中条目,有一个条目选中我们就将表单提交给DelSelectedServlet执行删除处理。(如果没有选中的条目不需要提交)。
当然,我们也可以使得href="javascript:deleteSelected(),href指向deleteSelected,我们设置deleteSelected方法来获取复选框的状态有选中则提交表单(与上一种方法操作相同)。
2点注意:
1、我们在表单中不设置提交表单的按钮,而是在使用JS代码判断有复选框被选中后,再调用表单的submit方法提交表单。
2、我们不需要考虑如何不删除第一行,我们只会判断name="uid"的条目是否被选中,而最上面一行即使被选中也不会被提交删除。
创建一个DelSelectedServlet类,这个类接收list.jsp提交过来的CheckBox,而CheckBox的value就是对应行用户的id值,所有行CheckBox的name都是uid,既提交的CheckBox的参数是uid=id值(表单提交的格式:name属性=value属性)。我们通过uid获取所有被选中用户的id值数组:
String[] ids = request.getParameterValues("uid");
获取调用service的delSelectedUser(id)方法对数据库进行查询。最后重定向回userListServlet,userListServlet会查询展示删除后的数据库用户资料。
在service包的UserService接口下添加delSelectedUser()方法,同时在UserServiceImpl方法实现delSelectedUser()方法,调用dao中的delete()方法,对数据库的信息进行查询。我们复用之前在DAO中设置delete(id)方法即可,不需要重新创建方法。
完成一个点击最上面的复选框可以完成全选的动作。参考自己文章《黑马就业班(02.JavaWeb+项目实战\02.JavaScript)——part2:BOM&DOM》——案例1:表格全选。
7、分页查询功能
首先是分页查询的分析,见视频16的解析。
* 好处:
1. 减轻服务器内存的开销,不需要一次性加载所有的查询数据。
2. 提升用户体验
这种较为复杂的功能需要分析功能的输入与输出即可。我们是站在服务器的角度看输入输出,客户端给服务器的数据就是输入,而服务器发送回客户端的数据就是输出。
首先,服务器需要输出到客户端的数据:总记录数totalCount、总页码totalPage、每一页数据的集合list、当前页码currentPage、每一页的显示条目rows。我们在服务器将这些数据封装成为一个PageBean分页对象,然后将这个对象输出到list.jsp页面,list.jsp拿到这个对象后,使用EL表达式与JSTL标签对数据其进行展示。
其次是客户端输入到服务器的数据:根据服务器给客户端的输出我们来分析客户端的输入。首先,总记录数totalCount我们在服务器里面通过:select count(*) from user 就可以查询出来,不需要从客户端输入;其次,总页码totalPage需要客户端提供每页显示条数rows,服务器根据每一页的显示条数计算输出数据的总页码;每一页数据的集合list可以在服务器使用分页查询,不需要客户端输入;当前页码currentPage,客户端在显示界面点击一个页码,就会将当前页面传递给服务器。因此,服务器需要接受客户端的2个数据:每一页的显示条目rows、当前页码currentPage。
总结:客户端将每一页的显示条目rows、当前页码currentPage输入给服务器,服务器根据这些数据查询出来相应的数据并封装成为一个PageBean对象,输出到客户端,客户端对PageBean对象进行遍历就可以显示相应的数据。
首先,我们会在浏览器展示页面list.jsp点击一个按钮,此时会跳转到一个处理分页查询的Servlet——FindUserByPageServlet,传递当前页码currentPage以及每一页的显示条目rows给FindUserByPageServlet。
FindUserByPageServlet接收到参数后,会调用Service的方法FindUserByPage(currentPage,rows)进行查询,返回一个封装查询结果的PageBean对象。同时将这个对象转发回list.jsp展示。
service的FindUserByPage(currentPage,rows)方法需要封装PageBean对象,这个对象需要的数据为:总记录数totalCount、总页码totalPage、每一页数据的集合list、当前页码currentPage、每一页的显示条目rows。
在service的FindUserByPage(currentPage,rows)方法中,首先创建一个新的PageBean对象,然后根据FindUserByPageServlet调用FindUserByPage(currentPage,rows)方法传递过来的2个参数,设置PageBean的currentPage、rows。调用dao的findTotalCount()方法,查询总记录数totalCount。接下来,根据当前页码currentPage以及每一页的条目数,计算分页查询的开始参数start。再调用dao的findByPage(start,rows),对数据库进行分页查询,并将查询到的当前页的数据封装成为一个List集合。最后根据前面提到的公司计算总页码totalPage。
最后将这些查询出来的数据封装进PageBean对象并返回。返回后FindUserByPageServlet就可以将对象传递给list.jsp进行展示。
首先我们在domain文件夹下创建一个PageBean对象用于封装保存返回的页面的数据。
创建一个FindUserByPageServlet类,取list.jsp传递过来的数据,调用service的findUserByPage(int current , int rows方法),返回一个PageBean对象,将查找到的PageBean对象请求转发到list.jsp显示。
在service包的UserService接口下添加findUserByPage()方法,同时在UserServiceImpl方法实现findUserByPage()方法。
在findUserByPage()方法中,创建一个空的PageBean对象,据findUserByPage方法的参数,设置PageBean对象currentPage与rows的值,调用dao的findTotalCount()方法查询总记录数,调用dao的findByPage(int start , int rows)方法分页查询当前页的List集合,计算总页码totalPage,并将这些数据封装到PageBean对象。
在dao包的UserDao接口下添加findTotalCount()、findByPage方法,同时在UserDaoImpl方法实现findTotalCount()、findByPage方法方法。并在findTotalCount()、findByPage方法方法中执行相应的查询。
我们在list.jsp页面直接访问FindUserByPageServlet类并存入相应的参数进行测试:localhost/day09case/findUserByPageServlet?currentPage=1&rows=5 , 并FindUserByPageServlet在加一行 System.out.println(pb); ,打印pb,测试有没有封装成功。结果打印:
PageBean{totalCount=22, totalPage=5, list=[User{id=4, name='王五', gender='男', age=88, address='广东', qq='222222222', email='[email protected]', username='null', password='null'}, User{id=5, name='赵六', gender='女', age=23, address='广东', qq='3246456', email='[email protected]', username='null', password='null'}, User{id=7, name='张三', gender='男', age=23, address='广东', qq='21432432', email='[email protected]', username='null', password='null'}, User{id=8, name='林九', gender='女', age=33, address='广东', qq='324324', email='sadas@ww', username='null', password='null'}, User{id=9, name='张三', gender='男', age=23, address='广东', qq='21432432', email='[email protected]', username='null', password='null'}], currentPage=1, rows=5}
说明后台的数据编写成功。
首先,我们在这里以便编写一边测试,为了可以直观得显式分页查询的效果,我们直接在地址栏输入:localhost/day09case/findUserByPageServlet?current=1&rows=5,直接访问FindUserByPageServlet并手动传递参数,这样才有效果。如果直接访问index.jsp,会访问UserListServlet转到list.jsp,显示所有的数据,并不会访问FindUserByPageServlet这无法测试分页功能。
首先,我们访问的是FindUserByPageServlet类,list.jsp显示数据部分,不再遍历user数据,而是显示FindUserByPageServlet传递过来PageBean的List。
注意,我们必须将前面所有功能中转发到UserListServlet全部该为转发到FindUserByPageServlet,否则会出现显示错误。
原来:<c:forEach items="${requestScope.user}" var="user" varStatus="s">
现在:<c:forEach items="${requestScope.pb.list}" var="user" varStatus="s">
显示总记录数与总页面,同样通过FindUserByPageServlet传递过来PageBean获取。
将所有页码换为真正的页码。 我们使用JSTL表达式遍历总页数,每遍历一个生成一个li标签。
<c:forEach begin="1" end="${requestScope.pb.totalPage}" var="i">
<li><a href="#">${i}a>li>
c:forEach>
接下来,设置每一个按钮的href的格式为:
http://localhost/day09case/findUserByPageServlet?currentPage=1&rows=5
这样我们点击这个按钮,就会跳转到FindUserByPageServlet进行分页显示处理。
href="${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${i}&rows=5"
其中${i}就是当前的页数。这样我们点击不同的页数就会跳转到那一页并显示那一页的数据
首先,我们将来不是在地址栏输入:localhost/day09case/findUserByPageServlet?currentPage=1&rows=5 来访问,而是通过index.jsp点击就可以显示有分页功能的list.jsp页面。我们看到index.jsp的代码是:
<a
href="${pageContext.request.contextPath}/userListServlet" style="text-decoration:none;font-size:33px">查询所有用户信息
a>
index.jsp之前访问的是UserListServlet类,显示所有的用户数据,那么现在我们不让index.jsp访问UserListServlet类,而是让它直接访问处理分页显示的类FindUserByPageServlet类:
<a
href="${pageContext.request.contextPath}/findUserByPageServlet" style="text-decoration:none;font-size:33px">查询所有用户信息
a>
注意,在FindUserByPageServlet类对currentPage与rows的特殊情况进行处理,既直接访问FindUserByPageServlet类,不点击分页条的某一页,currentPage为null,且rows也为null。因此当currentPage为null,且rows也为null时,在FindUserByPageServlet类对currentPage与rows的进行赋值:currentPage=1与rows=5,默认点击index.jsp的“查询所有用户信息”会访问第一页,一页5个条目。
使用BootStrap做当前页码的激活功能,既点击这一页这个按钮会有特殊的样式。
第一次访问list.jsp,我们在总页数totalPage中循环显示分页条,此时点击某一页,循环遍历表示当前的页数,由于currentPage=${i},currentPage的值就确定, 提交到findUserByPageServlet处理,findUserByPageServlet最后会返回到list.jsp显示,同时携带一个currentPage,表示之前点击的页数。
第二次访问list.jsp,此时会显示之前选中第i页的数据, list.jsp此时仍然在根据总页数totalPage循环显示分页条,此时循环中的某一个变量i如果等于currentPage,说明前一次第i页被点击, 我们将第i个按钮设置:class=“active”,分页条第i条显示特殊样式,而其他分页条则显示正常样式。(既循环使得所有分页条显示,找到循环中的第i条使其显示特殊格式,而其他分页条显示正常格式)。这样便实现了点击某一页就激活的功能。
实现点击进入上一页与下一页的功能。
我们在list.jsp点击第i页,就会将当前页数currentPage=${i}提交到findUserByPageServlet处理,findUserByPageServlet最后会返回第i页的数据到list.jsp显示。同时返回的数据还有PageBean对象pb,pb中带有当前页数pb.currentPage,那么我们在前一页设置:
${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${requestScope.pb.currentPage-1}&rows=5,
点击就会跳转到当前页的上一页。 注意此时当前页数使用PageBean对象的currentPage属性表示,别再用i表示,因为我们此时已经不在循环中。同样下一页就把currentPage换为${requestScope.pb.currentPage+1}即可。
但是这种分页查询还是有问题,就是我们在第一页点上一页或者最后一页点下一页,最后都会出错,因为currentPage已经不存在。 此时我们使用JSTL表达式控制上一页与下一页的激活状态,我们只将
/**
* 分页查询中,我们判断currentPage是否小于1,小于1就说明我们在第一页的时候点击上一页,传递过来currentPage=0
* 将其重新置为1,这样不管怎么点,返回到list.jsp页面显示的时候,currentPage都不会为0,就不会出错。
*/
if(currentPage < 1)
{
currentPage = 1;
}
同样,在最后一页点击下一页,也可以在UserServiceImpl类的findUserByPage()方法进行类似处理,如下:
/**
* 同样,我们判断当前页数currentPage是否大于总页码,如果等于总页码,说明到达最后一页还点击下一页,
* 我们将currentPage置为totalPage,在最后一页不管怎么点击下一页都只显示最后一页
*/
if(currentPage > totalPage)
{
currentPage = totalPage;
}
需要注意,currentPage的判断必须在将currentPage设置进入PageBean之前。因为判断最后一页需要用到totalPage,我们这里将totalPage的获取(需要用到totalCount)与totalCount的获取提前。
其实我们用css的样式将第一页的上一页按钮与最后一页的下一页按钮置为不可点击也可以,这是另一种做法。
这样,分页的所有功能就全部实现!!!
8、复杂条件查询功能
实现“姓名”、“籍贯”、“邮箱”的多条件查询,并且查询结果还要实现分页功能,那么我们在查询MYSQL数据库的时候必须使用模糊查询,同时使用分页查询。具体分析见视频20的分析。
模糊查询见自己文章《黑马就业班(01.JavaSE Java语言基础\12.MySQL数据库)——part1:MySQL数据库基础》的最后部分。
页面还是要展示分页的效果,所以仍然是输出一个PageBean对象。这个PageBean里面我们最关心的数据是复杂条件查询后,查询出来的所有条目数totalCount以及所有条目的集合List,因为这两个我们需要通过SQL语句来查询,而rows以及currentPage是浏览器发送过来的,而totalPage可以根据totalCount以及rows计算。显示的代码基本上改变不大。
totalCount查询语句:select count(*) from user where name like ? AND address like ?;
List集合查询语句:select * from user where name like ? AND address like ? limit ?,?;
但是,我们可能不是每一个复杂查询的参数都有选择,因此SQL语句应该是动态的
//注意这里
1、totalCount查询初始语句:select count(*) from user
2、遍历Map,判断每一个键的value是否有值(不是null或者""空字符串),如果有某一个条件,我们再拼接字符串sb.append(where key like ?)添加对应的查询语句。但是如果有多个条件,第二个还写sb.append(where key like ?)就不对,因此我们将totalCount查询初始语句设置为:
select count(*) from user where 1 = 1 效果与select count(*) from user相同,1=1是恒等式不影响查询结果。
然后sb.append(and key like ?),如果将来有多个我们再拼上:sb.append(and key like ?)也是一样的。
List集合查询语句同理。
对于浏览器输入,首先浏览器要将表单的复杂查询的参数提交过来,我们使用Map集合来封装3个查询参数,这三个参数都有键,但是用户可能没有查询某一个条件,所依不是所有的键都有值。这个Map集合会通过Request提交到服务器,我们服务器通过Request就可以获取。当然,为了实现分页查询,浏览器还需要提交当前页码totalPage以及每页显示数rows。
首先,需要说明的是,也给页面可能有很多form表单以及其他按钮类的标签通过Request对象提交数据,不只是一个form表单。比如我们的list.jsp,既有最上面的条件查询的表单提交(提交查询条件),中间显示数据的表单也会提交(checkbox用于删除选中),最后分页条按钮也会提交数据(currentPage与rows),只要各个表单按钮提交的参数名不同,我们Servlet类在处理获取的时候就能区分,而且list.jsp页面上通过request提交的参数也是提交到不同的Servlet类处理。
首先是填写条件的表单的设置。我们要将复杂条件查询的表单的参数通过Request提交到分页显示的Servlet:FindUserByPageServlet。注意给每一个标签添加name,这样表单才能提交。
其次,我们的FindUserByPageServlet接收分页条的参数,也接收复杂条件查询框的数据,最后跳转返回到list.jsp页面,返回PageBean对象。那么我们需要对FindUserByPageServlet类进行改造。
FindUserByPageServlet类除了接收分页条按钮提交过来的数据(通过getParameter(参数)方法接收),还得获取复杂条件查询框表单提交过来的参数Map(通过getParameterMap()方法接收,然后排除分页条提交的currentPage与rows)。同时,在调用service层的分页查询findUserByPage方法的时候,必须将Map集合传递进去,然后重新编写这个方法。
findUserByPage(Integer.parseInt(currentPage) , Integer.parseInt(rows) , condition);
接下来对service层UserServiceImpl类的findUserByPage()方法进行改造。
首先,我们在查询总记录数的时候需要传递参数condition:
dao.findTotalCount(condition);
其次,我们在查询每一页的记录的List集合时候,也需要传递参数condition
dao.findByPage(start , rows , condition)
接下来对dao层UserDaoImpl类的findTotalCount()方法与findByPage()方法进行改造。这部分见代码解析。
我们刷新list.jsp页面,此时我们没有使用复杂条件查询提交参数。发现下面打印的是正确的。
select count(*) from user where 1 = 1 -- sql语句
[] -- sql语句的查询参数的List集合
注意此处设置断点debug调试:先进入debug,设置断点(不设置断点会一次性运行完,与run效果一样),然后刷新list.jsp,点击查询使用复杂条件查询,就会进入debug查询,我们一步一步向下走,看各种变量的变化分析。需要注意视频中打断点的位置,我们一般要根据自己的需求对代码进行打断点。
以后开发尽量使用debug模式,这样方便调试。详细的调试方法参见下面文章,这文章讲得很清楚。
select count(*) from user where 1 = 1 and name like ? and address like ? -- sql语句
[%张%, %广东%] -- sql语句的查询参数的List集合
注意下方debug调试的几个按键的功能。
这里我们注意,结合视频debug的使用,结合上面文章介绍的debug的功能,针对这个案例,多使用几次debug调试,以熟悉debug的各个功能。(熟悉Debug很重要!!!特别是注意Debug过程中变量的变化!以后开发尽量使用Debug模式)。
完成findTotalCount()方法的编写,接下来findByPage()方法进行改造。这部分见代码解析。同样像上面一样设置断点并进行debug调试。
我们先从inde.jsp进入,控制台打印。此时默认访问第一页。
select count(*) from user where 1 = 1
[]
select * from user where 1 = 1 limit ? , ?
[0, 5]
随后我们查询“张”、“广东”进行复杂条件查询,控制台打印如下。此时默认访问第一页的5个数据。
select count(*) from user where 1 = 1 and name like ? and address like ?
[%张%, %广东%]
select * from user where 1 = 1 and name like ? and address like ? limit ? , ?
[%张%, %广东%, 0, 5]
我们点击查询的时候,我们发现条件查询的参数不见了!条件查询的参数不见,这是因为我们分页查询的条件没有回写,查询后结果返回,但是并没有携带之前的查询条件回来,因此此时查询条件不见。我们需要在FindUserByPageServlet使用Request返回查询条件condition这个Map集合,然后在list.jsp设置标签的value为返回的查询条件。
我们点击第二页,发现条件查询的结果消失了,这是因为我们点击分页条访问findUserByPageServlet的时候,并没有携带上面条件查询框的参数。那么我们在分页条按钮的href添加返回的condition的参数(condition封装上一页添加的复杂条件查询参数),使得我们点击分页条的时候,上一个分页查询的条件condition也会发送过去。
href="${pageContext.request.contextPath}/findUserByPageServlet?currentPage=${i}&rows=5&name=${condition.name[0]}&address=${condition.address[0]}&email=${condition.email[0]}"
这里如果我们查询的参数不正确,就会显示服务器错误,因此totalCount查询出来为0,我们再使用totalCount取计算分页查询的start,就会把起始位置设置为-5,查询数据库错误。因此,这里其实应该在FindUserByPageServlet判断PageBean的totalCount则当前页面数据不变,并提示查询条件错误,但是现在有点难以完成。