背景
分页查询是非常常见的需求。所以有必要做成一个比较公用的组件,避免大家重复实现分页功能。
实现
分页组件分为前台部门和后台部分。这里先前台说起。
前台分页组件
因为我们的网站用的是bootstrap的样式,所以找了一个基于bootstrap的前台分页组件——bootstrap-paginator。使用非常简单,这里关键是用velocity的宏把它定义成一个组件:
#macro(showPaginator $page)
<div id="paginator"></div>
<form id="form-paginator" style="display:none;" method="POST">
<input type="hidden" id="pageIndex" name="pageIndex"/>
<input type="hidden" id="pageSize" name="pageSize"/>
</form>
<script type='text/javascript'>
var options = {
currentPage: $page.pageIndex,
totalPages: $page.pageCount,
numberOfPages: '$!{page.pageSize}' || 10,
size: "small",
alignment: "right",
itemContainerClass: function (type, page, current) {
return (page === current) ? "active" : "pointer-cursor";
},
onPageClicked: function(e,originalEvent,type,page){
$('#pageIndex').val(page)
$('#pageSize').val('$!{page.pageSize}' || 10)
$("#form-paginator").submit()
}
}
$('#paginator').bootstrapPaginator(options);
// 增加totalRecord信息
$("#paginator>ul").append('<li><a href="javascript:void(0);">共$page.pageCount页, $page.recordCount条记录</a></li>');
</script>
#end
然后在需要分页的页面直接这样使用就可以了:
<script src="$request.getContextPath()/resources/bootstrap/js/bootstrap-paginator.min.js"></script>
#if($newsList.isNotEmpty())
#foreach($news in $newsList.getRecords())
#parse("partial/news.vm")
#end
#showPaginator($newsList)
#end
后端分页组件
后端也蛮简单,不过类比较多,一个个说起。
首先是我们的分页结果类:
package me.arganzheng.study.pagination.common;
import java.util.Collections;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import me.arganzheng.study.pagination.criteria.PagingCriteria;
public class Page<T> {
private int recordCount = 0;
private int pageIndex = 1;
private int pageCount = 0;
private int pageSize = 10;
private List<T> records = Collections.emptyList();
@SuppressWarnings("rawtypes")
private static final Page emptyPage = new Page();
public boolean isNotEmpty() {
return recordCount > 0;
}
public int getRecordCount() {
return recordCount;
}
public boolean isMultiplePages() {
return pageCount > 1;
}
public boolean isNotLastPage() {
return pageIndex != pageCount;
}
public boolean isNotFirstPage() {
return pageIndex != 1;
}
public int getPageIndex() {
return pageIndex;
}
public int getPageCount() {
return pageCount;
}
public List<T> getRecords() {
return records;
}
public static <T> Page<T> createInstance(List<T> items, int recordCount, int pageIndex, int pageSize) {
if (pageSize < 1 || recordCount < 0 || pageIndex < 1) {
throw new IllegalArgumentException("Invalid page parameter: recordCount: " + recordCount + ", pageIndex:"
+ pageIndex + ", pageSize: " + pageSize);
}
int pageCount = calculatePageCount(recordCount, pageSize);
Page<T> page = new Page<T>();
if (CollectionUtils.isNotEmpty(items)) {
page.records = items;
}
page.recordCount = recordCount;
page.pageIndex = pageIndex;
page.pageCount = pageCount;
page.pageSize = pageSize;
return page;
}
public static <T1, T2> Page<T1> createInstanceFrom(Page<T2> templatePage, List<T1> items) {
Page<T1> page = new Page<T1>();
if (CollectionUtils.isNotEmpty(items)) {
page.records = items;
}
page.recordCount = templatePage.recordCount;
page.pageIndex = templatePage.pageIndex;
page.pageCount = templatePage.pageCount;
page.pageSize = templatePage.pageSize;
return page;
}
@SuppressWarnings("unchecked")
public static <T> Page<T> emptyPage() {
return (Page<T>) emptyPage;
}
public static int calculatePageCount(int recordCount, int pageSize) {
int result = recordCount / pageSize;
if (recordCount % pageSize != 0) {
result++;
}
return result;
}
public static <T> Page<T> createInstance(List<T> recordsInAPage, int recordCount, PagingCriteria pagingCriteria) {
return Page.createInstance(recordsInAPage, recordCount, pagingCriteria.getPageIndex(),
pagingCriteria.getPageSize());
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
然后是我们的分页查询类:
package me.arganzheng.study.pagination.criteria;
public class PagingCriteria {
public static final int DEFAUT_PAGE_SIZE = 10;
private int pageIndex = 1;
private int pageSize = DEFAUT_PAGE_SIZE;
public PagingCriteria(){
}
public PagingCriteria(int pageIndex, int pageSize){
this.setPageIndex(pageIndex);
this.setPageSize(pageSize);
}
public PagingCriteria(Integer pageIndex, Integer pageSize){
if (pageSize == null || pageSize <= 0) {
pageSize = DEFAUT_PAGE_SIZE;
}
if (pageIndex == null || pageIndex <= 0) {
pageIndex = 1;
}
this.setPageIndex(pageIndex);
this.setPageSize(pageSize);
}
public PagingCriteria(int pageIndex){
this.setPageIndex(pageIndex);
}
public int getFromRecord() {
return pageSize * (pageIndex - 1);
}
public int getPageIndex() {
return pageIndex;
}
public void setPageIndex(int pageIndex) {
if (pageIndex < 1) {
throw new IllegalArgumentException("Invalid pageIndex: " + pageIndex);
}
this.pageIndex = pageIndex;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
if (pageSize < 1 || pageSize > 20) {
throw new IllegalArgumentException("Invalid pageSize: " + pageSize);
}
this.pageSize = pageSize;
}
public static PagingCriteria createFirstPageCriteria(PagingCriteria prototype) {
PagingCriteria result = new PagingCriteria(1, prototype.getPageSize());
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + pageIndex;
result = prime * result + pageSize;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
PagingCriteria other = (PagingCriteria) obj;
if (pageIndex != other.pageIndex) return false;
if (pageSize != other.pageSize) return false;
return true;
}
@Override
public String toString() {
return "PagingCriteria [pageIndex=" + pageIndex + ", pageSize=" + pageSize + "]";
}
}
具体的业务分页查询类需要继承这个类:
package me.arganzheng.study.pagination.news.criteria;
import java.util.Date;
import me.arganzheng.study.pagination.PagingCriteria;
/**
* 新闻分页查询对象
*
* @author arganzheng
*
*/
public class NewsPagingCriteria extends PagingCriteria {
private String id;
private Date addTime;
private String language;
private String category;
public Date getAddTime() {
return addTime;
}
public NewsPagingCriteria setAddTime(Date addTime) {
this.addTime = addTime;
return this;
}
public String getLanguage() {
return language;
}
public NewsPagingCriteria setLanguage(String language) {
this.language = language;
return this;
}
public String getCategory() {
return category;
}
public NewsPagingCriteria setCategory(String category) {
this.category = category;
return this;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
说明 我们这里没有偷懒让PagingCriteria直接extends HashMap,而是要求每个具体因为类都要继承PagingCriteria定义查询条件,目的是这样可以一看这个查询类就知道支持哪些条件查询。
最后是我们的基于MyBatis的一个BaseDao:
package me.arganzheng.study.pagination.dao;
import java.util.List;
import me.arganzheng.study.pagination.common.Page;
import me.arganzheng.study.pagination.criteria.PagingCriteria;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 分页、批量操作在这里进行。
*
* @author arganzheng
*/
public abstract class BaseDao {
protected Logger logger = Logger.getLogger(getClass());
@Autowired
protected SqlSession sqlSession;
public static final int MAX_BATCH_SIZE = 10000;
/**
* @param countStatementName
* @param queryStatementName
* @param query
* @return 分页结果
*/
protected Page queryForPagination(String queryStatementName, String countStatementName,
PagingCriteria pagingCriteria) {
Integer totalCount = sqlSession.selectOne(countStatementName, pagingCriteria);
if (totalCount != null && totalCount.intValue() > 0) {
List items = sqlSession.selectList(queryStatementName, pagingCriteria);
if (items != null && !items.isEmpty()) {
return Page.createInstance(items, totalCount, pagingCriteria);
}
}
return Page.emptyPage();
}
/**
* <pre>
* 返回Mapper。如:
*
* BlogMapper mapper = session.getMapper(BlogMapper.class);
* Blog blog = mapper.selectBlog(101);
* </pre>
*
* @param clazz
* @return
*/
protected <T> T getMapper(Class<T> clazz) {
return sqlSession.getMapper(clazz);
}
}
然后是MyBatis的XML映射文件,定义一个CommonsMapper.xml,定义分页SQL语句:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="me.arganzheng.study.pagination.commons">
<sql id="Pagination">
limit #{fromRecord}, #{pageSize}
</sql>
</mapper>
这样后台如果要使用分页功能,只需要这样定义:
package me.arganzheng.study.pagination.dao;
import java.util.List;
import me.arganzheng.study.pagination.common.Page;
import me.arganzheng.study.pagination.criteria.NewsPagingCriteria;
import org.springframework.stereotype.Repository;
/**
* @author arganzheng
*/
@Repository
public class NewsDao extends BaseDao {
@SuppressWarnings("unchecked")
public Page<News> listMyBookOwnership(NewsPagingCriteria pagingCriteria) {
return queryForPagination("me.arganzheng.study.pagination.mapper.NewsMapper.listNews",
"me.arganzheng.study.pagination.mapper.NewsMapper.countNews",
pagingCriteria);
}
}
然后在具体的Mapper中定义相应的select语句和count语句:
<select id="queryNews"
parameterType="me.arganzheng.study.pagination.criteria.NewsPagingCriteria"
resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from
<include refid="News_Table_Name" />
where 1=1
<choose>
<when test="id != null and id != '' ">
and id=#{id}
</when>
<otherwise>
<if test="language != null and language != '' ">
and language=#{language}
</if>
<if test="category != null and category != '' ">
and category=#{category}
</if>
<if test="addTime != null">
<![CDATA[
and add_time >= #{addTime}
]]>
</if>
</otherwise>
</choose>
order by add_time
desc
<include
refid="me.arganzheng.study.pagination.commons.Pagination" />
</select>
<select id="countNews"
parameterType="me.arganzheng.study.pagination.criteria.NewsPagingCriteria"
resultType="int">
select count(0)
from
<include refid="News_Table_Name" />
where 1=1
<choose>
<when test="id != null and id != '' ">
and id=#{id}
</when>
<otherwise>
<if test="language != null and language != '' ">
and language=#{language}
</if>
<if test="category != null and category != '' ">
and category=#{category}
</if>
<if test="addTime != null">
<![CDATA[
and add_time >= #{addTime}
]]>
</if>
</otherwise>
</choose>
order by add_time
desc
</select>