主题:基于Struts2+Spring+iBatis的web应用分页篇

您所在的位置:网站首页 pagertaglib 主题:基于Struts2+Spring+iBatis的web应用分页篇

主题:基于Struts2+Spring+iBatis的web应用分页篇

2023-01-19 15:20| 来源: 网络整理| 查看: 265

分页也是一个大家经常讨论的话题,网上也有过很多的分页方法的介绍,但往往仅仅只是局限在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类

Java代码       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;           }                             }     /** * 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类

Java代码             @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);           }                           @Override          protected String getDefaultTemplate() {               return TEMPLATE;           }                                 @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;           }                           @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));               }                 }       }    /** * 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页面上使用这个标签。

2.2.3 1.2 m /meidusa-tags "meidusa tags" pager com.meidusa.toolkit.web.page.PagerTag JSP totalRecord false true totalPage true true curPage true true pageLimit false true url true true curCssClass false true showTotalPage false true showTotalRecord false true directJumpType false true accesskey false false cssClass false false cssStyle false false disabled false false id false false key false false label false false labelposition false false name false false onblur false false onchange false false onclick false false ondblclick false false onfocus false false onkeydown false false onkeypress false false onkeyup false false onmousedown false false onmousemove false false onmouseout false false onmouseover false false onmouseup false false onselect false false required false false requiredposition false false tabindex false false template false false templateDir false false theme false false title false false tooltip false false tooltipConfig false false value false false textfield org.apache.struts2.views.jsp.ui.TextFieldTag JSP

 

 freemarker模板。根据Struts2的框架,我们可以为不同的主题定义多个freemarker模板,再结合css和参数控制,完全可以做到使用同一个分页标签实现多种风格不同的显示输出。

${parameters.totalRecord} 共${parameters.totalPage}页 首页 末页 下一页 上一页 ${text}?page=${page} ${text?replace("{page}",page)}

 

在jsp页面中我们需要首先载入标签库

  

 

 然后在页面中的调用就现得十分简单,我们只需要给出total page和total record的参数同时配合具体负责列表显示的aciton即可,在这个负责列表显示的aciton中,我们需要给出两个参数,pno和rows。pno指定当前显示第几页,rows则表示每页显示多少条记录。

 

 

 

照理说这样已经可以获得不错的重用性了,但问题是对于total page和total record,如果每次执行不同的业务逻辑都需要单独手工书写业务代码获取就显得十分的繁琐。那有没有一种比较方便的,并且可以通用的方法获得total page和total record呢?

 

 在这里我们使用一个专门获取total page和total record的action。在调用这个aciton的时候我们只要告诉它我们需要每页显示多少条记录,它会帮我们计算出total page和total record。

 

这个aciton的实现非常简单,只是简单的继承了一下我们的PagingAction类

public class PagingAction extends com.meidusa.toolkit.web.common.action.PagingAction { }

 

 PagingAction类的实现,主要调用的baseAO中的doPaging方法。

public class PagingAction extends ActionSupport implements ClientCookieAware{ 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方法。

 

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 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 { protected String orderByClause; protected List oredCriteria; public AbstractExample() { oredCriteria = new ArrayList(); } 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 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 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 criteriaWithoutValue; protected List criteriaWithSingleValue; protected List criteriaWithListValue; protected List criteriaWithBetweenValue; protected Criteria() { super(); criteriaWithoutValue = new ArrayList(); criteriaWithSingleValue = new ArrayList(); criteriaWithListValue = new ArrayList(); criteriaWithBetweenValue = new ArrayList(); } public boolean isValid() { return criteriaWithoutValue.size() > 0 || criteriaWithSingleValue.size() > 0 || criteriaWithListValue.size() > 0 || criteriaWithBetweenValue.size() > 0; } public List getCriteriaWithoutValue() { return criteriaWithoutValue; } public List getCriteriaWithSingleValue() { return criteriaWithSingleValue; } public List getCriteriaWithListValue() { return criteriaWithListValue; } public List 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 map = new HashMap(); map.put("condition", condition); map.put("value", value); criteriaWithSingleValue.add(map); } protected void addCriterion(String condition, List

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3