JavaWeb书城项目(二)

5、分页功能

分页模块的分析
在这里插入图片描述

5.1、分页的初步实现

在这里插入图片描述
对于上面图片,需要用到以下元素:
pageNo 当前页码(一般是由客户端传递的)
pageTotal 总页码
pageSize 每页显示数量,每页记录数(可以是由客户端传递的,也可以是由页面布局决定的)
pageTotalSize 总记录数
items 每页的数据,是一个集合,里面装载着要分页的对象数据

当一个 jsp 需要一些数据,但是 jsp 又没有这些数据的时候,就要想到用 servlet,那么提供一个 servlet,能给 jsp 页面传递以上数据

对于分页功能,可以使用在各个地方,所以可以将以上数据封装为一个带泛型的对象

那么 servlet 给 jsp 页面传递分页模块的 JavaBean 对象

那么,第一步创建分页模块的 JavaBean

5.1.1、编写分页模块的 JavaBean

因为pageSize 每页显示数量可以是由客户端传递的,也可以是由页面布局决定,在此先设定一个常量,作为默认值,后期需要再更改

public class Page<T> {
	//常量设置为 public ,后面也可以进行调用
    public static final int PAGE_SIZE = 4;
    private int pageNo;
    private int pageTotal;
    private int pageSize = PAGE_SIZE;
    private int pageTotalSize;
    private List<T> items;
}

如何能获取到这五个属性的值?

  1. pageNo 当前页码(一般是由客户端传递的)
  2. pageTotal 总页码
    总页码 = 总记录数 / 每页显示数量
    注意,当 总记录数 % 每页显示数量 > 0的时候,总页码+1
    即,总页码 3 = 记录数 5 / 每页显示 2
  3. pageSize 每页显示数量,每页记录数
    在此先设置默认值 4
    (可以是由客户端传递的,也可以是由页面布局决定的)
  4. pageTotalSize 总记录数
    数据库 可得
    select count(*) from t_book;
  5. items 每页的数据,是一个集合,里面装载着要分页的对象数据
    数据库 可得
    select * from t_book limit begin,pageSize;
    begin:要显示记录的起始索引(起始索引从 0 开始),可以通过当前页码和每页记录数得到。
    begin = ( pageNo - 1 ) * pageSize,第一页 (1-1)*4 = 0,第二页(2-1)*4 = 4
    pageSize:要显示的每页记录数

5.1.2、编写分页模块的 DAO

DAO 持久层是与数据库进行交互的一层,所有与数据库的操作,都在 DAO 持久层

所以从 DAO 要获取到 pageTotalSize 总记录数、 items 每页的数据

编写 PageDAO 接口

注意 items 每页的数据,返回的是包含着 Book 对象的集合,即 List<Book>

public interface PageDAO {
    //获取库中图书表格的所有图书条目数
    int getPageTotalSize(Connection connection);
    //查看每页的图书信息
    List<Book> queryItems(Connection connection, int begin, int pageSize);
}
编写 PageDAOImpl
public class PageDAOImpl  extends BaseDAO<Book> implements PageDAO {
    @Override
    public int getPageTotalSize(Connection connection) {
        String sql = "select count(*) from t_book;";
        //注意这里queryForSingleValue方法返回的是Object类型,但是实际得到的不一定是int类型
        //可以先转为大类,Number接口,然后转换为 int 类型用 intValue()
        //相较于直接强转为 long 类型更严谨些
        Number l = (Number) queryForSingleValue(connection, sql);
        return l.intValue();
    }

    @Override
    public List<Book> queryItems(Connection connection, int begin, int pageSize) {
        String sql = "select * from t_book limit ?,?;";
        List<Book> books = queryForList(connection, sql, begin, pageSize);
        return books;
    }
}
测试 DAO 持久层
public class PageTest {
    private PageDAOImpl p = new PageDAOImpl();
    @Test
    public void test(){
        Connection conn = jdbcUtils.getconn();
        int pageTotalSize = p.getPageTotalSize(conn);
        System.out.println(pageTotalSize);   //20
        jdbcUtils.close(conn);
    }
    @Test
    public void test1(){
        Connection conn = jdbcUtils.getconn();
        List<Book> books = p.queryItems(conn, 4, 4);
        books.forEach(System.out::println);
        jdbcUtils.close(conn);
    }
}

在这里插入图片描述

5.1.2、编写分页模块的 Service

在这一层业务层,就是要返回具体的 page 对象,传给web 层进行使用,用以处理分页业务

