Mybatis--分页查询

一、分页查询

分页查询则是在页面上将本来很多的数据分段显示,每页显示用户自定义的行数。可提高用户体验度,同时减少一次性加载,内存溢出风险。

1、真假分页

分页分为:真分页和假分页。

 假分页:一次性查询所有数据存入内存,翻页从内存中获取数据。优点:实现简单,性能高;缺

点:容易造成内存溢出。

 真分页:每次翻页从数据库中查询数据。优点:不容易造成内存溢出;缺点:实现复杂,性能相对低。

2、分页效果

 当前页的结果集数据,比如这一页有哪些商品信息。

 分页条信息,比如包含【首页】【上页】【下页】【末页】等。

二、分页设计

从上面请求访问的分页演示的效果图分析分页是如何设计?

1、分页需传递的参数

需要用户传入的参数:

  currentPage:当前页,跳转到第几页,int 类型,设置默认值,比如 1

  pageSize:每页最多多少条数据,int 类型,设置默认值,比如 10

2、分页需展示的数据

从分页效果图中可以看出,分页需要依赖的数据:

当前页货品信息: data/list

 分页条信息:

beginPage

首页

prevPage

上一页

nextPage

下一页

totalPage

总页数/末页

totalCount/rows: 总条数

currentPage

当前页

pageSize

每页显示多少条数据

3、分页需展示的数据的来源

来源于用户传入:

currentPage

当前页,int 类型

pageSize

每页显示多少条数据,int 类型

来源于两条 SQL 查询:

totalCount/rows: 数据总条数,int 类型

data/list

每一页的结果集数据,List 类型

来源于程序计算:

totalPage

总页数/末页,int 类型

prevPage

上一页,int 类型

nextPage

下一页,int 类型

3.1、结果总数与结果集

结果总数(totalCount/rows)和结果集(data/list)是来源于两条 SQL(必须掌)的查询:

