`

通用分页组件JPage

阅读更多

部门成立不久, 还没有自己统一的分页规范, 因此借这个机会自己编写了一个可以复用的小分页组件JPage.

1. 概述

    分页是一般的web系统不可缺少的功能之一, 主要设计到两个主题: 1, 解决针对不同RDBMS产品的分页方法(SQL语句)的问题; 2, 前端分页展示的代码. 针对第一个问题可以使用Hibernate的分页查询功能; 第二的问题, 可以自己在JSP页面里面编写SQL脚本合理地显示结果和控制元素, 但是如果将遍历集合的Java代码嵌入JSP页面, 那么页面的代码将会不怎么规范和友好, 当然这个问题可以借助于当前一些强大的开源框架予以解决, 例如部门项目广泛使用的pager-taglib.
    但是目前也有存在一些问题. 前期公司推广SNF框架, 数据访问层使用DAL组件, 其中的SQL语句(包括分页SQL语句)是要程序员自己编写, 但是分页语法不属于SQL规范定义的范畴, 所以不同RDBMS的分页语法差别很大, 即使是同一款RDBMS也有许多不同的分页方式, 其中性能差别很大. 以DB2为例, 之前测试的时候两个已经经过优化的分页SQL语句耗时相差也能达到5倍. 关于页面显示方面, 虽然开源框架提供了强大的分页展示功能, 但是分页毕竟是一个很小但又不可缺少的功能, 如果不想引入开源框架而又想实现从Dao层到View层的统一的分页方法可以考虑使用JPage这个类库.

2. 功能

    假如你现在需要使用以下SQL查询数据:
    “SELECT user_id, user_name FROM user_info where address=? ORDER BY age”
    当需要显示一个页面的时候你至少需要提供”每页显示几条记录(pageSize)”以及”我要显示第几页(pageNumber)”这样的信息, 然后最终输出的是你需要的”这一页需要显示这些数据”这个信息. 因此你只需要提供(1)SQL(2)pageSize(3)pageNum, JPage将会根据你连接的数据库JDBC Connection选择合适的也分SQL语句查询需要的数据(如果不提供pageSize和pageNum的值那么将使用默认的值pageSize=10, pageNum=1), 目前仅支持MySQL和DB2(已在MySQL for Windows32, DB2 for Windows32, DB2 for AIX64上测试通过).
    典型情况下页面显示主要有一下几个方面:



 <!--[if gte mso 9]><xml> <o:OfficeDocumentSettings> <o:AllowPNG/> </o:OfficeDocumentSettings> </xml><![endif]-->

 

其中导航部分的效果可以参考百度的搜索结果显示方式(因为说不清). JPage使用JSP自定义表达式的方式实现对数据的显示和控制.

 

3. 使用

 

    现在以Spring MVC + DB2 For Windows演示JPage的使用, JPage的核心类是com.suning.util.JPage, 通过这个类设置你需要显示的pageSize, SQL语句和pageNum;
    从面向对象的角度来说可以把一个页面当做一个对象, 这个对象包含需要显示的数据、当前页码、页大小等信息,这就是com.suning.util.PageBean, 使用
JPage# public PageBean<T> generatePageBean(int pageNum)
    这个方法可以产生最终需要显示的PageBean, 即页面对象.

 

3.1 Dao层

    JPage定义了一个接口com.suning.dao.PageDao用于定义使用JPage必须实现的方法用于使你可以根据实际的Dao层组件选择自己定义的分页查询语句(例如使用Hibernate等等), 但是为了保证使用的简单性以及尽量不耦合第三方代码, JPage提供了一个基于JDBC的默认实现: com.suning.dao.impl.jdbc.PageDaoImpl

    参考以下配置, 你至少需要提供dataSource

<bean name="pageDao" class="com.suning.dao.impl.jdbc.PageDaoImpl">
	<!-- 你需要提供一个DataSource -->
	<property name="dataSource" ref="dataSource" />
</bean>

<bean name="jpage" class="com.suning.util.JPage">
	<property name="dao" ref="pageDao" />
</bean>

 

3.2 Service层

    将jpage bean注入给Service类, 我这里使用Annotation方式注入:

@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
	private static Logger logger = LoggerFactory
			.getLogger(UserInfoServiceImpl.class);
	/**
	 * JPage分页组件
	 */
	@Resource(name = "jpage")
private JPage<Object[]> jpage;

//setter and getter...

 在Service层通过注入的JPage实例取得实际需要的数据和相关信息

 

@Override
public PageBean<Object[]> getOperateLogs(String userInfoUUID, Timestamp startTime, Timestamp endTime, int pageSize, int pageNum)
	throws IFMException {
StringBuilder querySQL = new StringBuilder(
"SELECT USER_UUID, USER_IP, HOST_NAME, OPERATE_CLASS_NAME, METHOD_NAME, OPERATE_RESULT_FLAG, OPERATE_DATE_TIME, EXTRA_INFO FROM OPERATE_LOG WHERE USER_UUID=?");
	if (startTime != null) {
		querySQL.append(" and OPERATE_DATE_TIME>=? ");
	}
	if (endTime != null) {
		querySQL.append(" and OPERATE_DATE_TIME<=?");
	}

	List<Object> params = new ArrayList<Object>();
	params.add(userInfoUUID);
	if (startTime != null) {
		params.add(startTime);
	}
	if (endTime != null) {
		params.add(endTime);
	}
		
	// 设置pageSize
	jpage.setPageSize(pageSize);
	// 设置实际查询SQL
	jpage.setSql(querySQL.toString(), params.toArray());
	// 返回第pageNum页的数据
	return jpage.generatePageBean(pageNum);
}

 

3.3 controller层



 

3.4 页面

前端显示的JSP标签使用JSP自定义标签实现, 因此需要先引入标签库
<%@ taglib uri="http://www.suning.com/wuliu/jpage-taglib" prefix="jpage"%>
JPage的核心控制标签是<jpage:jpage></jpage:jpage>, 该标签有以下属性:

属性名 是否必须 含义 备注
totalpageNum Y 总页数  
maxShowNum N 最大显示页数  
url Y 需要访问的URL 需要是相对路径, 属于BUG
pageNum Y 当前页码  

 

以上这些属性除了URL之外, 其他完全可以不用配置的, 但是JSP自定义标签需要的一些属性在后台没办法取到, 目前还未想到合理地办法予以解决, 在后期想办法简化这些配置.

<!-- 显示页码以及导航区域 -->
<jpage:jpage url="showOperateLog.action" totalPageNum="${pageBean.totalPageNum }"
	pageNum="${pageBean.pageNum }" pageSize="${pageBean.pageSize }" >
	<!-- 需要传递的参数  -->
	<jpage:param name="startTime" value="${startTime }"/>
	<jpage:param name="endTime" value="${endTime }"/>
	<jpage:param name="userInfoUUID" value="${userInfo.userInfoUUID}"/>
			
	<!-- 通过pageBean获取信息 -->
	<span>共${pageBean.totalNum }条记录</span>
	<span>共${pageBean.totalPageNum }页</span>

	<!-- 导航 -->
	<!-- 首页 -->
	<jpage:first>
		<a href="${pageUrl }">首页</a>
	</jpage:first>
	<!-- 上一页 -->
	<jpage:prev>
		<a href="${pageUrl }">上一页</a>
	</jpage:prev>
	<!-- 显示页 -->
	<jpage:pages>
		<c:choose>
			<c:when test="${index eq pageNum }">
				<a href="${indexUrl }"><font color="red" size="5px"> ${index}</font></a>
			</c:when>
			<c:otherwise>
				<a href="${indexUrl }">${index }</a>
			</c:otherwise>
		</c:choose>
	</jpage:pages>
	<!-- 下一页 -->
	<jpage:next>
		<a href="${pageUrl }">下一页</a>
	</jpage:next>
	<!-- 末页 -->
	<jpage:last>
		<a href="${pageUrl }">末页</a>
	</jpage:last>
</jpage:jpage>

 

<jpage:jpage/>有以下几个子标签



 

4. 不足以及问题列表

4.1 自定义标签复杂

    在” 代码清单6 使用JPage自定义标签显示分页控制”中可以看到如果使用JPage自定义标签那么代码量比较多(其实与开源分页类库pager-taglib比起来代码量差不多), 这是由于两个方面引起的. 一个是由于在显示”首页”, ”第X页”等等信息的时候有些项目使用JavaScript实现, 有些是使用超链接实现; 而且需要自定义CSS样式, 自定义显示控制等等, 因此就有了<jpage:first>, <jpage:next>这类子标签以提供用户的自定义. 另一方面是<jpage:jpage标签的属性过多>

<jpage:jpage url="showOperateLog.action" totalPageNum="${pageBean.totalPageNum }"
			pageNum="${pageBean.pageNum }" pageSize="${pageBean.pageSize }" >

 这四个属性只有url是需要用户定义的, 其他属性都是固定的可以直接copy, 但是由于水平有限目前还没想到比较好的办法可以消除这些属性的显式配置(开源的pager-taglib是通过配置过滤器的方式消除这些配置的).
其实<jpage:jpage>自定义标签虽然看起来代码多, 但是绝大部分都是可以直接copy使用的, 后期想办法简化.

 

5. 代码分析

 

5.1 JPage类

JPage主要的类是com.suning.util.JPage, 源文件如下:

package com.suning.util;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.suning.dao.PageDao;

/**
 * 分页辅助类, 便于方便地取得实际需要显示的页面数据;
 * 
 * 需要首先初始化一个JPage对象, 需要实际查询的SQL语句, 查询所定义的参数, 每一页的大小,
 * 以及针对不同RDBMS的实际分页语句;支持通过构造方法初始化, 或者通过setter两种方式初始化JPage对象.
 * 
 * 初始化JPage对象之后, 通过#getData(int pageNum)方法传入需要取得的页编号, 即可返回需要的数据.
 * 
 * @author 12072533
 * 
 * @param <T>
 */
public class JPage<T> {

	private static final Logger logger = LoggerFactory.getLogger(JPage.class);

	private static final Integer DEFAULT_PAGENUM = 1;
	private static final Integer DEFAULT_PAGESIZE = 10;

	private int pageSize = DEFAULT_PAGENUM;
	private String sql;
	private Object[] parameters;

	private PageDao<T> dao;

	/***************** Two basic constructor ******************/
	JPage() {
	}

	JPage(int pageSize, String sql, Object... parameters) {
		setPageSize(pageSize);
		setSql(sql, parameters);
	}

	public void setPageSize(int pageSize) {
		if (pageSize > 0) {
			this.pageSize = pageSize;
		}
	}

	/**
	 * 设置需要查询的SQL语句以及需要的参数
	 * 
	 * eg. setSql("SELECT * FROM TB_NAME WHERE COL01>param", new Object[]{10})
	 * 
	 * @param sql
	 * @param parameters
	 */
	public void setSql(String sql, Object... parameters) {
		validateSQL(sql);
		this.sql = sql;
		this.parameters = parameters;
	}

	public void setDao(PageDao<T> dao) {
		this.dao = dao;
	}

	/**
	 * 取得一个SQL查询的结果集的数量
	 * 
	 * @param sql
	 * @param parameters
	 * @return 查询的结果集的数量
	 */
	protected long getTotalNum(String sql, Object... parameters) {
		String countSQL = "SELECT COUNT(*) FROM (" + sql + ") AS TEMP";
		return this.dao.getTotalNum(countSQL, parameters);
	}

	/**
	 * 供客户端代码直接调用, 在初始化JPage对象后, 直接取得结果集数量
	 * 
	 * @return long
	 */
	public long getTotalNum() {
		return getTotalNum(sql, parameters);
	}

	/**
	 * 取得指定SQL查询在以pageSize为单位的时候实际的页面总数量
	 * 
	 * @param sql
	 * @param pageSize
	 *            每一页显示的记录数量
	 * @param parameters
	 * @return Integer
	 */
	protected Integer getTotalPageNum(String sql, int pageSize,
			Object... parameters) {
		long totalNum = getTotalNum(sql, parameters);
		int temp = Long.valueOf(totalNum).intValue() % pageSize;
		Integer totalPageNum = null;
		if (temp == 0) {
			totalPageNum = Long.valueOf(totalNum).intValue() / pageSize;
		} else {
			totalPageNum = (Long.valueOf(totalNum).intValue() / pageSize) + 1;
		}
		return totalPageNum;
	}

	/**
	 * 在初始化JPage对象后, 供客户端代码查询页面总数
	 * 
	 * @return 页面的数量
	 */
	public Integer getTotalPageNum() {
		return getTotalPageNum(sql, pageSize, parameters);
	}

	/**
	 * 取得指定页面的记录对象集合
	 * 
	 * @param pageSize
	 *            页面大小
	 * @param pageNum
	 *            页面编号, 从1开始
	 * @param sql
	 * @param parameters
	 * @return List<T>
	 */
	protected List<T> getData(int pageSize, int pageNum, String sql,
			Object... parameters) {
		if (pageNum < 1) {
			logger.warn("pageNum not properly set, using default page number 1");
			pageNum = DEFAULT_PAGENUM;
		}

		if (pageSize <= 0) {
			logger.warn("pageSize not properly set, using default page size 10");
			pageSize = DEFAULT_PAGESIZE;
		}

		Integer totalPageNum = getTotalPageNum(sql, pageSize, parameters);
		if (pageNum >= totalPageNum) {
			pageNum = totalPageNum;
		}

		int start = (pageNum - 1) * pageSize;
		return this.dao.getData(start, pageSize, sql, parameters);
	}

	/**
	 * 初始化JPage之后, 直接传入页面编号, 取得页面的记录集合
	 * 
	 * @param pageNum
	 * @return
	 */
	public List<T> getData(int pageNum) {
		return getData(pageSize, pageNum, sql, parameters);
	}

	/**
	 * Generate page Java Bean
	 * 
	 * @param pageNum
	 * @return PageBean contains all the information that the page needs to show
	 */
	public PageBean<T> generatePageBean(int pageNum) {
		PageBean<T> pageBean = new PageBean<T>(pageSize, pageNum,
				getTotalNum(), getData(pageNum));
		return pageBean;
	}

	/**
	 * 校验是否是SELECT查询语句
	 * 
	 * @param sql
	 */
	private static void validateSQL(String sql) {
		if (!(sql.toUpperCase().startsWith("SELECT"))) {
			throw new RuntimeException(
					"not supported operation, only query supported");
		}
	}

}

 

JPage的jar包和源文件在附录中, 至于文档中的几个问题大家有解决的办法没?

 

  • 大小: 128.5 KB
  • 大小: 81.6 KB
  • 大小: 57.4 KB
1
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics