【前言】
在BS中,分页技术的应用相当频繁。说到分页,简单的分页就很好实现了,如果在分页的基础上再加上业务逻辑,这就使得分页的技术更加的灵活了。
【简单分页】
我们先看一种简单的分页,为了做到复用,我们抽出分页公用的东西,即分页实体PageModel。
/**
* 封装分页信息
* @author Administrator
*
*/
public class PageModel<E> {
//结果集
private List<E> list;
//查询记录数
private int totalRecords;
//每页多少条数据
private int pageSize;
//第几页
private int pageNo;
/**
* 总页数
* @return
*/
public int getTotalPages() {
return (totalRecords + pageSize - 1) / pageSize;
}
/**
* 取得首页
* @return
*/
public int getTopPageNo() {
return 1;
}
/**
* 上一页
* @return
*/
public int getPreviousPageNo() {
if (pageNo <= 1) {
return 1;
}
return pageNo - 1;
}
/**
* 下一页
* @return
*/
public int getNextPageNo() {
if (pageNo >= getBottomPageNo()) {
return getBottomPageNo();
}
return pageNo + 1;
}
/**
* 取得尾页
* @return
*/
public int getBottomPageNo() {
return getTotalPages();
}
...
//get,set方法省略
}
在使用的时候,以查询用户集合为例
/**
* 分页查询
* @param pageNo 第几页
* @param pageSize 每页多少条数据
* @return pageModel
*/
public PageModel<User> findUserList(int pageNo, int pageSize) {
StringBuffer sbSql = new StringBuffer();
//查询的sql语句
sbSql.append("select user_id, user_name, password, contact_tel, email, create_date ")
.append("from ")
.append("( ")
.append("select rownum rn, user_id, user_name, password, contact_tel, email, create_date ")
.append("from ")
.append("( ")
.append("select user_id, user_name, password, contact_tel, email, create_date from t_user where user_id <> 'root' order by user_id ")
.append(") where rownum <= ? ")
.append(") where rn > ? ");
Connection conn = null;
PreparedStatement pstmt = null;
//结果集
ResultSet rs = null;
//定义用户分页实体
PageModel<User> pageModel = null;
try {
conn = DbUtil.getConnection();
pstmt = conn.prepareStatement(sbSql.toString());
//传入参数
pstmt.setInt(1, pageNo * pageSize);
pstmt.setInt(2, (pageNo - 1) * pageSize);
rs = pstmt.executeQuery();
List<User> userList = new ArrayList<User>();
//遍历,赋值
while (rs.next()) {
User user = new User();
user.setUserId(rs.getString("user_id"));
user.setUserName(rs.getString("user_name"));
user.setPassword(rs.getString("password"));
user.setContactTel(rs.getString("contact_tel"));
user.setEmail(rs.getString("email"));
user.setCreateDate(rs.getTimestamp("create_date"));
//添加到用户集合中
userList.add(user);
}
//构建用户分页实体,用于前台页面显示。
pageModel = new PageModel<User>();
pageModel.setList(userList);
pageModel.setTotalRecords(getTotalRecords(conn)); //总记录数
pageModel.setPageSize(pageSize);
pageModel.setPageNo(pageNo);
}catch(SQLException e) {
e.printStackTrace();
}finally {
DbUtil.close(rs);
DbUtil.close(pstmt);
DbUtil.close(conn);
}
return pageModel;
}
这里,我们只将pageNo(页号),pageSize(每页的条数)以及User(要分页的实体)作为参数传入就可以了。其他的属性,像TotalRecords(总记录数)等都可以计算得到。在sql语句中,我们限定了每次查询的数量,在前台显示的时候,直接将pageModel传到页面,然后获取其属性。
在第一种方法中,我们的需求比较简单:查询用户名不是root的所有用户,默认排序方式是按用户id排序。但当排序方式、排序的范围、每页显示条数等都不确定的情况下,这种分页方式就比较粗糙了。
【难度提升】
1、排序范围:分为两种——查询全部,查询部分。
2、排序条件:1:按最后更新时间排序,2:按主题发表时间排序,3:按回复数量排序
3、排序类型:升序,降序。
4、选择页码,展示相应的数据。
【分析】
1、pageModel 分页实体
我们仍将抽取分页实体PageBean,与第一种方法不同的是,为了满足单击页码查询的需求,新增了两个属性beginPageIndex和endPageIndex。
/**
* 分页中一页的信息
*
* @author YANG
*
*/
public class PageBean {
// 指定的或是页面参数
private int currentPage;
private int pageSize;
// 查询数据库
private List recordList;
private int recordCount;
// 需要计算
private int pageCount;
//开始
private int beginPageIndex;
//结束
private int endPageIndex;
//省去get,set
}
2、sql语句
因为查询条件数量和内容都不确定,因而sql语句绝对不能写死。
3、关于封装
1)sql语句可以进行封装。
如何解决:——抽象出Query类,将其where子句、orderby子句等进行封装。
/**
* 辅助拼接hql
*
* @author YANG
*
*/
public class QueryHelper {
private String fromClause;
private String whereClause = "";
private String orderByClause = "";
private List<Object> params = new ArrayList<Object>();
/**
* 生成from
*
* @param clazz
* @param alias
* 别名
*/
public QueryHelper(Class clazz, String alias) {
fromClause = " FROM " + clazz.getSimpleName() + " " + alias;
}
/**
* 拼接where子句
*
* @param condition
* @param param
*/
public QueryHelper addCondition(String condition, Object... param) {
if ("".equals(whereClause)) {
whereClause = " WHERE " + condition;
} else {
whereClause += " AND " + condition;
}
// 参数
if (param != null) {
for (Object p : param) {
params.add(p);
}
}
return this;
}
/**
* 第一个参数为true,拼接where子句
*
* @param append
* @param condition
* @param param
*/
public QueryHelper addCondition(boolean append, String condition, Object... param) {
if (append) {
addCondition(condition, param);
}
return this;
}
/**
* orderBy子句
*
* @param propertyName
* @param asc
* true 升序
*/
public QueryHelper addOrderByProperty(String propertyName, boolean asc) {
if ("".equals(orderByClause)) {
orderByClause = " ORDER BY " + propertyName + (asc ? "ASC" : "DESC");
} else {
orderByClause += ", " + propertyName + (asc ? "ASC" : "DESC");
}
return this;
}
/**
* 第一个参数为true,拼接orderby子句
*
* @param append
* @param propertyName
* @param asc
*/
public QueryHelper addOrderByProperty(boolean append, String propertyName,
boolean asc) {
if (append) {
addOrderByProperty(propertyName, asc);
}
return this;
}
/**
* 获取生成的用于查询数据列表的hql语句
*
* @return
*/
public String getListQueryHql() {
return fromClause + whereClause + orderByClause;
}
}
2)Page分页实体中的其他属性值
page中的参数可分为两类,一类是传入或给定的值,如pageSize、currentPage等;另一类是需要自动计算的属性,如beginPageIndex等。如果对需要回显页面的属性进行封装,既可避免向前台准备不必要的数据,也可防止遗漏。
如何解决:——在pageBean中利用构造函数完成
public PageBean(int currentPage, int pageSize, List recordList,
int recordCount) {
super();
this.currentPage = currentPage;
this.pageSize = pageSize;
this.recordList = recordList;
this.recordCount = recordCount;
pageCount = (recordCount + pageSize - 1) / pageSize;
// 计算beginPageIndex endPageIndex
if (pageCount > 5) {
// 总页数>5,显示分页
// 当前页附近共5个 前两个+后两个+本页
beginPageIndex=currentPage-2;
endPageIndex=currentPage+2;
// 前面页码不足2显示前5个
if(beginPageIndex<1){
beginPageIndex=1;
endPageIndex=5;
}
// 后面页码不足2,显示后5个
//TODO:bulijie
if(endPageIndex>pageCount){
beginPageIndex=pageCount-10+1;
endPageIndex=pageCount;
}
} else {
// 总页数<5
beginPageIndex = 1;
endPageIndex = pageCount;
}
}
……
具体业务中如何使用:
1)确定排序条件,即给QueryHelper传参。
new QueryHelper(Reply.class, "r")
// 查询范围
.addCondition("r.topic=?", topic)
//排序条件,按发表时间postTime
.addOrderByProperty( "r.postTime ", true)
.preparePageBean(replyService, pageNum, pageSize);
2)实现类中具体实现方法
public PageBean getPageBeanByParam(int pageNum, int pageSize, QueryHelper queryHelper) {
System.out.println("DaoSupportImpl.getPageBeanByParam()");
//获取参数列表,即查询条件
List<Object> params=queryHelper.getParams();
// 查询列表
Query query = getSession().createQuery(queryHelper.getListQueryHql());
//设定参数
if(params!=null){
for (int i = 0; i < params.size(); i++) {
query.setParameter(i,params.get(i));
}
}
query.setFirstResult((pageNum-1)*pageSize);
query.setMaxResults(pageSize);
List list=query.list();
// 总数量
Query countQuery=getSession()
.createQuery(queryHelper.getCountQueryHql());
if(params!=null){
for (int i = 0; i < params.size(); i++) {
countQuery.setParameter(i,params.get(i));
}
}
Long count = (Long)countQuery.uniqueResult();
//pageBean的构造函数方法
return new PageBean(pageNum, pageSize, list, count.intValue());
}
3)回显数据,最后页面的显示部分就很简单了,使用OGNL表达式或者EL表达式都可完成显示。
/**
* 查询分页信息,并放到栈顶
* @param service
* @param pageNum
* @param pageSize
*/
public void preparePageBean(DaoSupport<?> service,int pageNum,int pageSize){
PageBean pageBean = service.getPageBeanByParam(pageNum, pageSize,
this);
ActionContext.getContext().getValueStack().push(pageBean);
}
【小结】
首先,本文中的分页属于真分页。真分页的原则是显示什么查什么,而假分页是将所有的数据全部查出来,再前台控制数据的显示,这样性能会很差。在小编看来一般项目中,应用真分页的情况比较多一些。
在项目中,像分页、连接数据库、sql的增删改查等操作都可以考虑抽出一个工具类来做,其他业务上需要使用,直接拿来用就可以。以分页为例,在业务需求增多,查询难度加大的情况下,可采用拼接sql的方式来完成分页,在拼接时,要考虑是否不易出错,程序健壮性如何等问题。