简单实现一个单独的分页和多条件查询,主要是运用在ssm框架(基础的三层架构)的分页和多条件查询,使用的ajax异步请求。由于有些代码是我已经封装好了的,我会尽量给全代码,如果有看不懂的或者不理解可以评论。
数据库准备
首先准备数据库,一张表内含多个字段多条数据
搭建一个Java项目
项目结构如下:
创建bean包,实体类
public class Book {
private Integer id;
private String name;
private String author;
private Double price;
private Integer sales;
private Integer stock;
private String imgPath = "static/img/default.jpg";
public Book() {
}
public Book(Integer id, String name, String author, Double price, Integer sales, Integer stock, String imgPath) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
this.sales = sales;
this.stock = stock;
this.imgPath = imgPath;
}
/**
* 获取
* @return id
*/
public Integer getId() {
return id;
}
/**
* 设置
* @param id
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return author
*/
public String getAuthor() {
return author;
}
/**
* 设置
* @param author
*/
public void setAuthor(String author) {
this.author = author;
}
/**
* 获取
* @return price
*/
public Double getPrice() {
return price;
}
/**
* 设置
* @param price
*/
public void setPrice(Double price) {
this.price = price;
}
/**
* 获取
* @return sales
*/
public Integer getSales() {
return sales;
}
/**
* 设置
* @param sales
*/
public void setSales(Integer sales) {
this.sales = sales;
}
/**
* 获取
* @return stock
*/
public Integer getStock() {
return stock;
}
/**
* 设置
* @param stock
*/
public void setStock(Integer stock) {
this.stock = stock;
}
/**
* 获取
* @return imgPath
*/
public String getImgPath() {
return imgPath;
}
/**
* 设置
* @param imgPath
*/
public void setImgPath(String imgPath) {
this.imgPath = imgPath;
}
public String toString() {
return "Book{id = " + id + ", name = " + name + ", author = " + author + ", price = " + price + ", sales = " + sales + ", stock = " + stock + ", imgPath = " + imgPath + "}";
}
}
前端页面
.book_cond标签所在盒子是一个表单提交框,要将查询条件传回后端,只不过此次使用的是ajax技术,所以会将数据获取到script再由ajax异步发起请求
.b_list标签所在盒子是一个模板数据,之后会对他进行克隆,展示从数据库查询到的数据。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Title</title>
<base th:href="@{/}">
<style>
img{
width: 150px;
}
.b_list{
margin: 45px;
display: inline-block;
}
</style>
</head>
<body>
<div id="book">
<div class="book_cond" style="margin-left: 0px; margin-top: 20px;">
<form action="" method="get">
书名:<input type="text" name="name">
作者:<input type="text" name="author">
价格:<input id="min" type="text" name="min" value=""> 元 -
<input id="max" type="text" name="max" value=""> 元
<input type="button" value="查询" id="searchId"/>
</form>
</div>
<!--这里将其隐藏是因为这条数据是不展示的,只是起一个模板作用,方便jquery克隆 -->
<div class="b_list" style="display: none">
<div class="img_div">
<img class="book_img" alt="" src="img/default.jpg" />
</div>
<div class="book_info" style="width: 150px">
<div class="book_name">
<span class="sp1">书名:</span>
<span class="sp2">时间简史</span>
</div>
<div class="book_author">
<span class="sp1">作者:</span>
<span class="sp2">霍金</span>
</div>
<div class="book_price">
<span class="sp1">价格:</span>
<span class="sp2">¥30.00</span>
</div>
<div class="book_sales">
<span class="sp1">销量:</span>
<span class="sp2">230</span>
</div>
<div class="book_amount">
<span class="sp1">库存:</span>
<span class="sp2">1000</span>
</div>
<div class="book_add">
<button>加入购物车</button>
</div>
</div>
</div>
<div id="page_nav" style="margin-left: 200px">
<a href="javascript:void(0)">首页</a>
<a href="javascript:void(0)">上一页</a>
<a href="javascript:void(0)">下一页</a>
<a href="javascript:void(0)">末页</a>
共<span id="totalPageId">10</span>页,<span id="pageTotalCountId">30</span>条记录 到第<input value="4" name="pn" id="pn_input" style="width: 30px;"/>页
<input type="button" value="确定" id="jumpId">
</div>
</div>
</body>
</html>
我们是先将数据查询出来,ajax展示在页面上
javascript代码如下,现在还没有编写上下页功能,稍后等功能接近完整再来完善这一段代码
我们使用的是.post()异步请求BookServlet类,在这个servlet类中会接受form表单提交的参数,然后将查询出来的数据以json形式写到前端,也就是function (data){,data就是后端发送的数据。那么得到这些数据,就可以开始clone网页中的模板盒子,一个for循环,将所有Book对象数据写到div。有些参数看不懂没关系,因为后端还没讲解,后端会有一个Page工具类。
<script src="script/jquery-1.7.2.js"></script>
<script>
var myPageNo; //全局变量 P当前页
var myPageTotal; // 全局变量 最大页
$(function (){
mypage(1)
})
function mypage(pageNo){
//在查询框获取到的数据,将其表单序列化serialize()
// serialize()可以把表单中所有表单项的内容都获取到,并以name=value&name=value 的形式进行拼接。
var formParam = $("#book").find("form").serialize();
//将所有内容通过ajax传给BookServlet
$.post("BookServlet?method=searchPage",formParam+"&pageNo="+pageNo,function (data){
$("#book").find(".b_list:gt(0)").remove();
myPageNo = data.pageNo;
myPageTotal = data.pageTotal;
$("#totalPageId").html(myPageTotal)
$("#pageTotalCountId").html(data.pageTotalCount)
$("#pn_input").val(myPageNo)
for (let i = 0; i < data.items.length; i++) {
var newdiv = $("#book").find(".b_list").first().clone(); //克隆那个隐藏的
newdiv.css("display", "");
newdiv.find(".book_name").find(".sp2").html(data.items[i].name)
newdiv.find(".book_author").find(".sp2").html(data.items[i].author)
newdiv.find(".book_price").find(".sp2").html(data.items[i].price)
newdiv.find(".book_sales").find(".sp2").html(data.items[i].sales)
newdiv.find(".book_amount").find(".sp2").html(data.items[i].stock)
newdiv.find(".book_img").attr("src", "/bookimg/"+data.items[i].imgPath)
$("#book").append(newdiv)
}
})
}
</script>
编写servlet类
上面有写到ajax请求的是BookServlet类,所以现在开始编写。其作用是接收客户端发送过来的请求,并响应数据给客户端。这个servlet类,大家可以根据自己想法写,我这边有封装工具类,方便做项目,但是不适合没有项目经验的人观看,所以这边用最基础的service方法。
@WebServlet("/BookServletP")
public class BookServletP extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
String author = req.getParameter("author");
String min = req.getParameter("min");
String max = req.getParameter("max");
// pageNo当前页码
String pageNo = req.getParameter("pageNo");
// 当前页显示数量
String pageSize = req.getParameter("pageSize");
// 类型转换
int myPageNo = 1;
if (pageNo!="" && pageNo!=null){
myPageNo = Integer.parseInt(pageNo);
}
int myPageSize = 4;
if (pageSize!="" && pageSize!=null){
myPageSize = Integer.parseInt(pageSize);
}
double mymin = -1;
if (min!="" && min!=null){
mymin = Double.parseDouble(min);
}
double mymax = -1;
if (max!="" && max!=null){
mymax = Double.parseDouble(max);
}
BookDao bookDao = new BookDaoImpl();
Page<Book> bookPage = bookDao.searchPage(name, author, mymin, mymax, myPageNo, myPageSize);
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Gson gson = new Gson();
String s = gson.toJson(bookPage);
out.write(s);
}
}
因为getParameter()方法得到的都是字符串类型的,所以要进行类型转换,判断是不是空,空的话赋一个默认值就可以了。然后实例化一个BookDaoImpl对象,调用方法查询出符合多个条件的数据。再将数据通过gson传给前端。
创建dao层
我们这是将分页+多条件查询的功能单拎出来,所以dao层service层我都在一起写了,也避免层次结构太复杂,反而绕晕大家。
dao层首先是一个接口类BookDao,里面定义了一个方法searchPage()
public interface BookDao {
public Page<Book> searchPage(String name, String author, Double min, Double max, int myPageNo, int myPageSize);
}
然后BookDaoImpl类实现接口,方法就是将多条件查询的数据查询出来并返回Page实例,因为前端需要页码和总记录数。
public class BookDaoImpl extends BaseDao<Book> implements BookDao{
@Override
public Page<Book> searchPage(String name, String author, Double min, Double max, int myPageNo, int myPageSize) {
//1、首先求出多条件查询的数据总量
//2.定义一个list集合,用于存储sql查询的值,因为参数是不确定的,在前端有的值可能是空的,需要判断
List params = new ArrayList();
//3.定义一个字符串,存放查询数据记录数的sql语句 由于字符串会不停修改所以用StringBuilder类更好
// where 1 = 1 这么写是因为,条件可能会动态添加,保证sql语句不出错。
StringBuilder builder1 = new StringBuilder("select count(*) from t_book where 1 = 1");
//4.定于builder2,存放查询所有数据的sql语句
StringBuilder builder2 = new StringBuilder("select * from t_book where 1 = 1");
//5.判断各个参数的值是否有效
if (name != null && !name.equals("")){
//模糊查询,使用concat连接字符串... '%','?','%'
builder1.append(" and name like concat('%',?,'%')"); // 使用?占位符
builder2.append(" and name like concat('%',?,'%')");
params.add(name);
}
if (author != null && !author.equals("")){
builder1.append(" and author = ?");
builder2.append(" and author = ?");
params.add(author);
}
if ((min !=null && min != -1) && (max != null && max != -1)){
if (min > max){
//如果最小金额大于最大金额 那么进行两值交换
min = min+max;
max = min - max;
min = min - max;
}
params.add(min);
params.add(max);
builder1.append(" and price between ? and ?");
builder2.append(" and price between ? and ?");
}else if (min !=null && min != -1){
builder1.append(" and price > ?");
builder2.append(" and price > ?");
params.add(min);
}else if (max != null && max != -1){
builder1.append(" and price < ?");
builder2.append(" and price < ?");
params.add(max);
}
//总记录数
int totalCount = getValue(builder1.toString(), params.toArray());
Page<Book> page = Page.getPage(myPageNo, myPageSize, totalCount);
int begin = (page.getPageNo() - 1) * page.getPageSize();
builder2.append(" limit ? , ?");
params.add(begin);
params.add(page.getPageSize());
List<Book> bookList = getBeanList(builder2.toString(), params.toArray());
page.setItems(bookList);
return page;
}
}
这里一个重要的工具类就是Page。看懂就可以,其中一个重要的方法,getPage是一些逻辑计算总页码。在此方法是将page实例赋值然后再返回。
//分页工具类
public class Page<T> {
public static final Integer PAGE_SIZE = 4;
// 当前页码
private Integer pageNo;
// 总页码
private Integer pageTotal;
// 当前页显示数量
private Integer pageSize = PAGE_SIZE;
// 总记录数
private Integer pageTotalCount;
// 当前页数据
private List<T> items;
private String url;
public Page() {
}
public Page(Integer pageNo, Integer pageTotal, Integer pageSize, Integer pageTotalCount, List<T> items, String url) {
this.pageNo = pageNo;
this.pageTotal = pageTotal;
this.pageSize = pageSize;
this.pageTotalCount = pageTotalCount;
this.items = items;
this.url = url;
}
// public static <T> Page<T> getp(int i)
/**
* 将当前页码,当前页显示数量和显示总记录数赋值进Page对象并求出总页码,再将该对象返回
* @param pageNo 当前页码
* @param pageSize 当前页显示数量
* @param totalCount 总记录数
* @param <T> 方法泛型
* @return
*/
public static <T> Page<T> getPage(int pageNo, int pageSize, int totalCount){
Page<T> page = new Page<>();
//当前页显示数量
page.setPageSize(pageSize);
//当前页码
page.setPageNo(pageNo);
//总记录数
page.setPageTotalCount(totalCount);
//求出总页码
page.setPageTotal((totalCount+pageSize-1) / pageSize);
/*插入一段逻辑判断的代码,
我们判断一下当前页是否小于1,如果有小于1那么就设置成第一行
再次判断一下当前页是否大于最大页,如果大于最大页,就设置成最大页
*/
if (page.getPageNo()<1){
page.setPageNo(1);
}
if (page.getPageNo()>page.getPageTotal()){
page.setPageNo(page.getPageTotal());//设置最大页为当前页
}
return page;
}
/**
* 获取
* @return pageNo
*/
public Integer getPageNo() {
return pageNo;
}
/**
* 设置
* @param pageNo
*/
public void setPageNo(Integer pageNo) {
this.pageNo = pageNo;
}
/**
* 获取
* @return pageTotal
*/
public Integer getPageTotal() {
return pageTotal;
}
/**
* 设置
* @param pageTotal
*/
public void setPageTotal(Integer pageTotal) {
this.pageTotal = pageTotal;
}
/**
* 获取
* @return pageSize
*/
public Integer getPageSize() {
return pageSize;
}
/**
* 设置
* @param pageSize
*/
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
/**
* 获取
* @return pageTotalCount
*/
public Integer getPageTotalCount() {
return pageTotalCount;
}
/**
* 设置
* @param pageTotalCount
*/
public void setPageTotalCount(Integer pageTotalCount) {
this.pageTotalCount = pageTotalCount;
}
/**
* 获取
* @return items
*/
public List<T> getItems() {
return items;
}
/**
* 设置
* @param items
*/
public void setItems(List<T> items) {
this.items = items;
}
/**
* 获取
* @return url
*/
public String getUrl() {
return url;
}
/**
* 设置
* @param url
*/
public void setUrl(String url) {
this.url = url;
}
public String toString() {
return "Page{PAGE_SIZE = " + PAGE_SIZE + ", pageNo = " + pageNo + ", pageTotal = " + pageTotal + ", pageSize = " + pageSize + ", pageTotalCount = " + pageTotalCount + ", items = " + items + ", url = " + url + "}";
}
public static void generatePageNumbers(Page page, HttpServletRequest req){
// 分页模块中,页码展示+++++++++++++++++++++ 开始
//情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码
if (page.getPageTotal() <= 5){
req.setAttribute("begin",1);
req.setAttribute("end",page.getPageTotal());
}else{
//情况2:总页码>5
if (page.getPageNo()<=3){
req.setAttribute("begin",1);
req.setAttribute("end",5);
}else if (page.getPageNo()>page.getPageTotal()-3){
req.setAttribute("begin",page.getPageTotal()-4);
req.setAttribute("end",page.getPageTotal());
}else{
req.setAttribute("begin",page.getPageNo()-2);
req.setAttribute("end",page.getPageNo()+2);
}
}
// 分页模块中,页码展示+++++++++++++++++++++ 结束
}
}
那么到这里,这个功能的后端已经完成了。
现在可以继续前端的分页功能。
前端分页
在javascript代码里面添加一些逻辑,比如,定位当前页,我们要实现点击上一页下一页或首页展示不同数据,所以得给html中对应的a标签绑定点击事件。
完整javascript代码如下:
定义了一个新方法doPageEvents()作用对点击上一页下一页首页末页进行反应
在前端页面还有一个输入框,用于输入数字展示该页码对应的页面。只用绑定点击事件调用mypage()方法然后获取该框内的值就可以了。
到此,整个功能就已经完成
这是首页面,没有进行查询,展示的所有数据。
<script src="script/jquery-1.7.2.js"></script>
<script>
var myPageNo; //全局变量 P当前页
var myPageTotal; // 全局变量 最大页
$(function (){
mypage(1)
$('#searchId').click(function (){
//点击查询按钮时,调用分页方法
mypage(1);
})
$("#jumpId").click(function (){
mypage($("#pn_input").val())
})
})
function mypage(pageNo){
//在查询框获取到的数据,将其表单序列化serialize()
// serialize()可以把表单中所有表单项的内容都获取到,并以name=value&name=value 的形式进行拼接。
var formParam = $("#book").find("form").serialize();
//将所有内容通过ajax传给BookServlet
$.post("BookServlet?method=searchPage",formParam+"&pageNo="+pageNo,function (data){
$("#book").find(".b_list:gt(0)").remove();
myPageNo = data.pageNo;
myPageTotal = data.pageTotal;
$("#totalPageId").html(myPageTotal)
$("#pageTotalCountId").html(data.pageTotalCount)
$("#pn_input").val(myPageNo)
for (let i = 0; i < data.items.length; i++) {
var newdiv = $("#book").find(".b_list").first().clone(); //克隆那个隐藏的
newdiv.css("display", "");
newdiv.find(".book_name").find(".sp2").html(data.items[i].name)
newdiv.find(".book_author").find(".sp2").html(data.items[i].author)
newdiv.find(".book_price").find(".sp2").html(data.items[i].price)
newdiv.find(".book_sales").find(".sp2").html(data.items[i].sales)
newdiv.find(".book_amount").find(".sp2").html(data.items[i].stock)
newdiv.find(".book_img").attr("src", "/bookimg/"+data.items[i].imgPath)
$("#book").append(newdiv)
}
doPageEvents();
})
}
//为当前网页中的‘首页’‘上一页’‘末页’ 添加事件
function doPageEvents(){
var first = $("#page_nav a:contains('首页')");
var previous = $("#page_nav a:contains('上一页')");
var end = $("#page_nav a:contains('末页')");
var next = $("#page_nav a:contains('下一页')");
if(myPageNo <= 1){
first.unbind();
previous.unbind()
first.css({"color":"silver", "cursor":"default"});
previous.css({"color":"silver", "cursor":"default"})
}else{
first.css({"color":"", "cursor":"pointer"});
previous.css({"color":"", "cursor":"pointer"})
if (!(first.data('events') && first.data('events')['click'])){
first.click(function (){
mypage(1)
})
}
if (!(previous.data('events') && previous.data('events')['click'])){
previous.click(function (){
mypage(myPageNo-1 <=0 ? 1 : myPageNo-1)
})
}
}
//判断当前是否大于最大页
if (myPageNo >= myPageTotal){
//将'下一页'与'首页'失去点击事件
next.unbind()
end.unbind()
next.css("color", "silver");
end.css("color", "silver")
}else{
next.css("color", "");
end.css("color", "")
if (!(next.data('events') && next.data('events')['click'])){
next.click(function (){
mypage(myPageNo+1 >= myPageTotal ? myPageTotal : myPageNo + 1)
})
}
if (!(end.data('events') && end.data('events')['click'])){
end.click(function (){
mypage(myPageTotal)
})
}
}
}
</script>
查询后的如下:展示该条件下的所有数据。
需要源码的可以评论留言,无偿赠送和指导。因为本项目需要一些jar包,可后台私信。