【前言】
在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的方式来完成分页,在拼接时,要考虑是否不易出错,程序健壮性如何等问题。