分页模块的分析
对于上面图片,需要用到以下元素:
pageNo
当前页码(一般是由客户端传递的)
pageTotal
总页码
pageSize
每页显示数量,每页记录数(可以是由客户端传递的,也可以是由页面布局决定的)
pageTotalSize
总记录数
items
每页的数据,是一个集合,里面装载着要分页的对象数据
当一个 jsp 需要一些数据,但是 jsp 又没有这些数据的时候,就要想到用 servlet,那么提供一个 servlet,能给 jsp 页面传递以上数据
对于分页功能,可以使用在各个地方,所以可以将以上数据封装为一个带泛型的对象
那么 servlet 给 jsp 页面传递分页模块的 JavaBean 对象
那么,第一步创建分页模块的 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;
}
如何能获取到这五个属性的值?
pageNo
当前页码(一般是由客户端传递的)pageTotal
总页码总页码 = 总记录数 / 每页显示数量
pageSize
每页显示数量,每页记录数设置默认值 4
pageTotalSize
总记录数select count(*) from t_book;
items
每页的数据,是一个集合,里面装载着要分页的对象数据select * from t_book limit begin,pageSize;
begin = ( pageNo - 1 ) * pageSize
,第一页 (1-1)*4 = 0,第二页(2-1)*4 = 4DAO 持久层是与数据库进行交互的一层,所有与数据库的操作,都在 DAO 持久层
所以从 DAO 要获取到 pageTotalSize
总记录数、 items
每页的数据
注意 items
每页的数据,返回的是包含着 Book 对象的集合,即 List
public interface PageDAO {
//获取库中图书表格的所有图书条目数
int getPageTotalSize(Connection connection);
//查看每页的图书信息
List<Book> queryItems(Connection connection, int begin, int pageSize);
}
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;
}
}
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);
}
}
在这一层业务层,就是要返回具体的 page 对象,传给web 层进行使用,用以处理分页业务
public interface PageService {
//对于page对象的5个属性,只要知道了 pageNo、pageSize,其他属性就都知道了
//而这两个属性都是通过客户端传入得到
//所以在此实现通过传入这两个参数,返回对应的page对象
Page<Book> getPage(int pageNo,int pageSize);
}
注意:
当前页码是从客户端进行获取的,但是用户也可以在网址栏直接输入请求参数 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;
}
}
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 ,能得到分页所需要的数据
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);
}
}
注意:
在首页点击 “ 图书管理 ” 的超链接的时候,跳转的是 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="#">3a>
【${requestScope.page.pageNo}】
<a href="#">5a>
<%--当不位于末页时,才显示末页、下一页的按钮--%>
<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="#">3a>
【${requestScope.page.pageNo}】
<a href="#">5a>
<%--当不位于末页时,才显示末页、下一页的按钮--%>
<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 页面 范围内有效
location.href= "${pageScope.basePath}manager/bookServlet?methodName=page&pageNo="+p;
1,2,【3】,4,5
<%--当总页码小于等于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>
当前页码位于前面 3 个 的情况,页码范围是:1-5
1,【2】,3,4,5
当前页码大于3,但是小于总页码 -2 的时候
例如共10页,位于4-7的时候
5,6,【7】,8,9
当前页码为最后 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 发生变化,可以把循环摘出来
使用
对于
标签替换
<%--分页部分--%>
<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 程序对应的方法中获取 页码参数,并传递到分页页面
在 jsp 页面需要一些数据的时候,但是 jsp 又没有这些数据的时候,就用 servlet 来处理数据,然后请求转发到 jsp 页面
但是对于首页,是在项目运行的时候,自动访问的页面,除非首页网址设置为类似于http://localhost:8080/book02/manager/bookServlet?methodName=page
,但是比较奇怪
可以新建一个 jsp 页面,然后在这个首页请求转发到 servlet 程序,然后 servlet 程序会处理数据,并请求转发到新建的 jsp 页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<jsp:forward page="/client/clientServlet?methodName=page">jsp:forward>
只有一行代码,就是请求转发到 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);
}
}
将首页想要展示的都写在此
和 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>
和前面的分页操作一样,只不过是 首页、上一页、下一页、末页、指定页码 等的超链接指向的 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 页面
编写 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);
即在登陆成功之后的欢迎页面,显示 “ 欢迎 XXX 光临 ”
此语句不仅仅出现在登陆成功的页面,在订单部分也会显示
在 UserServlet 定义了 login() 方法,当用户进行登录操作的时候,会执行此方法,会将用户输入的参数封装进 request 域中,在登录失败的时候进行回显
但是在这里,欢迎的页面不仅仅出现在登陆成功之后请求转发的页面,还有后台页面也会显示,所以将用户名封装进 request 域中,然后进行回显,行不通
我们要记住一点,对于 四大域对象,首先考虑使用范围小的,小的不行再考虑用大的
四大域对象从小到大的范围的顺序 pageContext
====>>> request
====>>> session
====>>> application
并且凡是跟登录状态有关的,先考虑 session
步骤如下:
1、UserServlet 程序中保存用户登录的信息
2、修改 welcome_menu.jsp 页面
此外,还可以修改首页 index.jsp 页面的菜单
当用户已经登录的时候,就显示 欢迎 字样,当用户未登录的时候,就显示 登录、注册
步骤:
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());
}
}
修改【注销】的菜单地址
表单重复提交有三种常见的情况:
提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5,就会发起最后一次的请求。造成表单重复提交问题。解决方法:使用重定向来进行跳转
用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,就会着急,然后多点了几次提交操作,也会造成表单重复提交。解决方法:使用验证码来防止重复提交
用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。解决方法:使用验证码来防止重复提交
对于在第一次提交的时候,先拿到 session 域中的验证码,然后会删除掉 session 域中的验证码 ,然后将拿到的验证码和用户输入的进行比较,一致就允许操作,不一致就退出
对于非首次提交的时候,第一步也是需要获取到 session 域中的验证码,但是 session 域中的验证码已经被删掉了,所以就拿到 null ,和用户输入的验证码进行比较,永远不会一致,就会退出
就防止了重复提交的行为
验证码可以直接拿到现成第三方 jar 包来使用
谷歌验证码 kaptcha 使用步骤如下:
导入谷歌验证码的 jar 包
kaptcha-2.3.2.jar
在当前工程的 web.xml 中去配置用于生成验证码的 Servlet 程序
在 jar 包里面只有一个 Servlet 程序,需要在配置文件中配置一下两个标签
注意:
的地址配置的是/xxx.jpg
因为是图片
在表单中使用 img 标签,将 src 属性配置为 servlet 程序的地址, 去显示验证码图片并使用它
在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用
注意:为了防止表单重复提交,需要在获取了 session 域中的验证码(系统提供的验证码)之后,删除 session 域的数据,就可以防止重复提交
获取 session 域中的验证码,需要使用到 谷歌 jar 包提供的常量 KAPTCHA_SESSION_KEY
设置点击验证码就可以刷新验证码
在验证码图片绑定单击事件,将验证码的地址重新赋值给验证码 img 标签的图片路径
导入谷歌验证码的 jar 包
在 web.xml 中去配置用于生成验证码的 Servlet 程序
<servlet>
<servlet-name>KaptchaServletservlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>KaptchaServletservlet-name>
<url-pattern>/kapt.jpgurl-pattern>
servlet-mapping>
在表单中使用 img 标签去显示验证码图片并使用它
在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用
切换验证码
注意对于火狐浏览器、IE 浏览器,后台会给予缓存,对于赋予完全相同的地址时,并不会有刷新效果