第一条 SQL:查询符合条件的结果总数(totalCount/rows

 第二条 SQL:查询符合条件的结果集(data/list

  • 第一个 ?:从哪一个索引的数据开始查询(默认从 0 开始)
  • 第二个 ?:查询多少条数据

SELECT * FROM 表名 [WHERE 条件] LIMIT ?, ?

接下来分析第二条 SQL 中两个 ? 取值来源:

假设 product 表中有 21 条数据,每页分 5 条数据:

查询第一页数据:SELECT * FROM product LIMIT 0, 5

查询第二页数据:SELECT * FROM product LIMIT 5, 5

查询第三页数据:SELECT * FROM product LIMIT 10, 5

查询第四页数据:SELECT * FROM product LIMIT 15, 5

通过寻找规律发现:第一个 ? 取值来源于 (currentPage - 1) * pageSize;第二个 ? 取值来源于pageSize,即都来源于用户传递的分页参数。

3.2、总页数、上一页和下一页

总页数、上一页和下一页都是来源于程序计算出来的。

int totalPage = rows % pageSize == 0 ? rows / pageSize : rows / pageSize + 1;

  • 优先计算

int prevPage = currentPage - 1 >= 1 ? currentPage - 1 : 1;

int nextPage = currentPage + 1 <= totalPage ? currentPage + 1 : totalPage;

三、分页查询实现

给产品增加分页查询的功能。

1、访问流程

2、分页数据封装

为了能在页面上显示上述的分页效果,那么我们就得在把页面上的每一个数据封装成到某个对象共享给JSP

2.1、为什么要封装

若不封装的话,会怎样,效果如下:

恶心:数据太分散,需要共享多个数据,不方便统一管理多个数据。

解决方案:把多个需要共享的数据,封装到一个对象,往后就只需要把数据封装到该对象,再共享该对象即可。

2.2、编写 PageResult.java

在之前 Web CRUD 项目的基础上增加新的代码即可。

/**

* 封装结果数据(某一页的数据)

*/

@Getter

public class PageResult<T> {

// 两个用户的输入

private int currentPage; private int pageSize;

// 当前页码

// 每页显示的条数

// 两条 SQL 语句执行的结果

private int totalCount; private List<T> data;

// 总条数

// 当前页结果集数据

// 三个程序计算的数据

private int prevPage;

private int nextPage;

private int totalPage;

// 上一页页码

// 下一页页码

// 总页数/末页页码

  • 分页数据通过下面构造期封装好,但思考一下这个构造器什么调用?

public PageResult(int currentPage, int pageSize, int totalCount, List<T> data) {

this.currentPage = currentPage;

this.pageSize = pageSize;

this.totalCount = totalCount;

this.data = data;

  • 计算三个数据

this.totalPage = totalCount % pageSize == 0 ? totalCount / pageSize :

totalCount / pageSize + 1;

this.prevPage = currentPage - 1 >= 1 ? currentPage - 1 : 1;

this.nextPage = currentPage + 1 <= this.totalPage ? currentPage + 1 :

this.totalPage;

}

}

3、持久层分页功能实现

分页其最终的所有功能需要依赖的 SQL 就两条:

 查询数据的总数(为了显示分页条信息)。

 查询当前页的数据。

所以我们就得给 DAO 增加两个方法,一个查询数据总量,一个查询当前页的数据。

3.1、修改 IProductDAO.java

给其增加两个方法,分别用于查询结果总数和结果集。

int queryForCount(int currentPage, int pageSize);

List<Product> queryForList(int currentPage, int pageSize);

上述这样写的话方法是有两个形参的,但是 MyBatis 提供的操作方法传入执行 SQL 任务的参数,注意只能是一个,而现在两个参数需要传递两个分页的实参。

解决方案

方案一:使用 Map 来封装需要传递的参数。

方案二:使用 JavaBean 来封装需要传递的参数。

3.2、编写 QueryObject.java

目前用来封装分页参数,并解决上面的问题。

@Setter

@Getter

/**

  • 封装分页查询需要的两个请求传入的分页参数

*/

public class QueryObject {

private int currentPage = 1; // 当前页码,要跳转到哪一页的页码(需要给默认值)

private int pageSize = 3; // 每页显示条数(需要给默认值)

}

3.3、再修改 IProductDAO.java

修改分页查询的两个方法,改方法形参为 QueryObject

int queryForCount(QueryObject qo);

List<Product> queryForList(QueryObject qo);

3.4、修改 ProductDAOImpl.java

@Override

public int queryForCount(QueryObject qo) { SqlSession session = MyBatisUtil.getSession(); int totalCount =

session.selectOne("cn.mapper.ProductMapper.queryForCount", qo); session.close();

return totalCount;

}

@Override

public List<Product> queryForList(QueryObject qo) { SqlSession session = MyBatisUtil.getSession(); List<Product> products =

session.selectList("cn.mapper.ProductMapper.queryForList",qo); session.close();

return products;

}

3.5、修改 ProductMapper.xml

<select id="queryForCount" resultType="int"> SELECT COUNT(*) FROM product

</select>

<select id="queryForList" resultType="cn.domain.Product"> SELECT * FROM product LIMIT #{start}, #{pageSize}

</select>

3.6、修改 QueryObjet.java

 QueryObject 增加 getStart 方法,返回之前找出规律算出从那个位置开始查询数据,代码如下:

@Setter

@Getter

/**

  • 封装分页查询需要的两个请求传入的分页参数

*/

public class QueryObject {

private int currentPage = 1; // 当前页码,要跳转到哪一页的页码(需要给默认值)

private int pageSize = 3; // 每页显示条数(需要给默认值)

  • 用于 Limit 子句第一个 ? 取值 public int getStart(){

return (currentPage - 1) * pageSize;

}

}

3.7、编写单元测试

给测试类 ProductDAOTest 中添加分页测试的方法,测试一下。

@Test

public void testQueryForCount(){

QueryObject qo = new QueryObject();

System.out.println(productDAO.queryForCount(qo));

}

@Test

public void testQueryForList(){

QueryObject qo= new QueryObject();

System.out.println(productDAO.queryForList(qo));

}

4、业务层分页功能实现

 Servlet 调用,返回分页查询的结果,即返回 PageResult 对象,而形参类型为 QueryObject

4.1、编写 IProductService.java

public interface IProductService {

/**

  • 完成查询某一页的业务逻辑功能

*/

PageResult<Product> query(QueryObject qo);

}

4.2、编写 ProductServiceImpl.java

public class ProductServiceImpl implements IProductService {

private IProductDAO productDAO = new ProductDAOImpl();

@Override

public PageResult<Product> query(QueryObject qo) {

  • 调用 DAO 查询数据数量

int totalCount = productDAO.queryForCount(qo);

  • 为了性能加入判断,若查询的数据数量为 0,说明没有数据,返回返回空集合,即集合中没有

元素

if(totalCount == 0){

return new PageResult(qo.getCurrentPage(), qo.getPageSize(), totalCount, Collections.emptyList());

}

  • 执行到这里代表有数据,查询当前页的结果数据

List<Product> products = productDAO.queryForList(qo);

return new PageResult(qo.getCurrentPage(), qo.getPageSize(), totalCount, products);

}

}

4.3、编写单元测试类

 test 目录下的 cn.wolfcode.service 包下新建 ProductServiceTest 测试类,测试上面 query 方法,观察控制台输出结果。

public class ProductServiceTest {

private IProductService productService = new ProductServiceImpl();

@Test

public void testQuery(){

QueryObject qo= new QueryObject();

qo.setCurrentPage(1);

PageResult<Product> pageResult = productService.query(qo);

System.out.println("结果集数据:" + pageResult.getData());

System.out.println("当前页总记录数:" + pageResult.getTotalCount());

System.out.println("条数:" + pageResult.getData().size());

System.out.println("总页数:" + pageResult.getTotalPage());

System.out.println("上一页:" + pageResult.getPrevPage());

System.out.println("下一页:" + pageResult.getNextPage());

}

}

5、前台分页功能实现

包含编写 Servlet  JSPServlet 处理请求,调用业务方法,把查询到数据共享到 JSP 中,展示给用户看。

5.1、操作步骤

 必须先完成业务层组件,保证后台测试通过

 遵循 MVC 思想。

 浏览器发出分页请求参数(去往第几页/每页多少条数据),在 Servlet 中接收这些参数,并封装

 QueryObject 对象,调用 Service 中分页查询方法(query)。

 把得到的分页查询结果对象(PageResult)共享在请求作用域中,跳转到 JSP,显示即可。

 修改 JSP 页面,编写出分页条信息(分页条中的信息来源于 PageResult 对象)。

5.2、修改 ProductServlet.java

ProductServlet,获取页面传递的分页参数,执行查询,将结果共享到请求作用域,请求转发回到 list.jsp 页面。

注意暂时注释掉其他报错代码,因为目前产品业务类中只提供分页查询的方法。

private IProductService productService = new ProductServiceImpl();

protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

QueryObject qo = new QueryObject();

  • 获取请求参数 currentPage,并转型封装

String currentPage = req.getParameter("currentPage"); if(StringUtil.hasLength(currentPage)) {

qo.setCurrentPage(Integer.valueOf(currentPage));

}

  • 获取请求参数 pageSize,并转型封装

String pageSize = req.getParameter("pageSize"); if(StringUtil.hasLength(pageSize)) {

qo.setPageSize(Integer.valueOf(pageSize));

}

  • 调用业务层方法来处理请求查询某一页数据

PageResult<Product> pageResult = productService.query(qo);

// 把数据共享给 list.jsp

req.setAttribute("pageResult", pageResult);

// 控制跳转到 list.jsp 页面

req.getRequestDispatcher("/WEB-INF/views/product/list.jsp").forward(req,

resp);

}

5.3、修改 list.jsp

在此 JSP 使用 JSTL + EL 获取后台共享到请求作用域中的数据,展示给用户看。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>产品列表</title>

<script type="text/javascript">

window.onload = function () {

var trClzs = document.getElementsByClassName("trClassName"); for(var i = 0; i < trClzs.length; i++){

trClzs[i].onmouseover = function () {

console.log(1);

this.style.backgroundColor = "gray";

}

trClzs[i].onmouseout = function () {

console.log(2);

this.style.backgroundColor = "";

}

}

}

  • 分页 JS

function changePageSize() {

document.forms[0].submit();}

</script>

</head>

<body>

<a href="/replaceImg.jsp"><img src="${USER_IN_SESSION.headImg}" title="更换头像"/></a><br/>

<a href="/product?cmd=input">添加</a>

<form action="/product">

<table border="1" cellspacing="0" cellpadding="0" width="80%"> <tr>

<th>编号</th>

<th>货品名</th>

<th>分类编号</th>

<th>零售价</th>

<th>供应商</th>

<th>品牌</th>

<th>折扣</th>

<th>进货价</th>

<th>操作</th>

</tr>

<c:forEach var="product" items="${pageResult.data}"

varStatus="status">

<tr class="trClassName">

<td>${status.count}</td>

<td>${product.productName}</td>

<td>${product.dir_id}</td>

<td>${product.salePrice}</td>

<td>${product.supplier}</td>

<td>${product.brand}</td>

<td>${product.cutoff}</td>

<td>${product.costPrice}</td>

<td>

<a href="/product?cmd=delete&id=${product.id}">删除</a> <a href="/product?cmd=input&id=${product.id}">修改</a>

</td>

</tr>

</c:forEach>

<tr align="center">

<td colspan="9">

<a href="/product?currentPage=1">首页</a>

<a href="/product?currentPage=${pageResult.prevPage}">上一页

</a>

<a href="/product?currentPage=${pageResult.nextPage}">下一页

</a>

<a href="/product?currentPage=${pageResult.totalPage}">尾页

</a>

当前第 ${pageResult.currentPage} / ${pageResult.totalPage} 页一共 ${pageResult.totalCount} 条数据

跳转到<input type="number" onchange="changePageSize()" name="currentPage" value="${pageResult.currentPage}" style="width: 60px;">

每页显示

<select name="pageSize" onchange="changePageSize()">

<option value="3" ${pageResult.pageSize == 3 ?

'selected' : ''}> 3 </option>

<option value="5" ${pageResult.pageSize == 5 ?

'selected' : ''}> 5 </option>

<option value="8" ${pageResult.pageSize == 8 ?

'selected' : ''}> 8 </option>

</select>条数据

</td>

</tr>

</table>

</form>

</body>

</html>

四、常见问题

疑惑理解

  currentPage,表示要显示某一页数据的页码,在程序中表示翻页要去往的页面,比如

curentPage = 3,表示要跳转到第 3 页。

 上一页,下一页,末页是在 PageResult 的构造器中已经计算好。

常见问题:若刚开始翻页操作成功,但是翻几页之后就不能翻页了,只能重启 Tomcat 才可以翻页,但是操作几次又不能翻页了。问题原因:在 DAO 中没有关闭 SqlSession 对象释放资源。

Servlet 代码优化

protected void list(...) {

req2qo(req, qo);

// ......

}

  • 获取请求参数封装成对象

private void req2qo(HttpServletRequest req, QueryObject qo) {

String currentPage = req.getParameter("currentPage"); String pageSize = req.getParameter("pageSize");

if(StringUtil.hasLength(currentPage)) {

qo.setCurrentPage(Integer.valueOf(currentPage));

}

if(StringUtil.hasLength(pageSize)) {

qo.setPageSize(Integer.valueOf(pageSize));

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值