Servlet查询分页逻辑分页
思路
- 前端页面发起请求,要展示一页数据。 把
当前要展示的页码
、每页展示条数
传给后台(currentPage
、pageSize
) - 去缓存中拿到该表所有数据,如果缓存中没有数据,就去数据库查询所有数据,然后将其放入缓存(拿到该表中的所有数据)
- 根据前端要求展示的页数和要展示的条数,从拿到的所有数据中切分出需要的部分数据(
data
) - 根据第2步中拿到的所有数据,可以很方便拿到总数据条数 (
totalCount
) - 根据第4步中的
totalCount
和第1步中前端传过来的pageSize
可以计算出 (totalPage
)
涉及到参数
和物理分页一样
逻辑分页的index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a href="physical?currentPage=1&pageSize=20">物理分页</a>
<br />
<a href="logic?currentPage=1&pageSize=20">逻辑分页</a>
</body>
</html>
web包下逻辑分页
创建LogicPageServlet这个servlet类
package com.lanou3g.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.lanou3g.bean.Paganation;
import com.lanou3g.bean.Student;
import com.lanou3g.dao.StudentDao;
@WebServlet("/logic")
public class LogicPageServlet extends HttpServlet {
StudentDao studentDao = new StudentDao();
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 默认展示第一页
int currentPage = 1;
try {
currentPage = Integer.parseInt(req.getParameter("currentPage"));
} catch (NumberFormatException e) {
}
// 默认每页展示20条
int pageSize = 20;
try {
pageSize = Integer.parseInt(req.getParameter("pageSize"));
} catch (NumberFormatException e) {
}
//将前端参数封装到分页对象中
Paganation<Student> page = new Paganation<>();
page.setCurrentPage(currentPage);
page.setPageSize(pageSize);
// 调用dao中的逻辑分页查询方法
studentDao.findByLogicPage(page);
// 将分好页的数据放入请求域中
req.setAttribute("page", page);
// 转发请求到学生列表页
req.getRequestDispatcher("student_list2.jsp").forward(req, resp);
}
}
可以看到servlet中的逻辑与物理分页是相同的,接着在写连接数据库的逻辑
在utils包中创建缓存类Cache类
package com.lanou3g.utils;
/**
* 缓存类
* @author 小江
*
*/
import java.util.List;
import com.lanou3g.bean.Student;
public class Cache {
/**
* student表缓存数据: 用于逻辑分页查询
*/
public static List<Student> studentCache = null;
/**
* 清理学生表缓存
*/
public static void flushStudentCache() {
studentCache = null;
}
}
在dao包中的studentDao中写逻辑分页
如下所示
package com.lanou3g.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import com.lanou3g.bean.Paganation;
import com.lanou3g.bean.Student;
import com.lanou3g.utils.Cache;
import com.lanou3g.utils.DBTools;
public class StudentDao {
/**
* 逻辑分页
* @param page
*/
public void findByLogicPage(Paganation<Student> page) {
// 1. 获取前端传过来的分页参数(pageSize,currentPage)
int currentPage = page.getCurrentPage();
int pageSize = page.getPageSize();
// 2.去缓存中拿到该表所有数据,如果缓存中没有数据,就去数据库查询所有数据,然后将其放入缓存(拿到该表中的所有数据)
List<Student> studentList = Cache.studentCache;
if(studentList == null) {
//初始化学生数据列表
studentList = new ArrayList<>();
//查询学生表所有数据
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
String sql = "select * from student";
try {
//获取连接
conn = DBTools.getConnection();
//创建Statement对象
stmt = conn.createStatement();
//执行查询
rs = stmt.executeQuery(sql);
System.out.println("execute sql: " + sql);
while(rs.next()) {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setSname(rs.getString("sname"));
student.setSubject(rs.getString("subject"));
student.setTel(rs.getString("tel"));
student.setAddr(rs.getString("addr"));
student.setCreatetime(rs.getTimestamp("createtime"));
studentList.add(student);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 将本次查询到的所有学生表数据放入缓存
Cache.studentCache = studentList;
}
}
// 3.根据前端要求展示的页数和要展示的条数,从拿到的所有数据中切分出需要的部分数据('data')
//假设pageSize = 10,总记录数 = 34
//当前页:1 从list中取的数据范围:[00~10]
//当前页:2 从list中取的数据范围:[10~20]
//当前页:3 从list中取的数据范围:[20~30]
// 当前页:currentPage 从list中取的数据范围: [(currentPage-1)*pageSize ~ currentPage*pageSize)
// 按照上面推出来的公式:第4页 从list中取的数据范围: [30 ~ 40)
int startIdx = (currentPage - 1) * pageSize;
// (7-1)*20, 7*20
// [120,140) 而总记录数是138
int endIdx = currentPage * pageSize;
// endIdx的值最大不能超过总记录条数
if(endIdx>studentList.size()) {
endIdx = studentList.size();
}
// 10 0,10 0,9
//取到当前页数据
List<Student> data = studentList.subList(startIdx, endIdx);
page.setData(data);
// 取到总数据条数(totalCount)
page.setTotalCount(studentList.size());
}
/**
* 物理分页
*
* @param page
* @throws SQLException
*/
public void findByPhysicalPage(Paganation<Student> page){
// 从page对象中拿出前端传过来的两个参数: 当前页、页面大小
Integer currentPage = page.getCurrentPage();
Integer pageSize = page.getPageSize();
// 后台需要往page中补充剩下的三个参数: 总记录条数、当前页的数据、总页数
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 拿总记录数(totalCount): select count(*) from student;
String sql = "select count(*) as total_count from student";
conn = DBTools.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
if(rs.next()) {
int totalCount = rs.getInt("total_count");
// 将总记录条数封装到page对象中
page.setTotalCount(totalCount);
}
rs.close();
pstmt.close();
// 第n页数据范围: (n-1)*pageSize+1 ~ n * pageSize
// MySQL写法: limit (n-1)*pageSize, pageSize
List<Student> studentList = new ArrayList<>();
sql = "select * from student limit ?,?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, (currentPage-1)*pageSize);
pstmt.setInt(2, pageSize);
rs = pstmt.executeQuery();
while(rs.next()) {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setSname(rs.getString("sname"));
student.setSubject(rs.getString("subject"));
student.setTel(rs.getString("tel"));
student.setAddr(rs.getString("addr"));
student.setCreatetime(rs.getTimestamp("createtime"));
studentList.add(student);
}
// 将当前页数据封装到page对象中
page.setData(studentList);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 逻辑分页,在数据发生添加、更新以后,需要清理缓存,让查询重新从数据库拿最新的数据
*
* @param stu
* @return
*/
public int addStudent(Student stu) {
// do add....
// 清理缓存
Cache.flushStudentCache();
return 0;
}
public int updateStudent(Student stu) {
// do update....
// 清理缓存
Cache.flushStudentCache();
return 0;
}
}
显示分页页面
前端页面跟物理页面大致一样,只有跳转的路径不同,student_list2.jsp中的代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>学生列表</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
margin: 100px auto;
}
td,th {
border: 1px solid #888;
padding: 3px 8px;
text-align: center;
}
.pageBar>a {
text-decoration: none;
border: 1px solid blue;
padding: 3px 5px;
margin-left: 10px;
color: white;
border-radius: 3px;
background-color: lightblue;
}
.pageBar>span {
border: 1px solid #999;
padding: 3px 5px;
margin-left: 10px;
color: white;
border-radius: 3px;
background-color: #ccc;
}
.pageBar {
border: none;
padding-top: 10px;
}
.pageBar>.text {
border: none;
}
</style>
</head>
<body>
<table>
<caption>学生信息表</caption>
<tr>
<th>学号</th>
<th>姓名</th>
<th>学科</th>
<th>电话</th>
<th>住址</th>
<th>入学时间</th>
</tr>
<c:forEach var="stu" items="${page.data }">
<tr>
<td>${stu.id }</td>
<td>${stu.sname }</td>
<td>${stu.subject }</td>
<td>${stu.tel }</td>
<td>${stu.addr }</td>
<td>${stu.createtime }</td>
</tr>
</c:forEach>
<tr>
<td colspan="6" class="pageBar">
<!-- 当前页大于1时,说明还有上一页,这时首页和上一页都可以点击 -->
<c:if test="${page.currentPage > 1 }">
<a href="physicalPageServlet?currentPage=1&pageSize=20">首页</a>
<a href="physical?currentPage=${page.currentPage-1 }&pageSize=20">上一页</a>
</c:if>
<!-- 当前页小于等于1时,说明已经是第一页了,这时首页和上一页都不能点击 -->
<c:if test="${page.currentPage <= 1 }">
<span>首页</span>
<span>上一页</span>
</c:if>
<span class="text">
当前页码: ${page.currentPage } / ${page.totalPage }
</span>
<!-- 当前页小于总页数时,说明还没到最后一页,这时尾页和下一页都可以点击 -->
<c:if test="${page.currentPage < page.totalPage }">
<a href="physical?currentPage=${page.currentPage+1 }&pageSize=20">下一页</a>
<a href="physical?currentPage=${page.totalPage }&pageSize=20">尾页</a>
</c:if>
<!-- 当前页大于等于总页数时,说明还已经到最后一页,这时尾页和下一页都不可以点击 -->
<c:if test="${page.currentPage >= page.totalPage }">
<span>下一页</span>
<span>尾页</span>
</c:if>
</td>
</tr>
</table>
</body>
</html>
结果就是如下了
项目首页
逻辑分页和物理分页对比
逻辑分页
优点:
- 不用多次查询数据库,可以有效减轻数据库的压力;
- 效率高,响应速度快
缺点:
- 只适用于少量数据的表,如果数据量过大会占用过多内存,或内存装不下
- 由于引入了缓存,当数据更新时,需要我们处理缓存同步的逻辑
物理分页
优点:
- 对数据量没有要求,不管大表小表都能胜任
- 直接通过数据库SQL语句完成分页,代码中的逻辑相对简单
- 由于每次都是实时查询数据库,所以不需要处理缓存同步问题
缺点:
- 由于每次都需要查询数据库,而且还是两条语句(当前页数据、总记录数),性能很低,响应相对较慢