分页最终是落到sql语句。
如下查商品信息:
先查三条,再查三条,sql语句为:
select * from tb_product tp LIMIT 0,3
select * from tb_product tp LIMIT 3,3
select * from tb_product tp LIMIT 6,3
要查询多少页,每页查询多少条记录: select * from tb_product tp LIMIT (page-1)*pageSize,pageSize
1.普通分页查询
①.准备工作
在数据库准备如上表
新建项目day6_page
新建web.WEB-INF.lib包,引入jar包(fastjson,lombok,mysql-connector-java),同时配置给项目
将bootstrap的css,fonts,js包放到web包下
新建web.img目录,将商品图片放入该包
新建src.jdbc.properties
新建javasm.util包,javasm.controller包,javasm.dao包,javasm.entity包,javasm.service包,javasm.test包
新建util.DBHelper类,entity.ReturnCode类,entity.ReturnEntity类。
②.后端
新建dao.ProdDao
public interface ProdDao { /* * 支持分页 * */ List<Product> getAllProd(Integer page,Integer pageSize); }
新建dao.impl.ProdDaoImpl
如果有传入参数会用占位符,但参数不是由用户自己输入,即用户没有注入的可能,也可直接往里写。当然用?占位符更好,更好彻底。
public class ProdDaoImpl implements ProdDao { @Override public List<Product> getAllProd(Integer page,Integer pageSize) { Connection conn = DBHelper.getConn(); String sql = "select tp.prod_id,tp.prod_desc,tp.prod_name,tp.prod_price,tp.prod_img from tb_product tp LIMIT "+(page-1)*pageSize+","+pageSize; PreparedStatement preparedStatement = null; ResultSet resultSet = null; List<Product> listProd = new ArrayList<Product>(); System.out.println(sql); try { //防止注入攻击 preparedStatement = conn.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ String prodId = resultSet.getString("prod_id"); String prodName = resultSet.getString("prod_name"); Double prodPrice = resultSet.getDouble("prod_price"); String prodImg = resultSet.getString("prod_img"); String prodDesc = resultSet.getString("prod_desc"); Product user = new Product(prodId,prodName,prodPrice,prodImg,prodDesc); listProd.add(user); } } catch (SQLException e) { e.printStackTrace(); }finally { DBHelper.CloseConn(conn,null,preparedStatement,resultSet); } return listProd; } }
新建test.MyTest,做本地测试(使用main方法,测idea里写的java代码)
public class MyTest { public static void main(String[] args) { ProdDao prodDao = new ProdDaoImpl(); List<Product> allProd = prodDao.getAllProd(1, 3); System.out.println(allProd); } }
输出结果:select tp.prod_id,tp.prod_desc,tp.prod_name,tp.prod_price,tp.prod_img from tb_product tp LIMIT 0,3
[Product(prodId=FS0001452178, prodName=运动鞋, prodPrice=55.6, prodImg=img/shoe.jpg, prodDesc=只有一只, prodNum=null), Product(prodId=SJ00110013, prodName=便宜手机, prodPrice=95.3, prodImg=img/phone2.jpg, prodDesc=便宜手机, prodNum=null), Product(prodId=SJ00130333, prodName=测试手机12, prodPrice=100.0, prodImg=img/phone1.jpg, prodDesc=测试手机, prodNum=null)]
可测几次,尽量测各种情况。
新建 service.ProdService
public interface ProdService { List<Product> getAllProd(Integer page,Integer pageSize); }
新建service.impl.ProdServiceImpl
public class ProdServiceImpl implements ProdService { @Override public List<Product> getAllProd(Integer page,Integer pageSize) { ProdDao pd = new ProdDaoImpl(); return pd.getAllProd(page,pageSize); } }
新建controller.AjaxProdServlet
接收到的参数page和pageSize是字符串,最终要的是数字。故接收参数后,要转换格式,再封装对象。
但要注意有些情况是转换不了的,比如:没有参数,拿到了null。或者拿到了空字符串。故:servlet接收到参数,转换数据类型,一般会先做非空判断。非空判断,一种是null(对应没有key的情况,即对应的参数没有传),一种是""(传递了参数,但没有值)。
非空判断可认为是格式性代码。
但这样做了每次必须传参数,为了提高容错性,会给最终要使用的数据加初始值。不同的系统不太一样,一般使用比较广泛的是:page的默认值给1,pageSize给10。这里数据没那么多,pageSize就先给个3。
@WebServlet("/ajaxProd") public class AjaxProdServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String pageStr = req.getParameter("page"); String pageSizeStr = req.getParameter("pageSize"); Integer page = 1; Integer pageSize = 3; //转换前 会做非空判断 if(!"".equals(pageStr)&&pageStr!=null){ page = Integer.parseInt(pageStr); } if(!"".equals(pageSizeStr)&&pageSizeStr!=null){ pageSize = Integer.parseInt(pageSizeStr); } /* * 调用业务逻辑代码 * */ ProdService ps =new ProdServiceImpl(); List<Product> allProd = ps.getAllProd(page,pageSize); //做测试用 System.out.println(allProd); }
测试
修改系统运行环境为Tomcat(右上角)
修改根目录为/day6(右上角)
重新部署,运行,访问:http://localhost:8080/day6/ajaxProd
idea控制台输出:select tp.prod_id,tp.prod_desc,tp.prod_name,tp.prod_price,tp.prod_img from tb_product tp LIMIT 0,3
[Product(prodId=FS0001452178, prodName=运动鞋, prodPrice=55.6, prodImg=img/shoe.jpg, prodDesc=只有一只, prodNum=null), Product(prodId=SJ00110013, prodName=便宜手机, prodPrice=95.3, prodImg=img/phone2.jpg, prodDesc=便宜手机, prodNum=null), Product(prodId=SJ00130333, prodName=测试手机12, prodPrice=100.0, prodImg=img/phone1.jpg, prodDesc=测试手机, prodNum=null)]
同样尽可能测试
给ReturnCode新增两条状态
public enum ReturnCode { OPT_SUCCESS(10000,"操作成功"), OPT_FAILED(10005,"操作失败"), QUERY_SUCCESS(20000,"查询成功"), QUERY_NODATA(20001,"没有数据"), NAME_DUP(20001,"用户名重复"), NAME_OK(20000,"用户名可用"); private Integer code; private String msg; private ReturnCode(Integer code,String msg){ this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }
将AjaxProdServlet改成服务接口
按照规范去写代码,并写接口文档
@WebServlet("/ajaxProd") public class AjaxProdServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /* * 接收参数 * 转换格式 * 封装对象 * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 * pageSize 每页显示的记录数 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功" * * */ req.setCharacterEncoding("utf-8"); String pageStr = req.getParameter("page"); String pageSizeStr = req.getParameter("pageSize"); Integer page = 1; Integer pageSize = 3; if(!"".equals(pageStr)&&pageStr!=null){ page = Integer.parseInt(pageStr); } if(!"".equals(pageSizeStr)&&pageSizeStr!=null){ pageSize = Integer.parseInt(pageSizeStr); } /* * 调用业务逻辑代码 * */ ProdService ps =new ProdServiceImpl(); List<Product> allProd = ps.getAllProd(page,pageSize); /* * 返回数据 * * */ ReturnEntity re = new ReturnEntity(); if(allProd.size()>0){ re.setRetCode(ReturnCode.QUERY_SUCCESS.getCode()); re.setRetMsg(ReturnCode.QUERY_SUCCESS.getMsg()); re.setRetData(allProd); }else{ re.setRetCode(ReturnCode.QUERY_NODATA.getCode()); re.setRetMsg(ReturnCode.QUERY_NODATA.getMsg()); } //之前是resp.setContentType("text/html;charset=utf-8") 这样传浏览器也可以解析出这是json格式的字符串,但返回的是json格式的字符串时,更好的做法如下:这是json标准的响应头 resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(JSON.toJSONString(re)); writer.flush(); writer.close(); } }
重新部署,运行,访问:http://localhost:8080/day6/ajaxProd
可将页面显示的结果用json工具解析,百度json即可
③.前端
新建web.pages.showProd.html
访问服务接口,查出列表:
<html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" type="text/css" href="/day6/css/bootstrap.css"/> <script src="/day6/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="/day6/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> <style> .mydiv{ border: 1px solid gray; padding: 50px; width: 70%; height: 500px; margin: 50px auto; border-radius: 5%; } .mydiv img{ width:80px ; height:80px ; } </style> </head> <body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> </tr> <tbody id="mainData"> </tbody> </table> </div> </body> <script> /* * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 默认值1 * pageSize 每页显示的记录数 默认值3 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功"} * * */ //页面加载结束后,显示列表数据 //后台page和pageSize有默认值,不传参就走默认值。这里给空值 $.getJSON("/day6/ajaxProd","",function(data){ //发送Ajax请求,把想要的数据拉回来 console.log(data); //把数据遍历到页面中 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) }) </script> </html>
重新部署,访问:http://localhost:8080/day6/pages/showProd.html
页面显示:
接下来让页面数据动起来
加三个按钮去翻页,翻页实际是把页面的数据换一换,数据还是发请求去换。发送请求时,请求地址都一样,是同一个服务接口,只是发送的参数不同,参数都是page=xxx的形式。每次发送请求后要把原始数据清一下。
<body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> </tr> <tbody id="mainData"> </tbody> </table> <button class="btn btn-default pageBtn">1</button> <button class="btn btn-default pageBtn">2</button> <button class="btn btn-default pageBtn">3</button> </div> </body> <script> /* * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 默认值1 * pageSize 每页显示的记录数 默认值3 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功"} * * */ $.getJSON("/day6/ajaxProd","",function(data){ //发送Ajax请求,把想要的数据拉回来 console.log(data); //把数据遍历到页面中 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) }) $(".pageBtn").click(function () { $.getJSON("/day6/ajaxProd","page="+$(this).html(),function(data){ //发送Ajax请求,把想要的数据拉回来 console.log(data); $("#mainData").html(""); //清理数据 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) }) }) </script>
结果如下:
第二种让页面数据动起来的情况:放上界和下界
对于下一页按钮:
1.目前是1,则看到是2,目前是2,则看的是三,故要知道当前是哪一页,要翻转的页即当前页加一。
2.同时翻到最后一页,再往后翻就没有数据了,故应该根据响应码做出不同的处理。
3.并且不能翻的地方按钮应该禁用,到最后一页就不能翻了,故应该算出最后一页,计算方式是总记录数对每页显示的记录数取余,当结果为0时,总页数为总记录数对每页显示的记录数取商;当结果不为0时,总页数为总记录数对每页显示的记录数取商后加一。
总记录数sql:SELECT COUNT(1) as totalnum from tb_product
(已经写入规范,若查总记录数,count里用具体数字会比用*效率高一点。)
从数据库拿到总记录数,再算出总页数,又需要走业务逻辑。
④.回到后端
回到ProdDao
在dao里,要写成最小粒度的数据库操作,即dao里不要写具体的业务逻辑,查出来的数在service里计算。
public interface ProdDao { /* * 支持分页 * * 最小粒度的数据库操作 * */ List<Product> getAllProd(Integer page,Integer pageSize); //查总记录数 Integer getTotalNum(); }
在ProdDaoImpl里:
@Override public Integer getTotalNum() { Connection conn = DBHelper.getConn(); String sql = "select count(1) as totalnum from tb_product "; PreparedStatement preparedStatement = null; ResultSet resultSet = null; Integer totalNum = 0; System.out.println(sql); try { //防止注入攻击 preparedStatement = conn.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ totalNum = resultSet.getInt("totalnum"); } } catch (SQLException e) { e.printStackTrace(); }finally { DBHelper.CloseConn(conn,null,preparedStatement,resultSet); } return totalNum; }
在MyTest
每个dao写好都得测,不管简单还是复杂
public class MyTest { public static void main(String[] args) { ProdDao prodDao = new ProdDaoImpl(); Integer totalNum = prodDao.getTotalNum(); System.out.println(totalNum); } }
测试结果:select count(1) as totalnum from tb_product
7
在数据库再加一条记录再测一次,结果变成了8
在ProdService类:
public interface ProdService { List<Product> getAllProd(Integer page,Integer pageSize); Integer getTotalPage(Integer pageSize); }
在ProdServiceImpl:
@Override public Integer getTotalPage(Integer pageSize) { ProdDao pd = new ProdDaoImpl(); Integer totalPage = 0; Integer totalNum = pd.getTotalNum(); totalPage = totalNum%pageSize>0?totalNum/pageSize+1:totalNum/pageSize; return totalPage; }
在MyTest类:
public class MyTest { public static void main(String[] args) { ProdService ps = new ProdServiceImpl(); Integer totalPage = ps.getTotalPage(2); System.out.println(totalPage); } }
结果:select count(1) as totalnum from tb_product
4
换个几个pageSize再测几遍。
回到AjaxProdServlet
将总页数在服务接口返回去。同时在接口文档的retData将其变成键值对
* "retData": * totalpage: * listdata:[{"prodDesc":"测试手机","prodId":"SJ00150033", * "prodImg":"img/phone1.jpg","prodName":"测试手机 33","prodPrice":1033}],
如果这样,只在代码对应部分需加一个Map即可。
不过作为页码相关信息,只要查数据可能都可能用到,对于这种通用性更强的数据可以往上一级加。
在 entity.ReturnEntity中,以前只有retCode,retMsg,retData,现在可加totalPage
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class ReturnEntity { private Integer retCode; private String retMsg; private Object retData; private Integer totalPage; }
实际上,page,pageSize也可直接传给前台,
public class ReturnEntity { private Integer retCode; private String retMsg; private Object retData; private Integer totalPage; private Integer page; private Integer pageSize; }
对应的接口文档也增加相关信息。
实际上,这三个值都和页码相关,直接放一个对象将其封装。
新建entity.PageInfo
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class PageInfo { private Integer page; private Integer pageSize; private Integer totalPage; }
回到ReturnEntity:
public class ReturnEntity { private Integer retCode; private String retMsg; private Object retData; private PageInfo pif; }
回到接口文档:
* 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功", * "pageInfo":{"page":1,"pageSize":3,"totalPage":3}} * * */
在AjaxProdServlet代码上:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /* * 接收参数 * 转换格式 * 封装对象 * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 * pageSize 每页显示的记录数 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功", * "pageInfo":{"page":1,"pageSize":3,"totalPage":3}} * * */ req.setCharacterEncoding("utf-8"); String pageStr = req.getParameter("page"); String pageSizeStr = req.getParameter("pageSize"); Integer page = 1; Integer pageSize = 3; if(!"".equals(pageStr)&&pageStr!=null){ page = Integer.parseInt(pageStr); } if(!"".equals(pageSizeStr)&&pageSizeStr!=null){ pageSize = Integer.parseInt(pageSizeStr); } /* * 调用业务逻辑代码 * */ ProdService ps =new ProdServiceImpl(); List<Product> allProd = ps.getAllProd(page,pageSize); //返回的总页数(增加一处) Integer totalPage = ps.getTotalPage(pageSize); System.out.println(allProd); /* * 返回数据 * * */ ReturnEntity re = new ReturnEntity(); if(allProd.size()>0){ re.setRetCode(ReturnCode.QUERY_SUCCESS.getCode()); re.setRetMsg(ReturnCode.QUERY_SUCCESS.getMsg()); re.setRetData(allProd); //增加第二处 PageInfo pif = new PageInfo(page,pageSize,totalPage); re.setPif(pif); }else{ re.setRetCode(ReturnCode.QUERY_NODATA.getCode()); re.setRetMsg(ReturnCode.QUERY_NODATA.getMsg()); //增加第三处 PageInfo pif = new PageInfo(1,1,1); //没参数,则三个都给1 re.setPif(pif); } resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(JSON.toJSONString(re)); writer.flush(); writer.close(); }
重新部署,运行,访问 :http://localhost:8080/day6/pages/showProd.html,可在Network看到接收到的数据
⑤.回到前端
接口文档改变,前后端都会发生改变。
每次查询完数据,page,pageSize和totalPage三个值都会传过来。
<body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> </tr> <tbody id="mainData"> </tbody> </table> <button class="btn btn-default prevBtn">上一页</button> <button class="btn btn-default nextBtn">下一页</button> </div> </body> <script> /* * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 默认值1 * pageSize 每页显示的记录数 默认值3 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功"} * * */ var page ; var pageSize ; var totalPage ; $.getJSON("/day6/ajaxProd","",function(data){ //发送Ajax请求,把想要的数据拉回来 console.log(data); //把数据遍历到页面中 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) page = data.pif.page; pageSize = data.pif.pageSize; totalPage = data.pif.totalPage; }) $(".nextBtn").click(function () { $.getJSON("/day6/ajaxProd","page="+ (page+1),function(data){ //发送Ajax请求,把想要的数据拉回来 console.log(data); $("#mainData").html(""); //清理数据 if(data.retCode == 20000){ //查询成功 遍历数据 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) page = data.pif.page; pageSize = data.pif.pageSize; totalPage = data.pif.totalPage; }else if(data.retCode == 20001){ $("#mainData").html(data.retMsg ); } }) }) </script>
翻页都是访问同样的服务接口,操作都一样。上一页按钮同理:
$(".prevBtn").click(function () { $.getJSON("/day6/ajaxProd","page="+ (page-1),function(data){ console.log(data); $("#mainData").html(""); //清理数据 if(data.retCode == 20000){ //查询成功 遍历数据 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) page = data.pif.page; pageSize = data.pif.pageSize; totalPage = data.pif.totalPage; }else if(data.retCode == 20001){ $("#mainData").html(data.retMsg ); } }) })
初始页,下一页,上一页,请求地址相同,仅仅参数改变,可合并成一个函数,形参就是参数:
<script> /* * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 默认值1 * pageSize 每页显示的记录数 默认值3 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功"} * * */ var page ; var pageSize ; var totalPage ; queryData(""); $(".nextBtn").click(function () { var params = "page="+ (page+1); queryData(params); }) $(".prevBtn").click(function () { var params = "page="+ (page-1); queryData(params); }) function queryData(params){ //初始页,下一页,上一页仅仅参数改变 $.getJSON("/day6/ajaxProd",params,function(data){ console.log(data); $("#mainData").html(""); //清理数据 if(data.retCode == 20000){ //查询成功 遍历数据 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) page = data.pif.page; pageSize = data.pif.pageSize; totalPage = data.pif.totalPage; }else if(data.retCode == 20001){ $("#mainData").html(data.retMsg ); } }) } </script>
同时在页面应该显示当前多少页,总共多少页,每页显示多少条记录
<body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> </tr> <tbody id="mainData"> </tbody> </table> <button class="btn btn-default prevBtn">上一页</button> <button class="btn btn-default nextBtn">下一页</button> 当前第<span id="pageSpan"></span>页;总共<span id="totalPageSpan"></span>页;每页显示<span id="pageSizeSpan"></span> 条记录 </div> </body> <script> /* * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 默认值1 * pageSize 每页显示的记录数 默认值3 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功"} * * */ var page ; var pageSize ; var totalPage ; queryData(""); $(".nextBtn").click(function () { var params = "page="+ (page+1); queryData(params); }) $(".prevBtn").click(function () { var params = "page="+ (page-1); queryData(params); }) function queryData(params){ //初始页,下一页,上一页仅仅参数改变 $.getJSON("/day6/ajaxProd",params,function(data){ console.log(data); $("#mainData").html(""); //清理数据 if(data.retCode == 20000){ //查询成功 遍历数据 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) page = data.pif.page; pageSize = data.pif.pageSize; totalPage = data.pif.totalPage; $("#pageSpan").html(page); $("#pageSizeSpan").html(pageSize); $("#totalPageSpan").html(totalPage); }else if(data.retCode == 20001){ $("#mainData").html(data.retMsg ); } }) } </script>
接下来禁用按钮
<body> <div class="mydiv"> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> </tr> <tbody id="mainData"> </tbody> </table> <button class="btn btn-default prevBtn">上一页</button> <button class="btn btn-default nextBtn">下一页</button> 当前第<span id="pageSpan"></span>页;总共<span id="totalPageSpan"></span>页;每页显示<span id="pageSizeSpan"></span> 条记录 </div> </body> <script> /* * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 默认值1 * pageSize 每页显示的记录数 默认值3 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功"} * * */ var page ; var pageSize ; var totalPage ; queryData(""); $(".nextBtn").click(function () { var params = "page="+ (page+1); queryData(params); }) $(".prevBtn").click(function () { var params = "page="+ (page-1); queryData(params); }) function queryData(params){ //初始页,下一页,上一页仅仅参数改变 $.getJSON("/day6/ajaxProd",params,function(data){ console.log(data); $("#mainData").html(""); //清理数据 if(data.retCode == 20000){ //查询成功 遍历数据 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) page = data.pif.page; pageSize = data.pif.pageSize; totalPage = data.pif.totalPage; $("#pageSpan").html(page); $("#pageSizeSpan").html(pageSize); $("#totalPageSpan").html(totalPage); $(".prevBtn").prop("disabled",false); $(".nextBtn").prop("disabled",false); if(page==1){ $(".prevBtn").prop("disabled",true); } if(page==totalPage){ $(".nextBtn").prop("disabled",true); } }else if(data.retCode == 20001){ $("#mainData").html(data.retMsg ); } }) } </script>
结果如下:
先写主干,再一步步优化用户体验。写代码就是这样。除非同样的代码写过很多次。
2.带条件的分页查询
还是以商品表为例,sql语句:
SELECT * FROM tb_product tp where tp.prod_name LIKE '%测%' (一般名字模糊查询)
SELECT * FROM tb_product tp where tp.prod_id='SJ00150008 ' (一般id精确查询)
①.回到页面:
给用户准备了两个框,一个用来输入商品编号,一个用来输入商品名称
1.没条件的:两个框什么都不填,sql走:SELECT * FROM tb_product tp
2.一个条件的:只填一个,走以上sql的对应一个
3.两个条件的:两个都填,条件用and连接。
注:查不出来也是功能之一。
以上分析,如果不同条件走不同sql,代码重复量太多,这里用动态sql,即相同的地方不动,不同的地方根据不同条件拼接起来。动态sql也是dao层唯一会加的一种逻辑结构。
两个框的代码,用bootstrap代码美化:
<head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" type="text/css" href="/day6/css/bootstrap.css"/> <script src="/day6/js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> <script src="/day6/js/bootstrap.js" type="text/javascript" charset="utf-8"></script> <style> .mydiv{ border: 1px solid gray; padding: 50px; width: 70%; height: 800px; margin: 50px auto; border-radius: 5%; } .mydiv img{ width:80px ; height:80px ; } </style> </head> <body> <div class="mydiv"> <form class="form-inline"> <div class="form-group"> <label for="prodid">商品编号</label> <input type="text" class="form-control" id="prodid" placeholder="prodid"> </div> <div class="form-group"> <label for="prodname">商品名称</label> <input type="email" class="form-control" id="prodname" placeholder="prodname"> </div> <button id="queryBtn" type="button" class="btn btn-primary">查询</button> </form> <br/> <table class="table"> <tr> <th>商品编号</th> <th>商品信息</th> <th>单价</th> <th>描述信息</th> </tr> <tbody id="mainData"> </tbody> </table> <button class="btn btn-default prevBtn">上一页</button> <button class="btn btn-default nextBtn">下一页</button> 当前第 <span id="pageSpan"></span> 页;总共 <span id="totalPageSpan"></span> 页 每页显示 <span id="pageSizeSpan"></span> 条记录 </div> </body>
②.回到后台
ProdDao类:
查询条件属于商品对象,故不管条件多少,哪怕二十个,都封装一个商品进来即可。
public interface ProdDao { List<Product> getAllProd(Integer page,Integer pageSize,Product queryProd); Integer getTotalNum(); }
ProdDaoImpl类:
这里需要用到动态sql,对应的占位符赋值需要用到一个参数列表。最后将参数列表的参数对应赋值给占位符。
public class ProdDaoImpl implements ProdDao { @Override public List<Product> getAllProd(Integer page,Integer pageSize,Product queryProd) { Connection conn = DBHelper.getConn(); Boolean isWhere = true; //sql前后拼接时要给空格,防止两个字符拼到一起,导致sql无法运行 String sql = " select tp.prod_id,tp.prod_desc,tp.prod_name,tp.prod_price,tp.prod_img from tb_product tp "; List paramList = new ArrayList(); //参数列表,拼一个语句就放一个参数 //根据用户填入的查询参数,拼接查询语句 if(queryProd.getProdId()!=null && !"".equals(queryProd.getProdId())){ if(isWhere){ sql += " where "; //给空格 isWhere = false; }else{ sql += " and "; } sql += " tp.prod_id=? "; //给空格 paramList.add(queryProd.getProdId()); //如果走这个if,就会有where tp.prod_id=? //那么就给占位符赋值,所赋值就是接收到的参数,此参数是前端页面用户输入的 } if(queryProd.getProdName()!=null && !"".equals(queryProd.getProdName())){ if(isWhere){ sql += " where "; //给空格 isWhere = false; }else{ sql += " and "; } sql += " tp.prod_name LIKE ? "; //给空格 paramList.add("%"+queryProd.getProdName()+"%"); //用的模糊查询,要加% } sql += " LIMIT " +(page-1)*pageSize+" , "+pageSize; //给空格 PreparedStatement preparedStatement = null; ResultSet resultSet = null; List<Product> listProd = new ArrayList<Product>(); System.out.println(sql); try { preparedStatement = conn.prepareStatement(sql); for(int i=0;i<paramList.size();i++){ preparedStatement.setObject(i+1,paramList.get(i)); } resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ String prodId = resultSet.getString("prod_id"); String prodName = resultSet.getString("prod_name"); Double prodPrice = resultSet.getDouble("prod_price"); String prodImg = resultSet.getString("prod_img"); String prodDesc = resultSet.getString("prod_desc"); Product user = new Product(prodId,prodName,prodPrice,prodImg,prodDesc); listProd.add(user); } } catch (SQLException e) { e.printStackTrace(); }finally { DBHelper.CloseConn(conn,null,preparedStatement,resultSet); } return listProd; } @Override public Integer getTotalNum() { Connection conn = DBHelper.getConn(); String sql = "select count(1) as totalnum from tb_product "; PreparedStatement preparedStatement = null; ResultSet resultSet = null; Integer totalNum = 0; System.out.println(sql); try { //防止注入攻击 preparedStatement = conn.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ totalNum = resultSet.getInt("totalnum"); } } catch (SQLException e) { e.printStackTrace(); }finally { DBHelper.CloseConn(conn,null,preparedStatement,resultSet); } return totalNum; } }
在MyTest类:
public class MyTest { public static void main(String[] args) { ProdDao prodDao = new ProdDaoImpl(); Product queryProd = new Product(); prodDao.getAllProd(1,3,queryProd); } }
先测试空对象的:
select tp.prod_id,tp.prod_desc,tp.prod_name,tp.prod_price,tp.prod_img from tb_product tp LIMIT 0 , 3
[Product(prodId=FS0001452178, prodName=运动鞋, prodPrice=55.6, prodImg=img/shoe.jpg, prodDesc=只有一只, prodNum=null), Product(prodId=SJ00110013, prodName=便宜手机, prodPrice=95.3, prodImg=img/phone2.jpg, prodDesc=便宜手机, prodNum=null), Product(prodId=SJ00130333, prodName=测试手机12, prodPrice=100.0, prodImg=img/phone1.jpg, prodDesc=测试手机, prodNum=null)]
再测一个条件的:
public static void main(String[] args) { ProdDao prodDao = new ProdDaoImpl(); Product queryProd = new Product(); queryProd.setProdName("鞋"); List<Product> allProd = prodDao.getAllProd(1, 3, queryProd); System.out.println(allProd); }
结果:select tp.prod_id,tp.prod_desc,tp.prod_name,tp.prod_price,tp.prod_img from tb_product tp where tp.prod_name LIKE ? LIMIT 0 , 3
[Product(prodId=FS0001452178, prodName=运动鞋, prodPrice=55.6, prodImg=img/shoe.jpg, prodDesc=只有一只, prodNum=null)]
测两个条件的:略
测匹配不上的:略
改页数再测:略 ....
ProdService:
public interface ProdService { List<Product> getAllProd(Integer page,Integer pageSize,Product queryProd); Integer getTotalPage(Integer pageSize); }
ProdServiceImpl:
所谓的业务逻辑,一般比较常见的两种情况:
1.数据库里的数据内容与要显示的数据内容不同,需要计算/转换
2.数据库中的内容格式与页面展示的数据不同,需要转换
public class ProdServiceImpl implements ProdService { @Override public List<Product> getAllProd(Integer page,Integer pageSize,Product queryProduct) { ProdDao pd = new ProdDaoImpl(); return pd.getAllProd(page,pageSize,null); } @Override public Integer getTotalPage(Integer pageSize) { ProdDao pd = new ProdDaoImpl(); Integer totalPage = 0; Integer totalNum = pd.getTotalNum(); totalPage = totalNum%pageSize>0?totalNum/pageSize+1:totalNum/pageSize; return totalPage; } }
AjaxProdServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /* * 接收参数 * 转换格式 * 封装对象 * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 * pageSize 每页显示的记录数 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功", * "pageInfo":{"page":1,"pageSize":3,"totalPage":3}} * * */ req.setCharacterEncoding("utf-8"); String pageStr = req.getParameter("page"); String pageSizeStr = req.getParameter("pageSize"); String prodId = req.getParameter("prodid"); String prodName = req.getParameter("prodname"); Integer page = 1; Integer pageSize = 3; if(!"".equals(pageStr)&&pageStr!=null){ page = Integer.parseInt(pageStr); } if(!"".equals(pageSizeStr)&&pageSizeStr!=null){ pageSize = Integer.parseInt(pageSizeStr); } /* * 调用业务逻辑代码 * */ //查询使用的Product对象 Product queryProd = new Product(); queryProd.setProdId(prodId); queryProd.setProdName(prodName); ProdService ps =new ProdServiceImpl(); List<Product> allProd = ps.getAllProd(page,pageSize,queryProd); Integer totalPage = ps.getTotalPage(pageSize); System.out.println(allProd); /* * 返回数据 * * */ ReturnEntity re = new ReturnEntity(); if(allProd.size()>0){ re.setRetCode(ReturnCode.QUERY_SUCCESS.getCode()); re.setRetMsg(ReturnCode.QUERY_SUCCESS.getMsg()); re.setRetData(allProd); PageInfo pif = new PageInfo(page,pageSize,totalPage); re.setPif(pif); }else{ re.setRetCode(ReturnCode.QUERY_NODATA.getCode()); re.setRetMsg(ReturnCode.QUERY_NODATA.getMsg()); PageInfo pif = new PageInfo(1,1,1); //没参数,则三个都给1 re.setPif(pif); } resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(JSON.toJSONString(re)); writer.flush(); writer.close(); }
重新部署,运行,访问:http://localhost:8080/day6/ajaxProd,测试空的
访问:http://localhost:8080/day6/ajaxProd?prodid=SJ00150008,测试一个的
...
③.前端
接口文档改变,多了prodid商品编号和prodname商品名称
加了查询条件,总页数会发生改变。故,查询总记录数应该也相应的加where条件。
回到后端修改总页数的方法:
首先AjaxProdServlet里
调用service层方法时,也相应传入Product对象:
Integer totalPage = ps.getTotalPage(pageSize,queryProd);
对应的ProdService:
Integer getTotalPage(Integer pageSize,Product queryProd);
对应的ProdServiceImpl:
也把参数传入
@Override public Integer getTotalPage(Integer pageSize,Product queryProduct) { ProdDao pd = new ProdDaoImpl(); Integer totalPage = 0; Integer totalNum = pd.getTotalNum(queryProduct); totalPage = totalNum%pageSize>0?totalNum/pageSize+1:totalNum/pageSize; return totalPage; }
在ProdDao里:
Integer getTotalNum(Product queryProd);
在ProdDaoImpl里:
@Override public Integer getTotalNum(Product queryProd) { Connection conn = DBHelper.getConn(); String sql = "select count(1) as totalnum from tb_product tp "; //总记录数 需要是当前查询条件下的总记录数 Boolean isWhere = true; List paramList = new ArrayList(); if(queryProd.getProdId()!=null && !"".equals(queryProd.getProdId())){ if(isWhere){ sql += " where "; isWhere = false; }else{ sql += " and "; } sql += " tp.prod_id=? "; paramList.add(queryProd.getProdId()); } if(queryProd.getProdName()!=null && !"".equals(queryProd.getProdName())){ if(isWhere){ sql += " where "; isWhere = false; }else{ sql += " and "; } sql += " tp.prod_name LIKE ? "; paramList.add("%"+queryProd.getProdName()+"%"); } PreparedStatement preparedStatement = null; ResultSet resultSet = null; Integer totalNum = 0; System.out.println(sql); try { preparedStatement = conn.prepareStatement(sql); for(int i=0;i<paramList.size();i++){ preparedStatement.setObject(i+1,paramList.get(i)); } resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ totalNum = resultSet.getInt("totalnum"); } } catch (SQLException e) { e.printStackTrace(); }finally { DBHelper.CloseConn(conn,null,preparedStatement,resultSet); } return totalNum; }
在MyTest:
public class MyTest { public static void main(String[] args) { ProdDao prodDao = new ProdDaoImpl(); Product queryProd = new Product(); queryProd.setProdName("鞋"); //queryProd.setProdId("SJ00150008"); Integer totalNum = prodDao.getTotalNum(queryProd); System.out.println(totalNum); } }
结果:略
另一个问题:加入查询条件,在翻页的时候,又变成了没加查询条件的样子
所以翻页的时候要加入查询条件:
<script> /* * * 查询商品信息服务接口 * 请求地址 /day6/ajaxProd * 请求参数 page 页码 默认值1 * pageSize 每页显示的记录数 默认值3 * prodid 商品编号 * prodname 商品名称 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * 没有数据: * {"retCode":20001,"retMsg":"没有数据"} * 有数据: * {"retCode":20000, * "retData":[{"prodDesc":"测试手机","prodId":"SJ00150033","prodImg":"img/phone1.jpg","prodName":"测试手机33","prodPrice":1033}], * "retMsg":"查询成功"} * * */ var page ; var pageSize ; var totalPage ; //页面加载成功后 显示列表数据 queryData(""); //下一页 $(".nextBtn").click(function(){ //查询参数 var params = {"prodid":$("#prodid").val(),"prodname":$("#prodname").val(),"page":(page+1)}; queryData(params); }) //上一页 $(".prevBtn").click(function(){ //查询参数 var params = {"prodid":$("#prodid").val(),"prodname":$("#prodname").val(),"page":(page-1)}; queryData(params); }) $("#queryBtn").click(function(){ //查询参数 // var params = "prodid="+$("#prodid").val()+"&prodname="+$("#prodname").val(); var params = {"prodid":$("#prodid").val(),"prodname":$("#prodname").val()}; queryData(params); }) function queryData(params){ $.getJSON("/day6/ajaxProd",params,function(data){ console.log(data); $("#mainData").html(""); if(data.retCode == 20000){//查询成功 遍历数据 //把数据遍历到页面中 $.each(data.retData,function(i,d){ var content = '<tr>' +'<td>'+d.prodId+'</td>' +'<td>'+d.prodName+'<br/><img src="/day6/'+d.prodImg+'"/></td>' +'<td>'+d.prodPrice+'</td>' +'<td>'+d.prodDesc+'</td>' +'</tr>'; $("#mainData").append(content); }) //根据后台查询的页码参数 显示当前页相关信息 page = data.pif.page; pageSize = data.pif.pageSize; totalPage = data.pif.totalPage; $("#pageSpan").html(page); $("#pageSizeSpan").html(pageSize); $("#totalPageSpan").html(totalPage); //根据返回的页码信息 控制按钮启用禁用 $(".prevBtn").prop("disabled",false); $(".nextBtn").prop("disabled",false); if(page==1){ $(".prevBtn").prop("disabled",true); } if(page==totalPage){ $(".nextBtn").prop("disabled",true); } }else if(data.retCode == 20001){ $("#mainData").html(data.retMsg); } }) } </script>
3.要求
自己独立写出以上代码