编写 PageService 接口
public interface PageService {
    //对于page对象的5个属性,只要知道了 pageNo、pageSize,其他属性就都知道了
    //而这两个属性都是通过客户端传入得到
    //所以在此实现通过传入这两个参数,返回对应的page对象
    Page<Book> getPage(int pageNo,int pageSize);
}
编写 PageServiceImpl

注意:

当前页码是从客户端进行获取的,但是用户也可以在网址栏直接输入请求参数 pageNo,这种情况就会有一些bug

比如 pageNo= -99 ,或者只有10页的时候, pageNo=1199 ,所以可以在后端这里从源头上处理

对参数 pageNo进行判断,合理的才封装进 page 对象传递给 jsp 页面

如果当前页小于0,就直接让其等于1,即首页
如果当前页大于总页码,就让其等于总页码,即尾页

在这里插入图片描述

public class PageServiceImpl implements PageService {
    private PageDAOImpl pageDAOImpl = new PageDAOImpl();
    @Override
    public Page<Book> getPage(int pageNo, int pageSize) {
        Connection conn = jdbcUtils.getconn();
        //通过 set 方法将得到的属性都传入要返回的 page 对象中
        Page<Book> bookPage = new Page<>();
        //总记录数
        int pageTotalSize = pageDAOImpl.getPageTotalSize(conn);
        bookPage.setPageTotalSize(pageTotalSize);
        //总页码 = 总记录数 / 每页显示数量,并且向上取整
        int pageTotal = pageTotalSize/pageSize;
        if( pageTotalSize % pageSize >0){
            pageTotal += 1;
        }
        bookPage.setPageTotal(pageTotal);
        //注意,当前页码是从客户端进行获取的,但是如果直接从网址输入请求参数pageNo,就会有一些bug
        //比如 pageNo=-99 ,或者只有10页的时候, pageNo=1199 ,所以可以在后端这里从源头上处理
        //如果当前页小于0,就直接让其等于1,即首页,如果当前页大于总页码,就让其等于总页码,即尾页
        //但是下面的if语句有漏洞,如果pageNo=1,pageTotal=0
//        if(pageNo<1){
//            pageNo = 1;
//        }
//        if(pageNo>pageTotal){
//            pageNo = pageTotal;
//        }
        //优化
        int pageNon = (pageNo<1)? 1 : ((pageNo>pageTotal)? pageTotal : pageNo);
        //当前页码
        bookPage.setPageNo(pageNon);
        //每页显示数量
        bookPage.setPageSize(pageSize);
        // 每页的数据
        int begin = ( pageNon - 1 ) * pageSize;
        List<Book> books = pageDAOImpl.queryItems(conn, begin, pageSize);
        bookPage.setItems(books);
        jdbcUtils.close(conn);
        return bookPage;
    }
}
测试 Service 业务层
public class PageTest {
    @Test
    public void test2(){
        PageServiceImpl pageService = new PageServiceImpl();
        Page<Book> page = pageService.getPage(1, 3);
        System.out.println(page);
    }
}

输出结果

Page{
	pageNo=1, 
	pageTotal=6, 
	pageSize=4, 
	pageTotalSize=20, 
	items=[
		Book{id=1, name='java 从入门到放弃', price=80.0, author='国哥', sales=9999, stock=9, img_path='static/img/default.jpg'}, 
		Book{id=2, name='数据结构与算法', price=78.5, author='严敏君', sales=6, stock=13, img_path='static/img/default.jpg'}, 
		Book{id=3, name='怎样拐跑别人的媳妇', price=68.0, author='龙伍', sales=99999, stock=52, img_path='static/img/default.jpg'}, 
		Book{id=4, name='木虚肉盖饭', price=16.0, author='小胖', sales=1000, stock=50, img_path='static/img/default.jpg'}
	]
}

根据 pageNo、pageSize ,能得到分页所需要的数据

5.1.3、编写分页模块的 Web

编写 BookServlet 的 page() 方法

pageNo、pageSize 两个参数是客户端传递的,所以先获取这两个请求参数

servlet 这一部分的目的是为了给 jsp 页面传递分页模块的 Page 对象
所以调用 PageServiceImpl 的方法,获取 Page 对象

将获取到的 Page 对象放在 request 域中,再请求转发到 book_manager.jsp 页面

public class BookServlet extends BaseServlet {
    protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PageServiceImpl pageServiceImpl = new PageServiceImpl();
        // 获取请求的  pageNo、pageSize 两个参数
        int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 1);
        int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.PAGE_SIZE);
        //调用 PageServiceImpl 的 getPage() 方法
        Page<Book> page = pageServiceImpl.getPage(pageNo, pageSize);
        //将 Page 对象封装进 request 域中
        request.setAttribute("page",page);
        // 请求转发到图书列表
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }
}

5.2、 编写 book_manager.jsp 页面

注意:
在首页点击 “ 图书管理 ” 的超链接的时候,跳转的是 BookServlet 的 query() 方法,现在更改为 page() 方法
在这里插入图片描述

更改为 page() 方法之后, book_manager.jsp 页面是从 page() 方法请求转发过来的,那么 book_manager.jsp 页面之内的 request 域中的数据就会发生变化,注意更改

在这里插入图片描述
注意:
运行时候会显示错误信息,如下:
在这里插入图片描述
会显示这个错误信息是因为在 WebUtils 中的 parseInt() 方法,当项目首次打开的时候,客户端没有选择当前页,所以就没有参数传入,会 catch 到异常信息,并显示出来,但并不影响代码运行

在这里插入图片描述

在 book_manager.jsp 页面,先将首页部分已经排版好的分页标签复制过来

	<div id="page_nav">
		<a href="#">首页</a>
		<a href="#">上一页</a>
		<a href="#">3</a>4<a href="#">5</a>
		<a href="#">下一页</a>
		<a href="#">末页</a>10页,30条记录 到第<input value="4" name="pn" id="pn_input"/><input type="button" value="确定">
	</div>

对以上部分进行改写

首页、上一页、下一页、末页实现

注意:
当不位于首页时,才显示首页、上一页的按钮
当不位于末页时,才显示末页、下一页的按钮

在进行 if 判断的时候,判断条件的编写需要注意

${requestScope.page.pageNo > 1}
而不是
${requestScope.page.pageNo} > 1

${requestScope.page.pageNo < requestScope.page.pageTotal}
而不是
${requestScope.page.pageNo} < ${requestScope.page.pageTotal}

<%--分页部分--%>
<div id="page_nav">
	<%--当不位于首页时,才显示首页、上一页的按钮--%>
	<c:if test="${requestScope.page.pageNo > 1}">
		<a href="manager/bookServlet?methodName=page&pageNo=1">首页</a>
		<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageNo -1}">上一页</a>
	</c:if>
		<a href="#">3</a>
		【${requestScope.page.pageNo}】
		<a href="#">5</a>
		<%--当不位于末页时,才显示末页、下一页的按钮--%>
		<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
			<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageNo +1}">下一页</a>
			<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageTotal}">末页</a>
		</c:if>
		共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalSize}条记录 到第<input value="${requestScope.page.pageNo}" name="pn" id="pn_input"/><input type="button" value="确定">
</div>
跳转到指定页数 实现
<%--分页部分--%>
<div id="page_nav">
	<%--当不位于首页时,才显示首页、上一页的按钮--%>
	<c:if test="${requestScope.page.pageNo > 1}">
		<a href="manager/bookServlet?methodName=page&pageNo=1">首页</a>
		<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageNo -1}">上一页</a>
	</c:if>
		<a href="#">3</a>
		【${requestScope.page.pageNo}】
		<a href="#">5</a>
		<%--当不位于末页时,才显示末页、下一页的按钮--%>
		<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
			<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageNo +1}">下一页</a>
			<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageTotal}">末页</a>
		</c:if>
		共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalSize}条记录 到第<input value="${requestScope.page.pageNo}" name="pn" id="pn_input"/><input class="pagebutton" type="button" value="确定">
		<script type="text/javascript">
			$(function () {
				//确定按钮
				$("input.pagebutton").click(function () {
					//获取前面输入框中 输入的页码
					var p = $("#pn_input").val();
					//跳转到指定页码对应的网址
					// javaScript 语言中提供了一个 location 地址栏对象
					// 它有一个属性叫 href.它可以获取浏览器地址栏中的地址
					// href 属性可读,可写
					location.href="http://localhost:8080/book02/manager/bookServlet?methodName=page&pageNo="+p;
				})
			})
		</script>
</div>

注意:
在点击事件中,是传入具体的网址进行跳转,来实现指定页码跳转的

所以就会有问题:换电脑运行的时候,网址会不会发生作用

所以要像 动态 base 标签 一般,将前面的 http协议、主机ip 等信息进行动态化

而在 动态 base 标签 已经设置好了,当前页面也调用了这个 base 标签,可以传入 pageContext 域中,然后在此进行调用即可

pageContext 域:是 jsp 的上下文对象,当前 jsp 页面 范围内有效

在这里插入图片描述
注意写法:
EL 表达式写在双引号里面

location.href= "${pageScope.basePath}manager/bookServlet?methodName=page&pageNo="+p;
页码 1,2,【3】,4,5 的显示

实现以下效果,要分几种情况
在这里插入图片描述

  1. 当总页码小于等于5时,全部显示1,2,【3】,4,5
    执行循环,循环范围是 1-总页码
	<%--当总页码小于等于5时,全部显示--%>
	<c:if test="${requestScope.page.pageTotal <=5 }">
		<c:forEach begin="1" end="${requestScope.page.pageTotal}" var="i">
			<c:if test="${i ==requestScope.page.pageNo}">
				【${requestScope.page.pageNo}】
			</c:if>
			<c:if test="${i != requestScope.page.pageNo}">
				<a href="manager/bookServlet?methodName=page&pageNo=${i}">${i}</a>
			</c:if>
		</c:forEach>
	</c:if>
  1. 当总页码大于5时,分三种情况
    1. 当前页码位于前面 3 个 的情况,页码范围是:1-5
      1,【2】,3,4,5

    2. 当前页码大于3,但是小于总页码 -2 的时候
      例如共10页,位于4-7的时候
      5,6,【7】,8,9

    3. 当前页码为最后 3 个,页码范围是:总页码减 4 - 总页码
      6,7,8,【9】,10

	<%--当总页码大于5时,分三种情况--%>
	<c:if test="${requestScope.page.pageTotal > 5 }">
		<%--当前页码位于1、2、3的时候--%>
		<c:if test="${requestScope.page.pageNo <= 3}">
			<c:forEach begin="1" end="5" var="i">
				<c:if test="${i ==requestScope.page.pageNo}">
					【${requestScope.page.pageNo}】
				</c:if>
				<c:if test="${i != requestScope.page.pageNo}">
					<a href="manager/bookServlet?methodName=page&pageNo=${i}">${i}</a>
				</c:if>
			</c:forEach>
		</c:if>
		<%--当前页码大于3,但是小于总页码-2 的时候,例如共10页,位于4-7的时候--%>
		<c:if test="${requestScope.page.pageNo > 3 && requestScope.page.pageNo <(requestScope.page.pageTotal-2)}">
			<c:forEach begin="${requestScope.page.pageNo - 2}" end="${requestScope.page.pageNo + 2}" var="i">
				<c:if test="${i ==requestScope.page.pageNo}">
					【${requestScope.page.pageNo}】
				</c:if>
				<c:if test="${i != requestScope.page.pageNo}">
					<a href="manager/bookServlet?methodName=page&pageNo=${i}">${i}</a>
				</c:if>
			</c:forEach>
		</c:if>
		<%--当前页码大于等于总页码-2 的时候,例如共10页,位于8-10的时候--%>
		<c:if test="${requestScope.page.pageNo >= (requestScope.page.pageTotal-2)}">
		<c:forEach begin="${requestScope.page.pageTotal - 4}" end="${requestScope.page.pageTotal}" var="i">
			<c:if test="${i ==requestScope.page.pageNo}">
				【${requestScope.page.pageNo}】
			</c:if>
			<c:if test="${i != requestScope.page.pageNo}">
				<a href="manager/bookServlet?methodName=page&pageNo=${i}">${i}</a>
			</c:if>
		</c:forEach>
		</c:if>
	</c:if>

对于以上代码,过于冗长,可以进行优化

不难发现,上面有相同的部分——循环,仅仅是参数 begin、end 发生变化,可以把循环摘出来

使用 <c:set /> 标签对变量进行赋值

对于 <c:if />标签,也可以用 <c:choose> <c:when> <c:otherwise>标签替换

