一·数据库的分页实现
看一下数据库里有多少记录:
select count(*) from tbl_student limit 0,3;
mysql分页是通过limit,实现的:
从第0条开始取3条。从第三条开始取3条。
select stu_no,stu_name,stu_mark from tbl_student limit 0,3;
select stu_no,stu_name,stu_mark from tbl_student limit 3,3;
从第6条开始取3条:
二·页面
在utils包下新建类Page和PageTest,内容如下:
在utils包下新建的类Page,内容如下:
package edu.mju.stuwork.utils;
import java.util.Collection;
public class Page {
private Integer pageNo; //当前页号 *
private Integer pageSize; //每页记录条数 *
private Boolean nextPage; //是否有下一页
private Boolean prePage; //是否有上一页
private Long totalRecNum; //总共有多少条记录 (页面相关联的查询,总共有多少条记录)*
private Integer totalPageNum;//总共多少页
private Collection pageContent; //该页的数据(记录明细) *
private Integer startIndex; //记录开始位置
private Integer endIndex; //记录结束位置
public Page() {
super();
pageNo=1;
pageSize=3;
}
public Integer getPageNo() {
return pageNo;
}
public void setPageNo(Integer pageNo) {
this.pageNo = pageNo;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Boolean getNextPage() {
return pageNo<getTotalPageNum()?true:false;
}
public Boolean getPrePage() {
return pageNo>1?true:false;
}
public Long getTotalRecNum() {
return totalRecNum;
}
public void setTotalRecNum(Long totalRecNum) {
this.totalRecNum = totalRecNum;
}
public Integer getTotalPageNum() {
return totalRecNum%pageSize>0?(int)(totalRecNum/pageSize+1):(int)(totalRecNum/pageSize);
}
public Collection getPageContent() {
return pageContent;
}
public void setPageContent(Collection pageContent) {
this.pageContent = pageContent;
}
public int getStartIndex()
{
return pageSize*(pageNo-1); // size:10 pageno:3 21
}
public int getEndIndex()
{
return (pageSize*pageNo>this.totalRecNum)? (int)(this.totalRecNum.longValue()):(pageSize*pageNo);
}
@Override
public String toString() {
return "Page [pageNo=" + pageNo + ", pageSize=" + pageSize + ", nextPage=" + nextPage + ", prePage=" + prePage
+ ", totalRecNum=" + totalRecNum + ", totalPageNum=" + totalPageNum + ", pageContent=" + pageContent
+ ", startIndex=" + startIndex + ", endIndex=" + endIndex + "]";
}
}
在utils包下新建的类PageTest,内容如下:
package edu.mju.stuwork.utils;
public class PageTest {
/**
* @param args
*/
public static void main(String[] args) {
Page page=new Page();
page.setTotalRecNum(102L);
page.setPageSize(10);
System.out.println(page.getTotalPageNum());
page.setPageNo(11);
System.out.println(page.getStartIndex()+"-"+page.getEndIndex());
System.out.println(page.getNextPage());
System.out.println(page.getPrePage());
}
}
百度“福州”,结果是一个列表,是一个记录集。记录不一定以横条的形式出现,他可以以各种形式出现,
这就是一条记录了:
三个字段:图片,城市名称,以及说明。
有第一页第二页第三页,是一个页面。可以说是一个记录的显示列表,也可以说是一个页面对象,记录集只是页面的一个属性而已。Page类代表用户所看到的一切。
Page类的情况:
private Integer pageNo; //当前页号
private Integer pageSize; //每页记录条数 (页面规模)
有两个Boolen,分别判断有没有上一页,下一页:
private Boolean nextPage; //是否有下一页
private Boolean prePage; //是否有上一页
这个页面所属的查询总共有多少条记录:
private Long totalRecNum; //总共有多少条记录 (页面相关联的查询,总共有多少条记录)
有多少页面:
private Integer totalPageNum;//总共多少页
这个页面的数据,就是我们的查询结果:
private Collection pageContent; //该页的数据(记录明细)
页面的开始结束范围:
private Integer startIndex; //记录开始位置
private Integer endIndex; //记录结束位置
mysql可能不需要开始和结束范围。
开始访问页面时,new一个Page时,默认值是访问第一页,每页的记录条数默认为3条:
public Page() {
super();
pageNo=1;
pageSize=3;
}
可以设置页号和获得页号:
public Integer getPageNo() {
return pageNo;
}
public void setPageNo(Integer pageNo) {
this.pageNo = pageNo;
}
设置页面规模和取得页面规模:
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
获得下一页:
如果当前的页号比总页数来的小,就一定有下一页。
public Boolean getNextPage() {
return pageNo<getTotalPageNum()?true:false;
}
有上一页吗?:
如果当前的页号大于1,那就有。否则没有:
public Boolean getPrePage() {
return pageNo>1?true:false;
}
总共多少条记录,由外面传入:
public void setTotalRecNum(Long totalRecNum) {
this.totalRecNum = totalRecNum;
}
总共多少页:
把”当前的总记录条数“取模”页面规模“大于0,那么应该在整数除法的基础上加一,5条记录,每页3条,5%3>0,5/3要加一。6%3=0,6/3=2就不用。
public Integer getTotalPageNum() {
return totalRecNum%pageSize>0?(int)(totalRecNum/pageSize+1):(int)(totalRecNum/pageSize);
}
获得开始位置:
第一页就是0,第二页就是3.
public int getStartIndex()
{
return pageSize*(pageNo-1); // size:10 pageno:3 21
}
获得结束的位置:
如果页面规模乘以页号,比总的记录条数还多,说明有空余。返回总记录条数就可以了。如果不是,说明页面充满,返回页面规模乘以页号。
public int getEndIndex()
{
return (pageSize*pageNo>this.totalRecNum)? (int)(this.totalRecNum.longValue()):(pageSize*pageNo);
}
如何满足这个Page?只要四个属性有值,其他的属性都有值。
private Integer pageNo; //当前页号 *
private Integer pageSize; //每页记录条数 *
private Long totalRecNum; //总共有多少条记录 (页面相关联的查询,总共有多少条记录)*
private Collection pageContent; //该页的数据(记录明细) *
测试:
就是刚才的PageTest:
package edu.mju.stuwork.utils;
public class PageTest {
/**
* @param args
*/
public static void main(String[] args) {
// 新建一个页面
Page page=new Page();
// 设置总记录条数为102条,页面规模是10
page.setTotalRecNum(102L);
page.setPageSize(10);
// 输出总共有多少页
System.out.println(page.getTotalPageNum());
// 设置页号为11
page.setPageNo(11);
// 输出第11页的记录范围是多少
System.out.println(page.getStartIndex()+"-"+page.getEndIndex());
// 是否有上一页,下一页
System.out.println(page.getNextPage());
System.out.println(page.getPrePage());
}
}
Run As JavaApplication
102条记录,每页10条,总共11页。设置为第11页的话,记录范围是100-102.还有上一页吗?有。还有下一页吗?没有。
根据前面所说,我们需要4个数据才能把Page准备好。pageNo和pageSize是默认就有了。totalRecNum和pageContent需要我们手动获取。
三·获取页面所需数据
1·统计有多少条记录
来到StudentDao:
根据这个条件,告诉我有多少条记录。
添加:
long cntStusByCondition(StudentQryHelper helper);
来到StudentMapper书写映射:
totalCnt是别名
添加:
<select id="cntStusByCondition" parameterType="edu.mju.stuwork.service.StudentQryHelper" resultType="long">
select count(*) totalCnt
from tbl_student
<trim prefix="WHERE " prefixOverrides="AND |OR ">
<if test="qryStuName != null">
and stu_name like concat('%','${qryStuName}','%')
</if>
<if test="qryBeginMark != null">
and stu_mark >= #{qryBeginMark}
</if>
<if test="qryEndMark != null">
and stu_mark <= #{qryEndMark}
</if>
</trim>
</select>
回到Dao:
现在的条件不止一个,有三个。
添加:
List<Student> loadScopedStusByCondition(StudentQryHelper helper,int beginIdx, int pageSize);
回到StudentMapper:
如果这样写:
那么现在参数类型应该写什么呢?
如果写参数类型的话只有一个参数,但是我们有三个参数。如果参数类型继续写“edu.mju.stuwork.service.StudentQryHelper”,那前三个是可以的,那后面用蓝色框起来的两个应该怎么办呢?我们指定了一个参数类型,那么剩下还有两个参数没办法指定,因为一次只能指定一个参数类型。有一种做法是将三个参数分装到一个Map里,在Dao的方法中只传Map:
到时候就是aaa.begin这样。
但是可读性不够,
Mybatis提供了使用注解来完成的方法,
来到StudentDao,添加:
List<Student> loadScopedStusByCondition(@Param("helper") StudentQryHelper helper,
@Param("begin") int beginIdx,
@Param("size") int pageSize);
@Param("helper") StudentQryHelper helper
表示:”StudentQryHelper helper“这个参数在Mybatis映射文件中就叫helper。
上面的Param就是Mybatis的Param了:
在StudentMapper中修改“loadScopedStusByCondition”如下:
<select id="loadScopedStusByCondition" resultType="Student">
select *
from tbl_student
<trim prefix="WHERE " prefixOverrides="AND |OR ">
<if test="helper.qryStuName != null">
and stu_name like concat('%',#{helper.qryStuName},'%')
</if>
<if test="helper.qryBeginMark != null">
and stu_mark >= #{helper.qryBeginMark}
</if>
<if test="helper.qryEndMark != null">
and stu_mark <= #{helper.qryEndMark}
</if>
</trim>
order by stu_no desc
limit #{begin},#{size}
</select>
来到StudentService:
添加:
/**
* 获得一个基于某种查询的页面
* @param helper
* @param page
* @return
*/
Page loadPagedStus(StudentQryHelper helper, Page page);
helper是查询条件,page是初始页面,将初始页面传进去之后会把填充好数据的页面传出来。
来到StudentServiceImpl:
添加:
public Page loadPagedStus(StudentQryHelper helper, Page page) {//进来的page的“pageNo”和“pageSize”是有值的,现在我们要给他提供:1·在这样的查询条件下,有多少条记录,2·在这样的查询条件下,查询的结果是什么
page.setTotalRecNum(stuDao.cntStusByCondition(helper));
page.setPageContent(stuDao.loadScopedStusByCondition(helper, page.getStartIndex(), page.getPageSize()));
return page;
}
返回一个装满数据的Page。
来到StudentController:
修改:
原来:
现在:
@GetMapping("/students")
public String loadStus(Model model,StudentQryHelper helper,Page page) throws Exception {
// if (helper.getQryStuName()!=null && helper.getQryStuName().isEmpty()) {
// System.out.println("dfknkdfnkjdfnkd");
// }
if (org.apache.commons.lang3.StringUtils.isBlank(helper.getQryStuName())) {//使用lang3包的方法,做一个空串的检测
helper.setQryStuName(null);//使空串成为null,使下面的模糊查询不加上“%”,并使StudentMapper的动态sql语句不再添加相关部分的语句。
}
page= stuService.loadPagedStus(helper, page);
以key/value键值对形式保存到模型,其实就是保存到request范围(请求范围)
model.addAttribute("page", page);
model.addAttribute("helper",helper);
return "list_student";// 去到页面,叫list_student
}
来到list_student,
修改:
将原来stuList请求范围改为page.pageContent:
测试:
看一下SQL:
那第二页呢?
将网址改成http://localhost:8080/stuinfo/students?pageNo=2
:
如果写“?pageNo=2”,那学生控制器里的page就从默认值1,改成了2,在下面取值时自然就会算好:
来到mysql看看:
确实是应该是第二页的内容。
但是,这样要让用户输入网址,就不合理。
添加,翻页按钮,现在只显示了pageNo的一个属性pageNo,还有有没有上一页下一页,总共多少条记录,这些还没显示出来。
来到list_student:
添加:
<!-- 页面情况信息 -->
<div class="col-12 text-right"><!-- 占满一行,文字右对齐 -->
共${page.totalRecNum}条, 当前显示${page.startIndex+1}-${page.endIndex}条, 第${page.pageNo}/${page.totalPageNum}页
|<!-- 共有多少条记录,当前显示第几条到第几条。因为失从0开始,所以当前显示范围的开始加1。当前是第几页,总共多少页 -->
<c:if test="${page.pageNo>1}"><!-- jstl+EL 测试:如果page的页号大于一,就显示这个按钮 -->
<button class="btn btn-sm btn-outline-info" onclick="doQuery(1)">首页</button>
</c:if>
<c:if test="${page.prePage}"><!-- 如果page有上一页,就显示“上一页”按钮 -->
<button class="btn btn-sm btn-outline-info" onclick="doQuery(${page.pageNo-1})">上一页</button>
</c:if>
<c:if test="${page.nextPage}"><!-- 如果page有下一页,就显示“下一页”按钮 -->
<button class="btn btn-sm btn-outline-info" onclick="doQuery(${page.pageNo+1})">下一页</button>
</c:if>
<c:if test="${page.pageNo!=page.totalPageNum}"><!-- 如果page的页号不等于最后一页,就把末页的按钮放下去 -->
<button class="btn btn-sm btn-outline-info" onclick="doQuery(${page.totalPageNum})">末页</button>
</c:if>
|
到 <input type="text" class="text-center" id="pageNo" size=4 style="text-align:right;"/> 页
<button class="btn btn-sm btn-success" onclick="doQuery(parseInt($('#pageNo').val()));"> 跳 转 </button>
</div>
效果:
相当于把page的信息全部都取出来了。
所谓的分页是基于上面查询框的查询条件的分页,在查询框查找姓“张”的学生,然后点击下一页,就是查询姓“张”的同学的下一页。
当点击“下一页”时,在查询表单中偷偷添加条件pageNo=当前页号加一。然后通过js提交表单。
添加函数doQuery:当首页时是doQuery(1)
在list_student的script添加:
//查询第几页
function doQuery(pageNo){
if(pageNo<1 || pageNo>${page.totalPageNum})/* 页号小于一,或是页号大于总页数 */
{
alert('页号超出范围,有效范围:[1-${page.totalPageNum}]!');/* 提示页号超出有效范围,有效范围是多少 */
$('#pageNo').select();
return;
}
else
{
document.forms['stuQryFrm'].pageNo.value=''+pageNo;
document.forms['stuQryFrm'].submit();
}
}
给form一个name,用于定位:
测试:
报错:
添加隐含域:
<input type="hidden" name="pageNo" value="1"/>
测试:
可以添加nbsp,加点间隔:
取得上面pageNo的值,将其转成整数,传给doQuery:
输错了默认选中,方便修改: