分页也是一个大家经常讨论的话题,网上也有过很多的分页方法的介绍,但往往仅仅只是局限在web层或数据访问层的一个分页组件,对于一个典型的三层web应用来说笔者还没有见到过一个完整的例子。又或者是使用了这个组件后往往还要手工书写繁琐的代码。在这里笔者就象大家介绍一种基于Struts2+Spring+iBatis三层框架结构,而不用手工写一行代码的分页组件(或者说框架)。
首先,在web层我们可以考虑使用自定义标签来实现我们的分页组件,这样可以最大限度的实现组件的可复用性而使用Struts2又可以非常方便的扩展已有的组件。通过下面这张类图结构可以看到,所有Struts2的tag都继承自BodyTagSupport这个类,在BodyTagSupport这个类以及之上是属于sun servlet/jsp标准的一部分而从StrutsBodyTagSupport之后则是Struts框架的一部分,只要我们继承自Struts2的框架则我们的标签则自动的获得了对ognl表达式以及valueStack的支持。而所有的ui标签则继承自AbstractUITag这个类,对于我们需要的分页标签来说,这是一个很好的扩展点。
而每一个ui tag都会有一个对应的ui bean component。这个ui bean封装了对具体数据的处理(ognl表达式解析,从valueStack上取值),并且可以指定一个freemarker模板来渲染html输出。
通过下面的这个类图结构可以看到,所有Struts2的内置标签都继承了UIBean这个类,我们的分页标签ui component也会继承自这个类。
分页标签的tag类
/**
* struts2 版分页标签
*/
public class PagerTag extends AbstractUITag {
private static final long serialVersionUID = 719669934024053141L;
protected String totalRecord;
protected String totalPage;
protected String curPage;
protected String pageLimit;
protected String url;
protected String curCssClass;
protected String showTotalPage;
protected String showTotalRecord;
protected String directJumpType;
protected void populateParams() {
super.populateParams();
Pager pager = (Pager) component;
pager.setTotalRecord(totalRecord);
pager.setTotalPage(totalPage);
pager.setCurPage(curPage);
pager.setPageLimit(pageLimit);
pager.setUrl(url);
pager.setCurCssClass(curCssClass);
pager.setShowTotalPage(showTotalPage);
pager.setShowTotalRecord(showTotalRecord);
pager.setDirectJumpType(directJumpType);
}
@Override
public Component getBean(ValueStack stack, HttpServletRequest request,
HttpServletResponse response) {
return new Pager(stack, request, response);
}
public void setTotalRecord(String totalRecord){
this.totalRecord = totalRecord;
}
public void setTotalPage(String totalPage) {
this.totalPage = totalPage;
}
public void setCurPage(String curPage) {
this.curPage = curPage;
}
public void setPageLimit(String pageLimit) {
this.pageLimit = pageLimit;
}
public void setUrl(String url) {
this.url = url;
}
public void setCurCssClass(String curCssClass) {
this.curCssClass = curCssClass;
}
public void setShowTotalPage(String showTotalPage) {
this.showTotalPage = showTotalPage;
}
public void setShowTotalRecord(String showTotalRecord) {
this.showTotalRecord = showTotalRecord;
}
public void setDirectJumpType(String directJumpType) {
this.directJumpType = directJumpType;
}
}
分页标签的ui component类
/**
* struts2版的分页标签
*
*/
@StrutsTag(name = "pager", tldTagClass = "com.meidusa.toolkit.web.page.PagerTag", description = "meidusa pager tag")
public class Pager extends UIBean {
final public static String TEMPLATE = "pager";
protected String totalRecord;
protected String totalPage;
protected String curPage;
protected String pageLimit;
protected String url;
protected String curCssClass;
protected String showTotalPage;
protected String showTotalRecord;
protected String directJumpType;
public Pager(ValueStack stack, HttpServletRequest request,
HttpServletResponse response) {
super(stack, request, response);
}
/**
* 用于返回模板的名字,Struts2会自动在后面加入.ftl扩展名以找到特定的模板文件。
*/
@Override
protected String getDefaultTemplate() {
return TEMPLATE;
}
/**
* 设置UIBean的属性,一般Tag中有几个这样的属性,这里就有几个 StrutsTagAttribute注解,说明该属性是int类型,这一步很重要
*
* @param totalPage
*/
@StrutsTagAttribute(description = "total records", type = "Long")
public void setTotalRecord(String totalRecord) {
this.totalRecord = totalRecord;
}
@StrutsTagAttribute(description = "total pages", type = "Integer")
public void setTotalPage(String totalPage) {
this.totalPage = totalPage;
}
@StrutsTagAttribute(description = "current page", type = "Integer")
public void setCurPage(String curPage) {
this.curPage = curPage;
}
@StrutsTagAttribute(description = "how many pages in a panel once", type = "Integer")
public void setPageLimit(String pageLimit) {
this.pageLimit = pageLimit;
}
@StrutsTagAttribute(description = "url to be linked", type = "String")
public void setUrl(String url) {
this.url = url;
}
@StrutsTagAttribute(description = "css style of current page", type = "String")
public void setCurCssClass(String curCssClass) {
this.curCssClass = curCssClass;
}
@StrutsTagAttribute(description = "whether to show totalPage", type = "Boolean", defaultValue = "true")
public void setShowTotalPage(String showTotalPage) {
this.showTotalPage = showTotalPage;
}
@StrutsTagAttribute(description = "whether to show currentPage", type = "Boolean", defaultValue = "false")
public void setShowTotalRecord(String showTotalRecord) {
this.showTotalRecord = showTotalRecord;
}
// TODO 直接页面跳转
// 这里的directJumpType默认值为none, 可选值为 'select', 'goto'
@StrutsTagAttribute(description = "show type of direct jump type. such as select,textbox which can lead going to a page directly", type = "String", defaultValue = "none")
public void setDirectJumpType(String directJumpType) {
this.directJumpType = directJumpType;
}
/**
* 重写evaluateExtraParams()方法,在UIBean初始化后会调用这个方法来初始化设定参数,如addParameter方法,会在freemarker里的parameters里加入一个key
* value。这里要注意findString,还有相关的findxxxx方法,它们是已经封装好了的解释ognl语法的工具,具体是怎么样的,大家可以查看一下UIBean的api
* doc
*/
@Override
protected void evaluateExtraParams() {
super.evaluateExtraParams();
// findValue()方法本身已对OGNL进行了处理
if (totalRecord != null) {
addParameter("totalRecord", findValue(totalRecord));
}
if (totalPage != null) {
addParameter("totalPage", findValue(totalPage));
}
if (curPage != null) {
addParameter("curPage", findValue(curPage));
}
if (pageLimit != null) {
addParameter("pageLimit", findValue(pageLimit));
}
if (url != null) {
addParameter("url", findValue(url, String.class));
}
if (curCssClass != null) {
addParameter("curCssClass", findValue(curCssClass,String.class));
}
if (showTotalPage != null) {
addParameter("showTotalPage", findValue(showTotalPage,
Boolean.class));
}
if (showTotalRecord != null) {
addParameter("showTotalRecord", findValue(showTotalRecord,Boolean.class));
}
if (directJumpType != null) {
addParameter("directJumpType", findValue(directJumpType));
}
}
}
除了标签类和ui组件类之外,根据sun的servlet/jsp标准,我们还需要一个标签库的定义,才可以在jsp页面上使用这个标签。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>2.2.3</tlib-version> <jsp-version>1.2</jsp-version> <short-name>m</short-name> <uri>/meidusa-tags</uri> <display-name>"meidusa tags"</display-name> <description><![CDATA["meidusa tags supports custom components that usually used in web develop"]]></description> <tag> <name>pager</name> <tag-class>com.meidusa.toolkit.web.page.PagerTag</tag-class> <body-content>JSP</body-content> <attribute> <name>totalRecord</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[总记录数]]></description> </attribute> <attribute> <name>totalPage</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[总页数]]></description> </attribute> <attribute> <name>curPage</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[当前页号]]></description> </attribute> <attribute> <name>pageLimit</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[每版最多显示的页数,最好为奇数]]></description> </attribute> <attribute> <name>url</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[翻页时的请求地址,如:list?page={page},其中的{page}会被动态地替换]]></description> </attribute> <attribute> <name>curCssClass</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[当前页的span标签的cssClass]]></description> </attribute> <attribute> <name>showTotalPage</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[是否显示总页数,默认为"true"]]></description> </attribute> <attribute> <name>showTotalRecord</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[是否显示总记录数,只有设置了总记录数属性的时候才有效,默认为"false"]]></description> </attribute> <attribute> <name>directJumpType</name> <required>false</required> <rtexprvalue>true</rtexprvalue> <description><![CDATA[直接跳转的方式,"goto"或"select",默认为none]]></description> </attribute> <!--以下是原UIBean通用的属性--> <attribute> <name>accesskey</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html accesskey attribute on rendered html element]]></description> </attribute> <attribute> <name>cssClass</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[The css class to use for element]]></description> </attribute> <attribute> <name>cssStyle</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[The css style definitions for element to use]]></description> </attribute> <attribute> <name>disabled</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html disabled attribute on rendered html element]]></description> </attribute> <attribute> <name>id</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[id for referencing element. For UI and form tags it will be used as HTML id attribute]]></description> </attribute> <attribute> <name>key</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the key (name, value, label) for this particular component]]></description> </attribute> <attribute> <name>label</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Label expression used for rendering a element specific label]]></description> </attribute> <attribute> <name>labelposition</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Define label position of form element (top/left)]]></description> </attribute> <attribute> <name>name</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[The name to set for element]]></description> </attribute> <attribute> <name>onblur</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[ Set the html onblur attribute on rendered html element]]></description> </attribute> <attribute> <name>onchange</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onchange attribute on rendered html element]]></description> </attribute> <attribute> <name>onclick</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onclick attribute on rendered html element]]></description> </attribute> <attribute> <name>ondblclick</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html ondblclick attribute on rendered html element]]></description> </attribute> <attribute> <name>onfocus</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onfocus attribute on rendered html element]]></description> </attribute> <attribute> <name>onkeydown</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onkeydown attribute on rendered html element]]></description> </attribute> <attribute> <name>onkeypress</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onkeypress attribute on rendered html element]]></description> </attribute> <attribute> <name>onkeyup</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onkeyup attribute on rendered html element]]></description> </attribute> <attribute> <name>onmousedown</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onmousedown attribute on rendered html element]]></description> </attribute> <attribute> <name>onmousemove</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onmousemove attribute on rendered html element]]></description> </attribute> <attribute> <name>onmouseout</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onmouseout attribute on rendered html element]]></description> </attribute> <attribute> <name>onmouseover</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onmouseover attribute on rendered html element]]></description> </attribute> <attribute> <name>onmouseup</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onmouseup attribute on rendered html element]]></description> </attribute> <attribute> <name>onselect</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html onselect attribute on rendered html element]]></description> </attribute> <attribute> <name>required</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[If set to true, the rendered element will indicate that input is required]]></description> </attribute> <attribute> <name>requiredposition</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Define required position of required form element (left|right)]]></description> </attribute> <attribute> <name>tabindex</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html tabindex attribute on rendered html element]]></description> </attribute> <attribute> <name>template</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[The template (other than default) to use for rendering the element]]></description> </attribute> <attribute> <name>templateDir</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[The template directory.]]></description> </attribute> <attribute> <name>theme</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[The theme (other than default) to use for rendering the element]]></description> </attribute> <attribute> <name>title</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the html title attribute on rendered html element]]></description> </attribute> <attribute> <name>tooltip</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the tooltip of this particular component]]></description> </attribute> <attribute> <name>tooltipConfig</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Set the tooltip configuration]]></description> </attribute> <attribute> <name>value</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description><![CDATA[Preset the value of input element.]]></description> </attribute> </tag> <tag> <name>textfield</name> <tag-class>org.apache.struts2.views.jsp.ui.TextFieldTag</tag-class> <body-content>JSP</body-content> <description><![CDATA[Render an HTML input field of type text]]></description> </tag> </taglib>
freemarker模板。根据Struts2的框架,我们可以为不同的主题定义多个freemarker模板,再结合css和参数控制,完全可以做到使用同一个分页标签实现多种风格不同的显示输出。
<#--显示总记录数,只有同时设置了总记录数和显示总记录数时才有效--> <#if (parameters.totalRecord?exists) && (parameters.showTotalRecord?exists) && (parameters.showTotalRecord == true)> <span>${parameters.totalRecord}</span><#rt/> </#if> <#--打印选择页的面板 --> <#--首先,当parameters.pageLimit为空时将其设定为total--> <#if (parameters.pageLimit?exists)> <#assign limit = parameters.pageLimit?number /> <#else> <#assign limit = parameters.totalPage?number /> </#if> <#assign cur = (parameters.curPage!1)?number /> <#assign total = parameters.totalPage?number /> <@pagePanel cur=cur total=total url=parameters.url limit=limit /> <#--打印选择页的面板结束--> <#--显示总页数--> <#if parameters.showTotalPage?exists && (parameters.showTotalPage == true)> <span><a>共${parameters.totalPage}页</a></span><#rt/> </#if> <#--打印翻页面板的宏,配合printPage,printButton--> <#macro pagePanel cur total limit url curCssClass = "cur_page"><#--curCssClass默认值为cur_page--> <#--limit的中间数--> <#assign l_mid = (limit/2)?int + 1 /> <#--total的中间数--> <#assign t_mid = (total/2)?int /> <#--情况一:总页数小于等于限制显示页数,这时显示所有页--> <#if total ==0> <#elseif total ==1> <#elseif (cur ==1) && (total <= limit)> <@printPage left = 1 right = total cur = cur url = url curCssClass = curCssClass /> <@printNext/> <#elseif (cur ==total) && (total <= limit)> <@printPrev/> <@printPage left = 1 right = total cur = cur url = url curCssClass = curCssClass /> <#elseif (cur > 1) && (cur < total) && (total <= limit)> <@printPrev/> <@printPage left = 1 right = total cur = cur url = url curCssClass = curCssClass /> <@printNext/> <#else> <#--情况二:总页数大于限制显示页数,这时又分三种情况--> <#--情况1:显示的limit个页号在total个页面中偏向左端,1作为最左边的页号,当前页没能显示在中间,偏左,例: total = 20,cur = 2,limit = 5.显示的页面为:1 [2] 3 4 5 这种情况 cur <= l_mid --> <#if cur <= l_mid> <#if cur !=1> <@printPrev/> </#if> <@printPage left = 1 right = limit cur = cur url = url curCssClass = curCssClass /> <@printNext/> <@printEnd/> <#--情况2:显示的limit个页号在total个页面中偏向右端,total作为最右边的页号,当前页没能显示在中间,偏右,例: total = 20,cur = 19,limit = 5.显示的页面为:16 17 18 [19] 20 这种情况 cur > total - l_mid --> <#elseif (cur > (total - l_mid))> <@printStart/> <@printPrev/> <@printPage left = (total - limit + 1) right = total cur = cur url = url curCssClass = curCssClass /> <#if cur !=total> <@printNext/> </#if> <#--在中间的情况--> <#else> <@printStart/> <@printPrev/> <@printPage left = (cur - l_mid + 1) right = (cur + l_mid -1) cur = cur url = url curCssClass = curCssClass /> <@printNext/> <@printEnd/> </#if> </#if> </#macro> <#--根据最左与最右的页号来打印所显示的页面,当前页为的cssStyle为curCssClass--> <#macro printPage left right cur url curCssClass> <#list left..right as p> <#if p == cur> <span class = "${curCssClass}" >${p}</span><#rt/> <#else> <a href = "<@makeURL text=url page=p />">${p}</a><#rt/> </#if> </#list> </#macro> <#macro printStart> <a href="<@makeURL text=parameters.url page=1 />"> 首页 </a><#rt/> </#macro> <#macro printEnd> <a href="<@makeURL text=parameters.url page=total />"> 末页 </a><#rt/> </#macro> <#macro printNext> <a href="<@makeURL text=parameters.url page=cur+1 />"> 下一页 </a><#rt/> </#macro> <#macro printPrev> <a href="<@makeURL text=parameters.url page=cur-1 />"> 上一页 </a><#rt/> </#macro> <#--产生动态URL的宏--> <#macro makeURL text page> <#if text?last_index_of("{page}") < 0> ${text}?page=${page} <#else> ${text?replace("{page}",page)} </#if> </#macro>
在jsp页面中我们需要首先载入标签库
<%@ taglib prefix="m" uri="/meidusa-tags" %>
然后在页面中的调用就现得十分简单,我们只需要给出total page和total record的参数同时配合具体负责列表显示的aciton即可,在这个负责列表显示的aciton中,我们需要给出两个参数,pno和rows。pno指定当前显示第几页,rows则表示每页显示多少条记录。
<m:pager curPage="%{#parameters.pno[0]}" totalPage="%{#paging.totalPage}" theme="simple" url="/demo/browse.action?pno={page}" pageLimit="5" showTotalPage="false" totalRecord="%{#paging.totalRecord}" showTotalRecord="false" /> <s:action name="post!list" namespace="/demo" executeResult="true" > <s:param name="pno" value="#parameters.pno"/> <s:param name="rows" value="10"/> </s:action>
照理说这样已经可以获得不错的重用性了,但问题是对于total page和total record,如果每次执行不同的业务逻辑都需要单独手工书写业务代码获取就显得十分的繁琐。那有没有一种比较方便的,并且可以通用的方法获得total page和total record呢?
在这里我们使用一个专门获取total page和total record的action。在调用这个aciton的时候我们只要告诉它我们需要每页显示多少条记录,它会帮我们计算出total page和total record。
<s:action name="postPaging" id="paging" namespace="/demo" executeResult="false"> <s:param name="rows" value="10"/> </s:action>
这个aciton的实现非常简单,只是简单的继承了一下我们的PagingAction类
public class PagingAction extends
com.meidusa.toolkit.web.common.action.PagingAction<Cookie> {
}
PagingAction类的实现,主要调用的baseAO中的doPaging方法。
public class PagingAction<T extends ClientCookie> extends ActionSupport implements ClientCookieAware<T>{
private int totalPage;
private int totalRecord;
private int rows;
private BaseAO<?> ao;
private T cookie;
public void setAo(BaseAO<?> ao) {
this.ao = ao;
}
public void setClientCookie(T cookie){
this.cookie = cookie;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public long getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(int totalRecord) {
this.totalRecord = totalRecord;
}
public int getRows() {
return rows;
}
public void setRows(int rows) {
this.rows = rows;
}
public String execute(){
Result result = ao.doPaging(rows, getForeignKey());
if (!result.isSuccess()){
ResultCode resultCode = result.getResultCode();
this.addActionError(resultCode.getMessage().getMessage());
return ERROR;
}
totalPage = (Integer)result.getModels().get("totalPage");
totalRecord = (Integer)result.getModels().get("totalRecord");
return SUCCESS;
}
public String getForeignKey(){
return cookie.getLoginId();
}
}
doPaging在AbstractAO中的默认实现。它首先取得total record,然后再根据rows,也就是每页显示的记录数计算出total page的值。
public Result doPaging(int rows, String foreignKey){
Result result = new ResultSupport();
result.setSuccess(false);
K example = doExample(foreignKey);
int totalRecord = dao.countByExample(example);
int totalPage = (int)Math.ceil(totalRecord / (double)rows);
result.setDefaultModel("totalRecord", totalRecord);
result.setDefaultModel("totalPage", totalPage);
result.setSuccess(true);
return result;
}
我们再回过头来看一下在显示列表的时候具体是如何实现分页的(不是分页标签,而是对具体显示数据的分页)。在这个jsp页面中,调用了post action中的list方法。
<s:action name="post!list" namespace="/demo" executeResult="true" > <s:param name="pno" value="#parameters.pno"/> <s:param name="rows" value="10"/> </s:action>
post aciton本身非常的简单,主要是继承了我们的baseAciotn(在上一篇介绍CRUDL文章中已经详细讲解过,这里主要看一下跟分页相关的代码)。在list方法中主要是调用的业务层ao的doList方法。在调用这个方法时候还传入了一个paging作为参数,那么这个paging究竟是什么呢?
@SkipValidation
public String list() {
Result result = ao.doList(requestId, paging);
if (result.isSuccess()){
list = (List)result.getDefaultModel();
return Constants.LIST;
}else {
ResultCode resultCode = result.getResultCode();
this.addActionError(resultCode.getMessage().getMessage());
return ERROR;
}
}
paging就是一个pojo,非常的简单,主要就是包含了分页所需要的信息。pno表示我们需要第几页的数据,rows表示每页显示多少条记录,offset则是通过pno和rows计算出来的,表示从第几条数据开始取,主要是针对mysql数据库的分页实现。
public class Paging {
private int pno;
private int rows;
public int getPno() {
return pno;
}
public void setPno(int pno) {
this.pno = pno;
}
public int getRows() {
return rows;
}
public void setRows(int rows) {
this.rows = rows;
}
public int getOffset(){
if (pno == 0){
pno = 1;
}
return (pno-1)*rows;
}
}
在调用post!list方法时会通过一个PagingIntercept拦截器把参数rows的值注入到这个paging对象中,这个拦截器有点类似于modelDriven拦截器,只不过aciton需要实现Pageable接口。我们的baseAciton默认的实现了这个接口。
public class PagingIntercept extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();
if (action instanceof Pageable) {
Pageable pageable = (Pageable) action;
ValueStack stack = invocation.getStack();
if (pageable.getPaging() != null) {
stack.push(pageable.getPaging());
}
}
return invocation.invoke();
}
}
我们的baseAciton默认的实现了这个接口。
public interface Pageable {
public Paging getPaging();
}
那么再来看一下业务层ao的doList方法又是如何做到分页的呢,AbstractAO对doList的默认实现
public Result doList(String foreignKey, Paging paging){
Result result = new ResultSupport();
result.setSuccess(false);
K example = doExample(foreignKey);
if (paging.getRows()!=0){
example.setOffset(paging.getOffset());
example.setRows(paging.getRows());
}
List<T> list = dao.selectByExampleWithoutBLOBs(example);
result.setDefaultModel(list);
result.setSuccess(true);
return result;
}
在调用dao层得到列表的同时传入了一个example对象作为参数,而这个example对象则包含了需要分页的信息,rows和offset。那么这个example到底是个什么东西呢?
如果大家用过Ibator这个工具的话一定不会对example感到陌生,在本系列的上一篇当中也提到了Ibator这个工具,它是一个eclipse插件,专门用来自动生成ibatis的代码,除了model,dao接口及实现,sqlmapper外还包括了example对象。这个example对象可以自动生成针对SELECT语句的WHERE字句的各种条件的Criteria,而笔者对Ibator做了一番简单的改造,使其生成的所有example对象都继承自一个抽象类,并且加入了针对mysql数据库的分页支持。笔者把这个工具叫做IbatorPlus。
AbstractExample
public abstract class AbstractExample<T extends AbstractExample.Criteria> {
protected String orderByClause;
protected List<T> oredCriteria;
public AbstractExample() {
oredCriteria = new ArrayList<T>();
}
protected int offset;
protected int rows;
protected boolean distinct;
public boolean isDistinct() {
return distinct;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public void setOffset(int offset){
this.offset = offset;
}
public int getOffset(){
return offset;
}
public void setRows(int rows){
this.rows = rows;
}
public int getRows(){
return rows;
}
protected AbstractExample(AbstractExample<T> example) {
this.orderByClause = example.orderByClause;
this.oredCriteria = example.oredCriteria;
this.offset = example.offset;
this.rows = example.rows;
this.distinct = example.distinct;
}
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
public String getOrderByClause() {
return orderByClause;
}
public List<T> getOredCriteria() {
return oredCriteria;
}
public void or(T criteria) {
oredCriteria.add(criteria);
}
public T createCriteria() {
T criteria = createCriteriaInternal();
if (oredCriteria.size() == 0) {
oredCriteria.add(criteria);
}
return criteria;
}
protected abstract T createCriteriaInternal();
public void clear() {
oredCriteria.clear();
orderByClause = null;
distinct = false;
offset = 0;
rows = 0;
}
public static abstract class Criteria {
protected List<String> criteriaWithoutValue;
protected List<Map<String, Object>> criteriaWithSingleValue;
protected List<Map<String, Object>> criteriaWithListValue;
protected List<Map<String, Object>> criteriaWithBetweenValue;
protected Criteria() {
super();
criteriaWithoutValue = new ArrayList<String>();
criteriaWithSingleValue = new ArrayList<Map<String, Object>>();
criteriaWithListValue = new ArrayList<Map<String, Object>>();
criteriaWithBetweenValue = new ArrayList<Map<String, Object>>();
}
public boolean isValid() {
return criteriaWithoutValue.size() > 0 || criteriaWithSingleValue.size() > 0
|| criteriaWithListValue.size() > 0 || criteriaWithBetweenValue.size() > 0;
}
public List<String> getCriteriaWithoutValue() {
return criteriaWithoutValue;
}
public List<Map<String, Object>> getCriteriaWithSingleValue() {
return criteriaWithSingleValue;
}
public List<Map<String, Object>> getCriteriaWithListValue() {
return criteriaWithListValue;
}
public List<Map<String, Object>> getCriteriaWithBetweenValue() {
return criteriaWithBetweenValue;
}
protected void addCriterion(String condition) {
if (condition == null) {
throw new RuntimeException("Value for condition cannot be null");
}
criteriaWithoutValue.add(condition);
}
protected void addCriterion(String condition, Object value, String property) {
if (value == null) {
throw new RuntimeException("Value for " + property + " cannot be null");
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("condition", condition);
map.put("value", value);
criteriaWithSingleValue.add(map);
}
protected void addCriterion(String condition, List<? extends Object> values, String property) {
if (values == null || values.size() == 0) {
throw new RuntimeException("Value list for " + property
+ " cannot be null or empty");
}
Map<String, Object> map = new HashMap<String, Object>();
map.put("condition", condition);
map.put("values", values);
criteriaWithListValue.add(map);
}
protected void addCriterion(String condition, Object value1, Object value2, String property) {
if (value1 == null || value2 == null) {
throw new RuntimeException("Between values for " + property + " cannot be null");
}
List<Object> list = new ArrayList<Object>();
list.add(value1);
list.add(value2);
Map<String, Object> map = new HashMap<String, Object>();
map.put("condition", condition);
map.put("values", list);
criteriaWithBetweenValue.add(map);
}
public abstract Criteria andForeignKeyEqualTo(String value);
}
}
ibatorPlus生成的example类
public class PostExample extends AbstractExample<PostExample.Criteria> {
/**
* This method was generated by Apache iBATIS Ibator.
* This method corresponds to the database table post
*
* @ibatorgenerated Thu Feb 04 16:15:41 CST 2010
*/
public PostExample() {
super();
}
/**
* This method was generated by Apache iBATIS Ibator.
* This method corresponds to the database table post
*
* @ibatorgenerated Thu Feb 04 16:15:41 CST 2010
*/
public PostExample(PostExample example) {
super(example);
}
/**
* This method was generated by Apache iBATIS Ibator.
* This method corresponds to the database table post
*
* @ibatorgenerated Thu Feb 04 16:15:41 CST 2010
*/
protected Criteria createCriteriaInternal() {
Criteria criteria = new Criteria();
return criteria;
}
/**
* This class was generated by Apache iBATIS Ibator.
* This class corresponds to the database table post
*
* @ibatorgenerated Thu Feb 04 16:15:41 CST 2010
*/
public static class Criteria extends AbstractExample.Criteria {
public Criteria andForeignKeyEqualTo(String value) {
addCriterion("loginId like", value, "loginId");
return this;
}
public Criteria andIdIsNull() {
addCriterion("id is null");
return this;
}
public Criteria andIdIsNotNull() {
addCriterion("id is not null");
return this;
}
public Criteria andIdEqualTo(String value) {
addCriterion("id =", value, "id");
return this;
}
public Criteria andIdNotEqualTo(String value) {
addCriterion("id <>", value, "id");
return this;
}
public Criteria andIdGreaterThan(String value) {
addCriterion("id >", value, "id");
return this;
}
public Criteria andIdGreaterThanOrEqualTo(String value) {
addCriterion("id >=", value, "id");
return this;
}
public Criteria andIdLessThan(String value) {
addCriterion("id <", value, "id");
return this;
}
public Criteria andIdLessThanOrEqualTo(String value) {
addCriterion("id <=", value, "id");
return this;
}
public Criteria andIdLike(String value) {
addCriterion("id like", value, "id");
return this;
}
public Criteria andIdNotLike(String value) {
addCriterion("id not like", value, "id");
return this;
}
public Criteria andIdIn(List<String> values) {
addCriterion("id in", values, "id");
return this;
}
public Criteria andIdNotIn(List<String> values) {
addCriterion("id not in", values, "id");
return this;
}
public Criteria andIdBetween(String value1, String value2) {
addCriterion("id between", value1, value2, "id");
return this;
}
public Criteria andIdNotBetween(String value1, String value2) {
addCriterion("id not between", value1, value2, "id");
return this;
}
public Criteria andTitleIsNull() {
addCriterion("title is null");
return this;
}
public Criteria andTitleIsNotNull() {
addCriterion("title is not null");
return this;
}
public Criteria andTitleEqualTo(String value) {
addCriterion("title =", value, "title");
return this;
}
public Criteria andTitleNotEqualTo(String value) {
addCriterion("title <>", value, "title");
return this;
}
public Criteria andTitleGreaterThan(String value) {
addCriterion("title >", value, "title");
return this;
}
public Criteria andTitleGreaterThanOrEqualTo(String value) {
addCriterion("title >=", value, "title");
return this;
}
public Criteria andTitleLessThan(String value) {
addCriterion("title <", value, "title");
return this;
}
public Criteria andTitleLessThanOrEqualTo(String value) {
addCriterion("title <=", value, "title");
return this;
}
public Criteria andTitleLike(String value) {
addCriterion("title like", value, "title");
return this;
}
public Criteria andTitleNotLike(String value) {
addCriterion("title not like", value, "title");
return this;
}
public Criteria andTitleIn(List<String> values) {
addCriterion("title in", values, "title");
return this;
}
public Criteria andTitleNotIn(List<String> values) {
addCriterion("title not in", values, "title");
return this;
}
public Criteria andTitleBetween(String value1, String value2) {
addCriterion("title between", value1, value2, "title");
return this;
}
public Criteria andTitleNotBetween(String value1, String value2) {
addCriterion("title not between", value1, value2, "title");
return this;
}
public Criteria andLoginidIsNull() {
addCriterion("loginId is null");
return this;
}
public Criteria andLoginidIsNotNull() {
addCriterion("loginId is not null");
return this;
}
public Criteria andLoginidEqualTo(String value) {
addCriterion("loginId =", value, "loginid");
return this;
}
public Criteria andLoginidNotEqualTo(String value) {
addCriterion("loginId <>", value, "loginid");
return this;
}
public Criteria andLoginidGreaterThan(String value) {
addCriterion("loginId >", value, "loginid");
return this;
}
public Criteria andLoginidGreaterThanOrEqualTo(String value) {
addCriterion("loginId >=", value, "loginid");
return this;
}
public Criteria andLoginidLessThan(String value) {
addCriterion("loginId <", value, "loginid");
return this;
}
public Criteria andLoginidLessThanOrEqualTo(String value) {
addCriterion("loginId <=", value, "loginid");
return this;
}
public Criteria andLoginidLike(String value) {
addCriterion("loginId like", value, "loginid");
return this;
}
public Criteria andLoginidNotLike(String value) {
addCriterion("loginId not like", value, "loginid");
return this;
}
public Criteria andLoginidIn(List<String> values) {
addCriterion("loginId in", values, "loginid");
return this;
}
public Criteria andLoginidNotIn(List<String> values) {
addCriterion("loginId not in", values, "loginid");
return this;
}
public Criteria andLoginidBetween(String value1, String value2) {
addCriterion("loginId between", value1, value2, "loginid");
return this;
}
public Criteria andLoginidNotBetween(String value1, String value2) {
addCriterion("loginId not between", value1, value2, "loginid");
return this;
}
}
}
使用ibatorPlus生成的sqlmap也自动实现对分页的支持。
<select id="selectByExample" parameterClass="com.meidusa.demo.dal.model.PostExample" resultMap="BaseResultMap"> <!-- WARNING - @ibatorgenerated This element is automatically generated by Apache iBATIS Ibator, do not modify. This element was generated on Thu Feb 04 16:15:41 CST 2010. --> select <isParameterPresent> <isEqual compareValue="true" property="distinct"> distinct </isEqual> </isParameterPresent> <include refid="post.Base_Column_List" /> from post <isParameterPresent> <include refid="post.Example_Where_Clause" /> <isNotNull property="orderByClause"> order by $orderByClause$ </isNotNull> <isNotEqual compareValue="0" property="rows"> limit $offset$, $rows$ </isNotEqual> </isParameterPresent> </select>
看到这里有些读者朋友可能会问,那如果我用的数据库不是mysql怎么办呢?在回答这个问题前,不妨先来看一下我们的ibatorPlus配置文件,这个配置完全遵照了ibator配置文件的dtd规范,笔者没有改动。
有所不同的是,targetRuntime被替换成我们自己的实现类HierarchyIntrospectedTable,从而我们自动生成的model类,example类和dao接口都继承自一个抽象父类或抽象接口,使得我们可以对所有的CRUDL以及分页行为抽象,在具体的应用中只要继承自这些抽象父类或接口就会自动获得所有的默认功能。在这里,笔者也不禁想说,ibator本身就提供了很好的扩展机制,我们不仅可以替换targetRuntime的实现,还可以有针对性的写一些插件,比如在生成的sqlmap中加入对mysql分页的支持是通过MysqlPagingPlugin这个插件来实现的。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ibatorConfiguration PUBLIC "-//Apache Software Foundation//DTD Apache iBATIS Ibator Configuration 1.0//EN" "http://ibatis.apache.org/dtd/ibator-config_1_0.dtd" > <ibatorConfiguration > <classPathEntry location="D:\Temp\mysql-connector-java-5.1.6.jar" /> <ibatorContext id="context1" targetRuntime="com.meidusa.toolkit.plugin.ibator.core.HierarchyIntrospectedTable"> <ibatorPlugin type="com.meidusa.toolkit.plugin.ibator.core.MysqlPagingPlugin"/> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/demo" userId="root"> </jdbcConnection> <javaModelGenerator targetPackage="com.meidusa.demo.dal.model" targetProject="demo-dal/src/main/java" > <property name="exampleRootInnerClass" value="com.meidusa.toolkit.dal.ibator.AbstractExample.Criteria"/> <property name="rootClass" value="com.meidusa.toolkit.dal.ibator.AbstractModel"/> </javaModelGenerator> <sqlMapGenerator targetPackage="com.meidusa.demo.dal.sqlmap" targetProject="demo-dal/src/main/resources" /> <daoGenerator targetPackage="com.meidusa.demo.dal" targetProject="demo-dal/src/main/java" type="SPRING" > </daoGenerator> <table schema="demo" tableName="post" domainObjectName="Post"> <property name="exampleRootClass" value="com.meidusa.toolkit.dal.ibator.AbstractExample<com.meidusa.demo.dal.model.PostExample.Criteria>"/> <property name="rootInterface" value="com.meidusa.toolkit.dal.ibator.AbstractDAO<Post,PostExample>"/> </table> </ibatorContext> </ibatorConfiguration>