<%--分页部分--%>
<div id="page_nav">
	<%--当不位于首页时,才显示首页、上一页的按钮--%>
	<c:if test="${requestScope.page.pageNo > 1}">
		<a href="manager/bookServlet?methodName=page&pageNo=1">首页</a>
		<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageNo -1}">上一页</a>
	</c:if>
		
	<%--页码的显示--%>
	<c:choose>
		<%--当总页码小于等于5时,全部显示--%>
		<c:when test="${requestScope.page.pageTotal <=5 }">
			<c:set var="b" value="1"/>
			<c:set var="e" value="${requestScope.page.pageTotal}"/>
		</c:when>
		<%--当总页码大于5时,分三种情况--%>
		<c:otherwise>
			<c:choose>
				<%--当前页码位于1、2、3的时候--%>
				<c:when test="${requestScope.page.pageNo <= 3}">
					<c:set var="b" value="1"/>
					<c:set var="e" value="5"/>
				</c:when>
				<%--当前页码大于等于总页码-2 的时候,例如共10页,位于8-10的时候--%>
				<c:when test="${requestScope.page.pageNo >= (requestScope.page.pageTotal-2)}">
					<c:set var="b" value="${requestScope.page.pageTotal - 4}"/>
					<c:set var="e" value="${requestScope.page.pageTotal}"/>
				</c:when>
				<%--当前页码大于3,但是小于总页码-2 的时候,例如共10页,位于4-7的时候--%>
				<c:otherwise>
					<c:set var="b" value="${requestScope.page.pageNo - 2}"/>
					<c:set var="e" value="${requestScope.page.pageNo + 2}"/>
				</c:otherwise>
			</c:choose>
		</c:otherwise>
	</c:choose>
	<%--循环语句--%>
	<c:forEach begin="${b}" end="${e}" var="i">
		<c:if test="${i ==requestScope.page.pageNo}">
			【${requestScope.page.pageNo}】
		</c:if>
		<c:if test="${i != requestScope.page.pageNo}">
			<a href="manager/bookServlet?methodName=page&pageNo=${i}">${i}</a>
		</c:if>
	</c:forEach>

	<%--当不位于末页时,才显示末页、下一页的按钮--%>
	<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
		<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageNo +1}">下一页</a>
		<a href="manager/bookServlet?methodName=page&pageNo=${requestScope.page.pageTotal}">末页</a>
	</c:if>
		
	共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalSize}条记录 到第<input value="${requestScope.page.pageNo}" name="pn" id="pn_input"/>页
	
	<%--跳转指定页码--%>
	<input class="pagebutton" type="button" value="确定">
		<script type="text/javascript">
			$(function () {
				//确定按钮
				$("input.pagebutton").click(function () {
					//获取前面输入框中 输入的页码
					var p = $("#pn_input").val();
					//跳转到指定页码对应的网址
					// javaScript 语言中提供了一个 location 地址栏对象
					// 它有一个属性叫 href.它可以获取浏览器地址栏中的地址
					// href 属性可读,可写
					location.href= "${pageScope.basePath}manager/bookServlet?methodName=page&pageNo="+p;
				})
			})
		</script>
</div>
添加、修改、删除等操作后的回显页面

因为现在 图书管理 部分不再是跳转到 BookServlet 的 query() 方法,而是跳转到 BookServlet 的 page() 方法

并且在删除、修改之后要跳转到当前正在修改数据的页码

添加、修改:
当点击跳转到 book_edit.jsp 页面的时候,传递请求参数
在这里插入图片描述
在 book_edit.jsp 页面获取参数,并传递到 bookServlet 程序
在这里插入图片描述
在 BookServlet 程序对应的方法中获取 页码参数,并传递到分页页面
在这里插入图片描述
删除:
当点击跳转到 bookServlet 程序的时候,传递请求参数,在 BookServlet 程序对应的方法中获取 页码参数,并传递到分页页面
在这里插入图片描述
在这里插入图片描述

5.3、 编写 index.jsp 首页的分页

在这里插入图片描述
在 jsp 页面需要一些数据的时候,但是 jsp 又没有这些数据的时候,就用 servlet 来处理数据,然后请求转发到 jsp 页面

但是对于首页,是在项目运行的时候,自动访问的页面,除非首页网址设置为类似于http://localhost:8080/book02/manager/bookServlet?methodName=page,但是比较奇怪

可以新建一个 jsp 页面,然后在这个首页请求转发到 servlet 程序,然后 servlet 程序会处理数据,并请求转发到新建的 jsp 页面

web 目录下的 index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:forward page="/client/clientServlet?methodName=page"></jsp:forward>

只有一行代码,就是请求转发到 ClientServlet 程序

ClientServlet 程序

可以在此处理数据,然后请求转发到 新建的client目录下的 index.jsp 页面

与 BookServlet 程序里面的 page() 方法一致,就是最后请求转发的地址不一样,其他都一样

public class ClientServlet extends BaseServlet {
    protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PageServiceImpl pageServiceImpl = new PageServiceImpl();
        // 获取请求的 pageNo、pageSize 两个参数
        int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 1);
        int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.PAGE_SIZE);
        //调用 PageServiceImpl 的 getPage() 方法
        Page<Book> page = pageServiceImpl.getPage(pageNo, pageSize);
        //将 Page 对象封装进 request 域中
        request.setAttribute("page",page);
        //跳转到新建的client目录下的 index
        request.getRequestDispatcher("/pages/client/index.jsp").forward(request,response);
    }
}

client目录下的 index.jsp

将首页想要展示的都写在此

1、图书信息的展示

和 book_manager.jsp 里面展示图书信息的代码类似,也是使用 for each

<c:forEach items="${requestScope.page.items}" var="book">
	<div class="b_list">
		<div class="img_div">
			<img class="book_img" alt="" src="${book.img_path}" />
		</div>
		<div class="book_info">
			<div class="book_name">
				<span class="sp1">书名:</span>
				<span class="sp2">${book.name}</span>
			</div>
			<div class="book_author">
				<span class="sp1">作者:</span>
				<span class="sp2">${book.author}</span>
			</div>
			<div class="book_price">
				<span class="sp1">价格:</span>
				<span class="sp2">¥${book.price}</span>
			</div>
			<div class="book_sales">
				<span class="sp1">销量:</span>
				<span class="sp2">${book.sales}</span>
			</div>
			<div class="book_amount">
				<span class="sp1">库存:</span>
				<span class="sp2">${book.stock}</span>
			</div>
			<div class="book_add">
				<button>加入购物车</button>
			</div>
		</div>
	</div>
</c:forEach>
2、分页操作

和前面的分页操作一样,只不过是 首页、上一页、下一页、末页、指定页码 等的超链接指向的 servlet 程序地址不同

不防就将要用到的 servlet 程序地址定义为 Page 对象的属性之一,然后将分页操作单独抽离出来,然后在想要使用的位置动态包含即可

Page 对象
在 page 对象中添加 url 属性
生成get/set方法,构造器就不需要改了
在这里插入图片描述
Servlet 程序的 page() 分页方法中设置 url 的分页请求地址
在这里插入图片描述
在这里插入图片描述

抽离出来的 page_nav.jsp 页面
修改分页条中请求地址为 url 变量输出,并抽取一个单独的 jsp 页面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core_1_1" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%--分页部分--%>
<div id="page_nav">
    <%--当不位于首页时,才显示首页、上一页的按钮--%>
    <c:if test="${requestScope.page.pageNo > 1}">
        <a href="${requestScope.page.url}&pageNo=1">首页</a>
        <a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo -1}">上一页</a>
    </c:if>

    <%--页码的显示--%>
    <c:choose>
        <%--当总页码小于等于5时,全部显示--%>
        <c:when test="${requestScope.page.pageTotal <=5 }">
            <c:set var="b" value="1"/>
            <c:set var="e" value="${requestScope.page.pageTotal}"/>
        </c:when>
        <%--当总页码大于5时,分三种情况--%>
        <c:otherwise>
            <c:choose>
                <%--当前页码位于1、2、3的时候--%>
                <c:when test="${requestScope.page.pageNo <= 3}">
                    <c:set var="b" value="1"/>
                    <c:set var="e" value="5"/>
                </c:when>
                <%--当前页码大于等于总页码-2 的时候,例如共10页,位于8-10的时候--%>
                <c:when test="${requestScope.page.pageNo >= (requestScope.page.pageTotal-2)}">
                    <c:set var="b" value="${requestScope.page.pageTotal - 4}"/>
                    <c:set var="e" value="${requestScope.page.pageTotal}"/>
                </c:when>
                <%--当前页码大于3,但是小于总页码-2 的时候,例如共10页,位于4-7的时候--%>
                <c:otherwise>
                    <c:set var="b" value="${requestScope.page.pageNo - 2}"/>
                    <c:set var="e" value="${requestScope.page.pageNo + 2}"/>
                </c:otherwise>
            </c:choose>
        </c:otherwise>
    </c:choose>
    <%--循环语句--%>
    <c:forEach begin="${b}" end="${e}" var="i">
        <c:if test="${i ==requestScope.page.pageNo}">
            【${requestScope.page.pageNo}】
        </c:if>
        <c:if test="${i != requestScope.page.pageNo}">
            <a href="${requestScope.page.url}&pageNo=${i}">${i}</a>
        </c:if>
    </c:forEach>

    <%--当不位于末页时,才显示末页、下一页的按钮--%>
    <c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
        <a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo +1}">下一页</a>
        <a href="${requestScope.page.url}&pageNo=${requestScope.page.pageTotal}">末页</a>
    </c:if>

    共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalSize}条记录 到第<input value="${requestScope.page.pageNo}" name="pn" id="pn_input"/>页

    <%--跳转指定页码--%>
    <input class="pagebutton" type="button" value="确定">
    <script type="text/javascript">
        $(function () {
            //确定按钮
            $("input.pagebutton").click(function () {
                //获取前面输入框中 输入的页码
                var p = $("#pn_input").val();
                //跳转到指定页码对应的网址
                // javaScript 语言中提供了一个 location 地址栏对象
                // 它有一个属性叫 href.它可以获取浏览器地址栏中的地址
                // href 属性可读,可写
                location.href= "${pageScope.basePath}${requestScope.page.url}&pageNo="+p;
            })
        })
    </script>
</div>

在这里插入图片描述
book_manager.jsp 页面
client目录下的 index.jsp 页面

在要使用的页面中,使用动态包含即可(静态包含也可)
在这里插入图片描述

5.4、首页价格搜索

在这里插入图片描述
先按价格搜索,在对结果进行分页显示

编写 DAO
从数据库查询价格区间内的总记录数
从数据库查询价格区间内的页数据

public class PageDAOImpl  extends BaseDAO<Book> implements PageDAO {
    public int getPageTotalSizeByPrice(Connection connection,int minPrice,int maxPrice) {
        String sql = "select count(*) from t_book WHERE `price` between ? and ?;";
        //注意这里queryForSingleValue方法返回的是Object类型,但是实际得到的不一定是int类型
        //可以先转为大类,Number接口,然后转换为 int 类型用 intValue()
        //相较于直接强转为 long 类型更严谨些
        Number l = (Number) queryForSingleValue(connection, sql,minPrice,maxPrice);
        return l.intValue();
    }

    public List<Book> queryItemsByPrice(Connection connection, int begin, int pageSize,int minPrice,int maxPrice) {
        String sql = "select * from t_book WHERE `price` between ? and ? order by `price` limit ?,?;";
        List<Book> books = queryForList(connection, sql,minPrice,maxPrice, begin, pageSize);
        return books;
    }
}

编写 Service 层
调用 DAO 层的方法,返回一个价格区间内的 page 对象

public class PageServiceImpl implements PageService {
    private PageDAOImpl pageDAOImpl = new PageDAOImpl();
    public Page<Book> getPageByPrice(int pageNo, int pageSize,int minPrice,int maxPrice) {
        Connection conn = jdbcUtils.getconn();
        //通过 set 方法将得到的属性都传入要返回的 page 对象中
        Page<Book> bookPage = new Page<>();
        //总记录数
        int pageTotalSize = pageDAOImpl.getPageTotalSizeByPrice(conn,minPrice,maxPrice);
        bookPage.setPageTotalSize(pageTotalSize);
        //总页码 = 总记录数 / 每页显示数量
        int pageTotal = pageTotalSize/pageSize;
        if( pageTotalSize % pageSize >0){
            pageTotal += 1;
        }
        bookPage.setPageTotal(pageTotal);
        //当前页码
        int pageNon = (pageNo<1)? 1 : ((pageNo>pageTotal)? pageTotal : pageNo);
        bookPage.setPageNo(pageNon);
        //每页显示数量
        bookPage.setPageSize(pageSize);
        // 每页的数据
        int begin = ( pageNon - 1 ) * pageSize;
        List<Book> books = pageDAOImpl.queryItemsByPrice(conn, begin, pageSize,minPrice,maxPrice);
        bookPage.setItems(books);
        jdbcUtils.close(conn);
        return bookPage;
    }
}

编写 web 层
这里在获取请求参数 最低价、最高价的时候,要设置默认值,最高价默认值可以设置为 Integer.MAX_VALUE

也可以去数据库获取最高的价格,然后设置为默认值

贪懒,没有去数据库获取,直接设为 Integer.MAX_VALUE

public class ClientServlet extends BaseServlet {
    protected void pageByPrice(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PageServiceImpl pageServiceImpl = new PageServiceImpl();
        // 获取请求的 pageNo、pageSize、minPrice、maxPrice 参数
        int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 1);
        int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.PAGE_SIZE);
        int minPrice = WebUtils.parseInt(request.getParameter("min"), 0);
        int maxPrice = WebUtils.parseInt(request.getParameter("max"), Integer.MAX_VALUE);
        //将用户输入的最低价、最高价封装进 request 域中,用于表单回显
        request.setAttribute("min",minPrice);
        request.setAttribute("max",maxPrice);
        //调用 PageServiceImpl 的 getPageByPrice() 方法
        Page<Book> page = pageServiceImpl.getPageByPrice(pageNo, pageSize,minPrice,maxPrice);
        //设置 Page 对象的 url 属性
        page.setUrl("client/clientServlet?methodName=pageByPrice");
        //将 Page 对象封装进 request 域中
        request.setAttribute("page",page);
        //跳转到新建的client目录下的 index
        request.getRequestDispatcher("/pages/client/index.jsp").forward(request,response);
    }
}

client目录下的 index.jsp 页面
注意,表单的提交要设置参数,不能直接在请求地址中添加
在这里插入图片描述

有问题需要解决
在这里插入图片描述
在价格搜索之后,下面分页的按钮上面没有最低价和最高价的请求参数,所以点击之后会显示所有的图书,而不是价格搜索出来的图书
在这里插入图片描述
因为分页的调用是传入 Page 对象 url 属性,那么在 servlet 程序里面设置 url 属性的时候,将 最低价、最高价的参数也传入即可

//设置 Page 对象的 url 属性
page.setUrl("client/clientServlet?methodName=pageByPrice&min="+minPrice+"&max="+maxPrice);

6、登陆、登出功能完善

6.1、登陆—显示用户名

即在登陆成功之后的欢迎页面,显示 “ 欢迎 XXX 光临 ”
在这里插入图片描述
此语句不仅仅出现在登陆成功的页面,在订单部分也会显示
在这里插入图片描述
在 UserServlet 定义了 login() 方法,当用户进行登录操作的时候,会执行此方法,会将用户输入的参数封装进 request 域中,在登录失败的时候进行回显

但是在这里,欢迎的页面不仅仅出现在登陆成功之后请求转发的页面,还有后台页面也会显示,所以将用户名封装进 request 域中,然后进行回显,行不通

我们要记住一点,对于 四大域对象,首先考虑使用范围小的,小的不行再考虑用大的

四大域对象从小到大的范围的顺序 pageContext ====>>> request ====>>> session ====>>> application

并且凡是跟登录状态有关的,先考虑 session

步骤如下:
1、UserServlet 程序中保存用户登录的信息
在这里插入图片描述
2、修改 welcome_menu.jsp 页面
在这里插入图片描述

此外,还可以修改首页 index.jsp 页面的菜单

当用户已经登录的时候,就显示 欢迎 字样,当用户未登录的时候,就显示 登录、注册
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2、登出—注销用户

步骤:
1、销毁 Session 中用户登录的信息(或者销毁 Session)
2、重定向到首页(或登录页面)

UserServlet 程序中添加 logout 方法

public class UserServlet extends BaseServlet {
    protected void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //清空 session 域中的用户数据
        request.getSession().invalidate();
        //请求重定向到首页(仅仅传入获取工程路径的方法)
        response.sendRedirect(request.getContextPath());
    }
}

修改【注销】的菜单地址

在这里插入图片描述

6.3、表单重复提交之-----验证码

表单重复提交有三种常见的情况:

  1. 提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5,就会发起最后一次的请求。造成表单重复提交问题。解决方法:使用重定向来进行跳转

  2. 用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,就会着急,然后多点了几次提交操作,也会造成表单重复提交。解决方法:使用验证码来防止重复提交

  3. 用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。解决方法:使用验证码来防止重复提交

在这里插入图片描述
使用验证码来防止重复提交的原理,无非就是

对于在第一次提交的时候,先拿到 session 域中的验证码,然后会删除掉 session 域中的验证码 ,然后将拿到的验证码和用户输入的进行比较,一致就允许操作,不一致就退出

对于非首次提交的时候,第一步也是需要获取到 session 域中的验证码,但是 session 域中的验证码已经被删掉了,所以就拿到 null ,和用户输入的验证码进行比较,永远不会一致,就会退出

就防止了重复提交的行为

6.4、谷歌 kaptcha 图片验证码的使用

验证码可以直接拿到现成第三方 jar 包来使用

谷歌验证码 kaptcha 使用步骤如下:

  1. 导入谷歌验证码的 jar 包
    kaptcha-2.3.2.jar

  2. 在当前工程的 web.xml 中去配置用于生成验证码的 Servlet 程序
    在 jar 包里面只有一个 Servlet 程序,需要在配置文件中配置一下两个标签
    注意: <url-pattern> 的地址配置的是/xxx.jpg 因为是图片

  3. 在表单中使用 img 标签,将 src 属性配置为 servlet 程序的地址, 去显示验证码图片并使用它

  4. 在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用
    注意:为了防止表单重复提交,需要在获取了 session 域中的验证码(系统提供的验证码)之后,删除 session 域的数据,就可以防止重复提交
    获取 session 域中的验证码,需要使用到 谷歌 jar 包提供的常量 KAPTCHA_SESSION_KEY

  5. 设置点击验证码就可以刷新验证码
    在验证码图片绑定单击事件,将验证码的地址重新赋值给验证码 img 标签的图片路径

导入谷歌验证码的 jar 包
在这里插入图片描述
在 web.xml 中去配置用于生成验证码的 Servlet 程序

    <servlet>
        <servlet-name>KaptchaServlet</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>KaptchaServlet</servlet-name>
        <url-pattern>/kapt.jpg</url-pattern>
    </servlet-mapping>

在表单中使用 img 标签去显示验证码图片并使用它
在这里插入图片描述
在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用
在这里插入图片描述
切换验证码

注意对于火狐浏览器、IE 浏览器,后台会给予缓存,对于赋予完全相同的地址时,并不会有刷新效果

所以可以在链接后面给一个永远不相同的请求参数,可以考虑加上时间戳,random有时候可能会相同
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值