两个字母搞定J2EE通用分页标签

 

作者:张纪豪(转载请注明出处)

(一)摘要与设计思想

 

    海水无量,只取一瓢。大量的数据显示,分段提取是通用的做法,是的,这叫分页技术。

    <t:p/>,对于WEB程序员,这种标签格式不会陌生。t:代表自定义标签库;p:分页标签。两个字母(具体的说它是6个字符组成)便可以搞定J2EE平台下所有分页功能。当然可以扩充,例如显示总记录数、总页数、当前页数、每页记录数等信息。

效果截图1:

效果截图2:

  本文将从图书、新闻两个实体数据显示来详细介绍这种通用分页技术。


 

 J2EE项目架构设计:

    最初微软的Asp脚本语言给动态WEB应用程序开发带来广阔的天地,由此互联网更加精采,不过笔者没有用过Asp来架构过大型项目,因为听说中关村的程序员大部分都有颈椎病预期都是因为陷进了微软的泥潭(逗乐!)。对于那种将所有程序代码和网页显示代码混在一个文件中且脱离不了过程化的编程方法,用在大型项目的构建着实有些可怕。因此J2EE规范中将程序与页面代码分离是一次伟大的革新,并在后来演变成多层结构分层治之的思想,随后微软的C#效仿之,这才初步将程序员慢慢解放。

    典型的J2EE项目分为模型-视图-控制三层,即MVC(Model- View-Controller),模型是整个项目中最复杂部分,它除了有核心的数据操纵技术,还需要紧密的业务逻辑,所以模型层又可包含模型领域、数据访问层、服务层;控制层有一个经典的框架是Struts,熟悉它的Action就知道控制层的作用;视图层较容易些,例如最广泛使用的JSP。通过MVC 概念就知道原来说用JSP做项目就可看出自己还没有入行,现在已经不是微软的ASP时代,因为在J2EE里根本不用JSP就可以强大的WEB项目(例如页面用velocity模板技术而不用JSP)。这也就意味着J2EE项目里的视图不再局限于网页,它可能是Excel、PDF、Desktable Window等等。

    本文阐述的分页技术围绕一个含数据领域为五层的小项目来展开描述,控制层和模型层都交给Spring管理,属真正的SSH集成开发模式:

    表现层为采用JSP技术;

    控制层采用当今最热门Struts(版本为1.x,不是Struts2) ,虽然我发现它的牙都快掉光了,但还是选择它以更能熟练掌握本分页方法;

    模型层中含有服务层、数据库访问层和数据领域层,其中:

    持久层采用Hibernate(版本3.2),Hibernate管理领域对象的配置采用注解方式,与xml方式配置功能是一样。

    控制层和模型层全交给Spring容器管理。

    数据库管理系统不太关心,因为现在是面向对象编程,你给Hibernate 怎么配置,Hibernate就怎么实现。

   下一节将介绍工程建立、数据领域模型以及它的映射到数据库必须的配置(上述本例采注解方式)、page对象的封装……

---


 

(二)项目工程基础和领域模型

 

一、工程基础:

    1、我们先在MyEclipse开发工具里建一个WEB项目,并将各包建立如下:

    com.zhangjihao.domain    存放领域对象,需要持久化到数据库的

    com.zhangjihao.bean      存放非持久化到数据库的bean

    com.zhangjihao.dao       数据访问层

    com.zhangjihao.service   服务层

    com.zhangjihao.web       WEB层,其中web包下还有struts、filter、taglib等子包

    com.zhangjihao.util      自己的工具包

 

  2、准备所需的jar包:

    再将Struts1.2、Spring2.5、Hibernate3.2、Log4j、Sun和Apache的工共组件、数据库驱动等等所需的jar包都拷入到WebRoot/WEB-INF/lib目录,并Build Path到MyEclipse中(一般MyEclipse会自动Build Path)。

    3、xml文件的准备:

    web.xml中加上Struts和Spring配置:

        <!-- 配置Spring并在WEB容器中实例化之 -->
         <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:beans.xml</param-value>
         </context-param>
           <listener>
                <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
          </listener>
        
        
         <!-- 利用spring解决乱码 -->
         <filter>
          <filter-name>encoding</filter-name>
          <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
          <init-param>
           <param-name>encoding</param-name>
           <param-value>UTF-8</param-value>
          </init-param>
         </filter>
         <filter-mapping>
          <filter-name>encoding</filter-name>
          <url-pattern>/*</url-pattern>
         </filter-mapping>
        
        
         <!-- 利用spring解决hibernate延迟加载问题 -->
         <filter>
          <filter-name>openSessionInViewFilter</filter-name>
          <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
         </filter>
         <filter-mapping>
          <filter-name>openSessionInViewFilter</filter-name>
          <url-pattern>/*</url-pattern>
         </filter-mapping>


         <!-- 配置Struts1 -->
          <servlet>
          <servlet-name>action</servlet-name>
          <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
           <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
           </init-param>
           <load-on-startup>0</load-on-startup>
         </servlet>
         <servlet-mapping>
           <servlet-name>action</servlet-name>
           <url-pattern>*.do</url-pattern>
         </servlet-mapping>

    在struts- config.xml中加上(第四篇将有将更详细的内容):

        <!--将action交给Spring管理-->

        <controller>
             <set-property property="processorClass"
              value="org.springframework.web.struts.DelegatingRequestProcessor"/>
        </controller>

    准备一个Spring的xml配置文件:

    由于web.xml里已经写了beans.xml,我们就用它作为Spring的配置文件,由于Hibernate也交给了Spring管理,我们就不需要给Hibernate写配置文件,而是将Hibernate配置信息写在beans.xml:

<!--注意这些名称空间,很初学者使用Spring时出错就是因为这里没有声明-->
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-2.5.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
   ">

  <!-- 注入一个数据源,我们这里用的是MySQL数据库 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="url" value="jdbc:mysql://localhost:3306/数据库名?useUnicode=true&amp;characterEncoding=UTF-8"/>
  <property name="driverClassName" value="org.gjt.mm.mysql.Driver"/>
  <property name="username" value="数据库用户名"/>
  <property name="password" value="数据库密码"/>
   <!-- 连接池启动时的初始值 -->
  <property name="initialSize" value="1"/>
   <!-- 连接池的最大值 -->
  <property name="maxActive" value="500"/>
   <!-- 最大空闲值,当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
  <property name="maxIdle" value="2"/>
   <!--  最小空闲值,当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
  <property name="minIdle" value="1"/>
</bean> 
 
<!-- Spring集成Hibernate配置 -->
  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean">
<!--这个是用注解方式来配置实体映射-->

  <property name="dataSource" ref="dataSource"/>
  <property name="annotatedClasses">
   <list>
    <value>com.zhangjihao.domain.Book</value>
    <value>com.zhangjihao.domain.News</value>
   </list>
  </property>
  <property name="annotatedPackages">
   <list>
    <value>com.zhangjihao.domain</value>
   </list>
  </property>
  <property name="hibernateProperties">
       <value>
      hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
          hibernate.hbm2ddl.auto=update
          hibernate.show_sql=true
          hibernate.format_sql=false
       </value>
  </property>
</bean>

<!-- 配置事务管理并打开Spring声明式事务功能  -->
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <tx:annotation-driven transaction-manager="txManager"/>
   
    <!-- 打开Spring注解依赖注入功能 -->
    <context:annotation-config/>
   
    <!-- 打开Spring扫描创建Bean功能 -->
    <context:component-scan base-package="com.zhangjihao"/>

  <!-- 打开AOP拦截功能 -->
  <aop:aspectj-autoproxy/>

    再在数据库管理系统中开一个库。通过上面的工作,我们的工程基础基本准备好。这些虽然偏主题,但笔者作事偏谨,故啰嗦了这么多,或许对刚接触SSH集成开发的朋友还是有很大的帮助。


 

二、实体搭建(Entity Bean):

    我们这个主题在前面讲过围绕图书和新闻两个实体来描述分页方法,所以建立这个实体,并将Hibernate映射配置借助JPA标准在实体方法中通过注解来配置:

1、图书实体Book.java

package com.zhangjihao.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;


@Entity
public class Book implements Serializable{

private static final long serialVersionUID = 5251288153182073422L;
//**编号**//
private Integer id;
//**书名**//
private String bookname;
//**作者**//
private String author;
//**出版社**//
private String publisher;
//**价格**//
private Float price;
//**ISBN号**//
private String isbn;
//**日期**//
private Date pubDate = new Date();
//**描述**//
private String description;


/构造方法

public Book() {}
public Book(Integer id, String bookname, String author, String publisher,
Float price, String isbn, Date pubDate) {
this.id = id;
this.bookname = bookname;
this.author = author;
this.publisher = publisher;
this.price = price;
this.isbn = isbn;
this.pubDate = pubDate;
}
/Getter
@Id
@GeneratedValue
public Integer getId() {
return id;
}
@Column(nullable=false,length=100)
public String getBookname() {
return bookname;
}
@Column(nullable=false,length=30)
public String getAuthor() {
return author;
}
@Column(length=100)
public String getPublisher() {
return publisher;
}
@Column(nullable=false)
public Float getPrice() {
return price;
}
@Column(length=100)
public String getIsbn() {
return isbn;
}
@Temporal(TemporalType.DATE)
public Date getPubDate() {
return pubDate;
}

/**支持大字段**/
@Lob
@Basic(fetch=FetchType.EAGER,optional=false)
@Column(columnDefinition="LONGTEXT NOT NULL")
public String getDescription() {
return description;
}

/Setter
public void setId(Integer id) {
this.id = id;
}
public void setBookname(String bookname) {
this.bookname = bookname;
}
public void setAuthor(String author) {
this.author = author;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
public void setPrice(Float price) {
this.price = price;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public void setPubDate(Date pubDate) {
this.pubDate = pubDate;
}
public void setDescription(String description) {
this.description = description;
}


/Override Method
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Book other = (Book) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

2、新闻实体(News.java)

package com.zhangjihao.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class News implements Serializable{

private static final long serialVersionUID = 928281566591424585L;
//**编号**//
private Integer id;
//**新闻标题**//
private String title;
//**新闻来源**//
private String rootin;
//**创建日期**//
private Date createdate;
//**新闻内容**//
private String content;


/构造方法
public News() {}

public News(Integer id, String title, String rootin, Date createdate) {
this.id = id;
this.title = title;
this.rootin = rootin;
this.createdate = createdate;
}

/Getter
@Id
@GeneratedValue
public Integer getId() {
return id;
}

@Column(nullable=false,length=100)
public String getTitle() {
return title;
}

@Column(length=100)
public String getRootin() {
return rootin;
}

@Temporal(TemporalType.DATE)
public Date getCreatedate() {
return createdate;
}
@Lob
@Basic(fetch=FetchType.EAGER,optional=false)
@Column(columnDefinition="LONGTEXT NOT NULL")
public String getContent() {
return content;
}


/Setter
public void setId(Integer id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setRootin(String rootin) {
this.rootin = rootin;
}
public void setCreatedate(Date createdate) {
this.createdate = createdate;
}
public void setContent(String content) {
this.content = content;
}


/Override Method
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final News other = (News) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

    当然,这仅仅是为了描述分页技术,对于真正的应用,这种数据库设计是不完美的,例如最常用到图书分类、新闻分类、发布人、有效性设定等信息,在此都作省略。

    编写java实体Bean时,都要养成几个习惯:

    _实现Serializable接口;

    _覆盖hashCode()和equals(Object obj)方法;

    _不再用基本类型定义字段;

    _定义了常用字段的构造方法时,一定要定义一个空的构造方法。

    上面两个实体类都有两个构造方法,一个空的,一个是用于列表显示时用的。我们知道,当分页显示图书时,其每本书的内容介绍(description字段) 是不需要显示出来的,因为当用户点某本书查看更多详细的书内容介绍时才需要显示图书描述(description字段),而往往在数据库中 description字段中数据库甚至大于其它所有字段内容之和,所以从性能角度考虑,在分页显示时图书列表则不需要description字段,大家可仔细看看上面的构造方法。

    还需要注意类名上和get方法上面的注解,它们功能和*.hbm.xmk文件内容差不多,不熟悉的朋友去看看JPA的书籍。

 

三、 Page Bean

下面是重点的Page类封装(Page.java)。
    对于一个分页项,其中有三个重要的属性分别是:总记录数量、每页记录数量、当前页,有了这三个属性便可以计算出总页数,当然有必要再加一个url属性,因为第n页和n±1页对于服务器来讲是两个不同的页面,url便是用于页面的导向,好,下面看看详细的代码:

package com.zhangjihao.bean;

/**
* 分页Bean
* @author 张纪豪
* @version 0.1
* Build Time Mar 19, 2007
*/
public class Page {

/**URL上页码参数**/
public static String pageNumberParameterName = "pageIndex";

/**当前页码**/
private int pageIndex;

/**每页记录数**/
private int pageSize = 10;

/**总记录数**/
private int totalCount;

/**页码上的地址**/
private String url;
   
    /**
     * Index starts from 1, 2, 3...
     */
    public Page(int pageIndex) {
        this.pageIndex = pageIndex;
    }

    /**
     * Index starts from 1, 2, 3...
     */
    public Page(int pageIndex, int pageSize) {
        this.pageIndex = pageIndex;
        this.pageSize = pageSize;
    }
   
    /**
     * 带URL地址构造方法,最常用的一种
     * @param pageIndex  当前页
     * @param pageSize   每页记录数
     * @param url        URL地址
     */
    public Page(int pageIndex, int pageSize, String url) {
        this.pageIndex = pageIndex;
        this.pageSize = pageSize;
        this.url = url;
    }
   
public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}

    public int getPageIndex() {
        return pageIndex;
    }

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public int getPageSize() {
        return pageSize;
    }

    /**
     * 开始页
     * @return
     */
    public int getFirstIndex() {
        return (pageIndex-1) * pageSize;
    }

    /**
     * 最后页
     * @return
     */
    public int getLastIndex() {
        int n = getFirstIndex() + pageSize;
        if (n > totalCount)
            n = totalCount;
        return n;
    }
   
    public int getPageCount() {
        if (totalCount==0)
            return 0;
        return totalCount / pageSize + (totalCount % pageSize==0 ? 0 : 1);
    }

    public boolean isEmpty() {
        return totalCount==0;
    }

    public boolean getHasPrevious() {
        return pageIndex > 1;
    }

    public boolean getHasNext() {
        return pageIndex < getPageCount();
    }

public String getUrl() {return url;}
public void setUrl(String url) {this.url = url;}
}


    对于Page类的写法非常灵活,有点将查询结果封闭到该对象上,甚至有点将SQL语句封装里面,笔者认为只要达到功能,越简单越好。

    事实上有了Page对象,整个思路就全部明白,后面的工作无非是将Page对象中的数据填充和显示出来

(三)、数据访问层设计与数据分页

 

    数据访问层的设计非常重要,在一个较大的项目中,应该利用泛型技术做一个公共数据访问组件,由于本主题要阐述的是分页,所以不搞那么复杂。也因如此,服务层内容也省掉。

    在com.zhangjihao.dao包中建立两个接口和两个对应的实现类:

BookDao接口

package com.zhangjihao.dao;

import java.util.List;

import com.zhangjihao.bean.Page;
import com.zhangjihao.domain.Book;

public interface BookDao {

public abstract void save(Book book);

public abstract void update(Book book);

public abstract void delete(Book book);

public abstract Book getBook(Integer id);

//数据分页
public abstract List listAllBooks(Page page);

}

BookDaoImpl数据访问组件

package com.zhangjihao.dao;

import java.util.List;

import javax.annotation.Resource;

import org.hibernate.Query;
import org.hibernate.SessionFactory;

import com.zhangjihao.bean.Page;
import com.zhangjihao.domain.Book;
@Repository("BookDao")  //交给Spring管理
@Transactional          //声明Spring管理事务
public class BookDaoImpl implements BookDao {

         //在Spring容器里取得Hibernate的session工厂对象
@Resource protected SessionFactory sessionFactory;

public void save(Book book) {
sessionFactory.getCurrentSession().persist(book);
}

public void update(Book book) {
sessionFactory.getCurrentSession().merge(book);
}

public void delete(Book book){
sessionFactory.getCurrentSession().delete(book);
}

public Book getBook(Integer id) {
return (Book)sessionFactory.getCurrentSession().get(Book.class, id);
}

//数据分页
@SuppressWarnings("unchecked")
public List listAllBooks(Page page) {
Query query = sessionFactory.getCurrentSession().createQuery("select count(*) from Book");

//得到总记录并保存到page对象中
long l = (Long)query.uniqueResult();
page.setTotalCount((int)l);
query = sessionFactory.getCurrentSession().createQuery("from Book");

//注意下面这个分页参数
query.setFirstResult(page.getFirst()).setMaxResults(page.getPageSize()).setFetchSize(page.getPageSize());
return query.list();
}
}


NewsDao接口

package com.zhangjihao.dao;

import java.util.List;

import com.zhangjihao.bean.Page;
import com.zhangjihao.domain.News;

public interface NewsDao {

public abstract void save(News news);

public abstract void update(News news);

public abstract void delete(News news);

public abstract News getNews(Integer id);

//数据分页
public abstract List listAllNews(Page page);

}

NewsDaoImpl数据访问组件

package com.zhangjihao.dao;

import java.util.List;

import javax.annotation.Resource;

import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.zhangjihao.bean.Page;
import com.zhangjihao.domain.News;
@Repository("NewsDao")    //交给Spring管理
@Transactional            //声明Spring管理事务
public class NewsDaoImpl implements NewsDao {

@Resource protected SessionFactory sessionFactory;

public void save(News news) {
sessionFactory.getCurrentSession().save(news);
}
public void delete(News news) {
sessionFactory.getCurrentSession().delete(news);
}
public void update(News news) {
sessionFactory.getCurrentSession().merge(news);
}
public News getNews(Integer id) {
return (News)sessionFactory.getCurrentSession().get(News.class, id);
}
public List listAllNews(Page page) {
Query query = sessionFactory.getCurrentSession().createQuery("select count(*) from News");

//得到总记录并保存到page对象中
long l = (Long)query.uniqueResult();
page.setTotalCount((int)l);
query = sessionFactory.getCurrentSession().createQuery("from News");

//注意下面这个分页参数
query.setFirstResult(page.getFirst()).setMaxResults(page.getPageSize()).setFetchSize(page.getPageSize());
return query.list();
}
}

    listAllBooks和listAllNews 两方法返回的就是一页的数据。

 


---

 

 

(四)、控制层取得分页数据

 

    在上一篇中强调本主题要阐述的是分页,不搞那么复杂,所以省掉服务层代码,我们直接在控制层的Action里调用Dao层组件。

     工作之前,我们先在com.zhangjihao.util包下做一个工具类,用来从请求参数中得到页码值:

WebUtil.java

package com.zhangjihao.util;

import javax.servlet.http.HttpServletRequest;

public class WebUtil {

//根据指定参数在Request对象得到整数值,如果为空或出错则返回给定的默认值
public static int getIntByRequestParament(HttpServletRequest request, String param, int defaultvalue){
try {
return Integer.parseInt(request.getParameter(param));
} catch (Exception e) {
return defaultvalue;
}
}
}

    开始在com.zhangjihao.web.struts.action建立两个Action:

BookAction.java

package com.zhangjihao.web.struts.action;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.springframework.stereotype.Controller;

import com.zhangjihao.bean.Page;
import com.zhangjihao.dao.BookDao;
import com.zhangjihao.util.WebUtil;

@Controller("/listBook")  //注意这个注解,它的作用是将这个action交给Spring管理,实现SSH集成
public class BookAction extends Action {

//注入一个bookDao数据访问组件
@Resource BookDao bookDao;

public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {

//从request对象里得到页码信息,如果为空或不是数值都返回1
int pageindex = WebUtil.getIntByRequestParament(request, "pageindex", 1);

//构造一个page对象,第1个参数是当前页,第2个参数是该页最大记录数,第3个是页码上的连接地址
Page page = new Page(pageindex,20,"listBook.do");

//将page对象送到数据访问组件层,返回当前页的数据借助request作用域送到视图层,
//当然也可以封装到page对象里
request.setAttribute("books", bookDao.listAllBooks(page));

//出来后的page对象已经有了总记录数了,自然就有了页码信息
request.setAttribute("page", page);

return mapping.findForward("list");
}

}

NewsAction.java

package com.zhangjihao.web.struts.action;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.springframework.stereotype.Controller;

import com.zhangjihao.bean.Page;
import com.zhangjihao.dao.NewsDao;
import com.zhangjihao.util.WebUtil;

@Controller("/listNews")   //注意这个注解,它的作用是将这个action交给Spring管理,实现SSH集成
public class NewsAction extends Action {

//注入一个newsDao数据访问组件
@Resource NewsDao newsDao;

public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
//从request对象里得到页码信息,如果为空或不是数值都返回1
int pageindex = WebUtil.getIntByRequestParament(request, "pageindex", 1);

//构造一个page对象,第1个参数是当前页,第2个参数是该页最大记录数,第3个是页码上的连接地址
Page page = new Page(pageindex,20,"listNews.do");

//将page对象送到数据访问组件层,返回当前页的数据借助request作用域送到视图层,
//当然也可以封装到page对象里
request.setAttribute("newses", newsDao.listAllNews(page));

//出来后的page对象已经有了总记录数了,自然就有了页码信息
request.setAttribute("page", page);

return mapping.findForward("list");
}
}

 

再到struts-config.xml文件配置两个action

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://struts.apache.org/dtds/struts-config_1_3.dtd">
         
<struts-config>

<form-beans>
</form-beans>

  <global-forwards>
   <forward name="message" path="/WEB-INF/page/public/message.jsp"/>
</global-forwards>

  <action-mappings>
   <!-- BookAction,注意这个path中的值与Action类名上的注解内容一致-->
   <action path="/listBook" validate="false" scope="request">
    <forward name="list" path="/WEB-INF/page/public/list_book.jsp"/>
   </action>
   <!-- NewsAction,大家发现是不是没有写type属性,交给Spring管理后就可以省掉了-->
   <action path="/listNews" validate="false" scope="request">
    <forward name="list" path="/WEB-INF/page/public/list_news.jsp"/>
   </action> 
  </action-mappings>


  <!-- 将控制器交给Spring,也意思着编写的Action都由Spring管理-->
  <controller>
   <set-property property="processorClass" value="org.springframework.web.struts.DelegatingRequestProcessor"/>
  </controller>

  <message-resources parameter="ApplicationResources" />

  <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
     <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
    </plug-in>
</struts-config>

    后面进行视图层内容,也是最重要的内容

 

----

 

(五)、视图层的JSP页面输出分页数据

 

    由于Action中往request对象里放了分页数据,那我们在JSP页面就可遍历出来,但同时应将页码信息输出来,我们看看JSP页面是如何处理的?

    在WebRoot/WEB-INF/jsp/目录下建立两个jsp文件:

list_book.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://zhangjihao.com/page" prefix="t" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>图书列表</title>
  </head>
 
  <body>
  <h1 align="center">图书列表</h1>
  <TABLE align="center">
   <tr>
    <td>书名</td>
    <td>作者</td>
    <td>出版社</td>
    <td>价格</td>
    <td>ISBN号</td>
    <td>日期</td>
   </tr>
   <tr>
     <c:forEach items="${books}" var="book">
      <td>${book.bookname}</td>
     <td>${book.author}</td>
     <td>${book.publisher}</td>
     <td>${book.price}</td>
     <td>${book.isbn}</td>
     <td>${book.pubDate}</td>
     </c:forEach>
   </tr>
   </TABLE>
   <!-- 注意了,两个字母实现通用分页 -->
   <t:p/>
  </body>
</html>

list_news.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://zhangjihao.com/page" prefix="t" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>新闻列表</title>
  </head>
 
  <body>
  <h1 align="center">新闻列表</h1>
  <TABLE align="center">
   <tr>
    <td>新闻标题</td>
    <td>新闻来源</td>
    <td>日期</td>
   </tr>
   <tr>
     <c:forEach items="${newses}" var="news">
      <td>${news.title}</td>
     <td>${news.rootin}</td>
     <td>${news.createdate}</td>
     </c:forEach>
   </tr>
   </TABLE>
   <!-- 注意了,两个字母实现通用分页 -->
   <t:p/>
  </body>
</html>

 

jsp文件中最重要的是声明标签库和调用分页标签:

<%@ taglib uri="http://zhangjihao.com/page" prefix="t" %>

<t:p/>

    事实上这目前还不能在页面上输出页码信息,做过JSP标签库的朋友都知道,还要编写标签库和配置标签,好,下一步也是最后一步,我们就开始编写分页标签。

 

---

(六)、标签库编程及配置

 

    对于标签大家熟悉不过了,HTML文件全是标签组成,再看JSP、JSTL、Struts、Spring MVC都搞那么多标签,全把程序员捆在尖括号中央,郁闷之余,发誓也搞个自己的尖括号^_^。

    机会来了......

    先带大家了解下JSP标记:

    标准的JSP 标记可以调用JavaBeans组件或者执行客户的请求,这大大降低了JSP开发的复杂度和维护量。JSP技术也允许你自定义taglib,其实换句话说,taglib可以看成是对JSP标记的一种扩展,正如xml是对html的一种扩展一样。taglib通常定义在tag标签库中,这种标签库存放着你自己定义的tag标签。简而言之,如果使用taglib,那么你可以设计自己的JSP标记!

    一个自定义的tag标签是用户定义的一种JSP标记。当一个含有自定义的tag标签的JSP页面被jsp引擎编译成servlet时,tag标签被转化成了对一个称为tag处理类的对象进行的操作。于是当JSP页面被jsp引擎转化为servlet后,实际上tag标签被转化成为了对tag处理类的操作。

    要使用tag标签,JSP程序员必须做两件事:

    · 声明此tag标签的tag库
    · 实现此tag标签

    我们反着来,先实现tag标签

PagePrint.java

package com.zhangjihao.web.taglib;


import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;

import com.zhangjihao.bean.Page;


/**
* 页码标签程序,特点:
* 1、5页后当前页码总在中间
* 2、页码上的URL地址支持多种方式,如:
*   page.setUrl("http://www.zhangjihao.com/article/list_*.do");     “*”号会被替换成页码,Spring MVC支持这种伪静态地址
*   page.setUrl("http://www.zhangjihao.com/article/list.do");       地址后面会自动加上页码参数
*   page.setUrl("http://www.zhangjihao.com/article/list.do?cid=1"); 地址后面会自动加上页码参数,不影响多参数连接。
* 3、页码显示样式可由外面css样式传入
*
* @author 张纪豪
* @version 0.2
* Build Time Mar 21, 2007
*/
public class PagePrint extends TagSupport {

private static final long serialVersionUID = 1L;

/**是否显示总记录数*/
private boolean totalcount = true; //如果外面不指定,将默认为显示

/**是否显示总页数*/
private boolean pagecount = true;  //如果外面不指定,将默认为显示

/**当前页码的显示样式**/
private String  curPageNumberStyle = "pagenumbervisited"; //如果外面不指定,将按默认值

/**带链接的页码的显示样式**/
private String linkPageNumberStyle = "pagenumber";//如果外面不指定,将按默认值


public int doStartTag() throws JspException {
//在标签类中定义Request对象
HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
  String curPath = request.getContextPath();

if(request.getAttribute("page") != null){

 
  //大家还记得我们在控制层将page对象放到了Request作用里吗?现在是用它的时候了
    Page page = (Page)request.getAttribute("page");

    JspWriter out = pageContext.getOut();

    StringBuffer sb = new StringBuffer("");

    try {
     //如果设定了要打印总页数,则在JSP页面中输出当前页和总页数
     if(this.isPagecount())
      sb.append("当前页/总页数:"+page.getPageIndex()+"/"+page.getPageCount()+"&nbsp;&nbsp;&nbsp;");
   
     sb.append("页码:");

   
//如果当前页码大于1时,应该输出开始一页和上一页的连接
    if(page.getPageIndex() > 1){
      sb.append("<a title=\"开始一页\" href=\""+conversionUrl(page.getUrl(),1)+"\">
<img src=\""+curPath+"/images/navigation_top.gif\" border=\"0\"/></a>");
      sb.append("<a title=\"上一页\"   href=\""+conversionUrl(page.getUrl(),page.getPageIndex()-1)+"\">
<img src=\""+curPath+"/images/navigation_previous.gif\" border=\"0\"/></a>&nbsp;&nbsp;");
    }

   
//开始在总页数里循环
    for (int cur = 1; cur <= page.getPageCount(); cur++){

     //假如有几十页、几百页、甚至n多,我们不能全部将页码输出吧?这里只取10页
     if(page.getPageIndex() < 5 && cur < 10){
       if(cur == page.getPageIndex()){

        //当前页不应该有连接
         sb.append("<span class=\"pagenumbervisited\">"+cur+"</span>&nbsp;");
       }else{
         sb.append("<a href=\""+conversionUrl(page.getUrl(),cur)+"\" class=\"pagenumber\" title=\"第"+cur+"页\">"+cur+"</a>&nbsp;");
       }
     }else{

       //进入5页以后,要让当前页码总在中间。百度原来是不是抄我的?哈哈...吹一把
       if(cur > page.getPageIndex() -5 && cur < page.getPageIndex()+5){
        if(cur == page.getPageIndex()){
         sb.append("<span class=\"pagenumbervisited\">"+cur+"</span>&nbsp;");
        }else{
         sb.append("<a href=\""+conversionUrl(page.getUrl(),cur)+"\"
class=\"pagenumber\" title=\"第"+cur+"页\">"+cur+"</a>&nbsp;");
        }
       }
      }
     }
   
     //只要没有进到最后一页,都应该输出下一面和最后一页连接
     if(page.getPageIndex() < page.getPageCount()){
      sb.append("&nbsp;&nbsp;<a title=\"下一页\" href='"+conversionUrl(page.getUrl(),page.getPageIndex()+1)+"'>
<img src=\""+curPath+"/images/navigation_next.gif\" border=\"0\"/></a>");
      sb.append("<a title=\"最后一页\" href='"+conversionUrl(page.getUrl(),page.getPageCount())+"'>
<img src=\""+curPath+"/images/navigation_bott.gif\" border=\"0\"/></a>\n");
     }
   
     //标记中调用是否指定输出每页记录数和总记录数
     if(this.isTotalcount())
      sb.append("&nbsp;&nbsp;&nbsp;每页记录数/总记录数:"+
page.getPageSize()+"/"+page.getTotalCount());

   } catch (Exception e) {
     sb.append(e.getMessage());
   }finally{

     //往页面上输出
     try {
      out.println(sb.toString());
     } catch (IOException e) {
      e.printStackTrace();
     }
    }
  }
  return TagSupport.SKIP_BODY;
}


//辅助方法

private String conversionUrl(String url, int pageindex){
   if(url == null || url.length() < 1)
    return "index.jsp";
 
   if(url.indexOf('*')>-1){
    return url.replace("*", String.valueOf(pageindex));
   }else{
    StringBuffer sb = new StringBuffer(url);
    if(sb.indexOf("?") > -1){
     char c = sb.charAt(sb.length()-1);
     if(c=='?' || c=='&'){
      sb.append(Page.pageNumberParameterName + "=" + pageindex);
     }else{
      sb.append("&"+Page.pageNumberParameterName + "=" + pageindex);
     }
    }else{
     sb.append("?"+Page.pageNumberParameterName + "=" + pageindex);
    }
    return sb.toString();
   }
}


/Setter&Getter/

public boolean isTotalcount() {
   return totalcount;
}

public boolean isPagecount() {
   return pagecount;
}

public void setTotalcount(boolean totalcount) {
   this.totalcount = totalcount;
}

public void setPagecount(boolean pagecount) {
   this.pagecount = pagecount;
}

public String getCurPageNumberStyle() {
   return curPageNumberStyle;
}

public void setCurPageNumberStyle(String curPageNumberStyle) {
   this.curPageNumberStyle = curPageNumberStyle;
}

public String getLinkPageNumberStyle() {
   return linkPageNumberStyle;
}

public void setLinkPageNumberStyle(String linkPageNumberStyle) {
   this.linkPageNumberStyle = linkPageNumberStyle;
  }

}

    标签的实现需要继承TagSupport,有关这方面的详细知识可以参考J2EE规范Servlet中的JSP2.0技术。页码信息中还用了几个小图片,像开始一页、上一页、下一页、最后一页都是用图片标识的,所以大家去找几个图片放在Webroot/images目录下,当然也可以去掉,用文或符号描述。

    下面配置上述的标签实现,在WEB-INF目录下建立一个tld文件(这里我用了自己的名字全拼作文件名):

zhangjihao.tld

    <?xml version="1.0" encoding="UTF-8"?>
    <!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>1.0</tlib-version>
     <jsp-version>1.0</jsp-version>
     <short-name>p</short-name>

    <!-- 标签库地址,如果这写这个,JSP页面上用逻辑路径指定也可以找到 -->

     <uri>http://zhangjihao.com/page</uri>
     <description>分页标签库</description>
     <tag>
      <!-- 标签名 -->
    <name>p</name>

    <!-- 实现类 -->

      <tag-class>com.zhangjihao.web.taglib.PagePrint</tag-class>

    <!-- 参数:是否输出页数信息,默认为输出 -->
      <attribute>
       <name>totalcount</name>
       <rtexprvalue>false</rtexprvalue>
       <type>boolean</type>
      </attribute>

    <!-- 参数:是否输出总记录信息,默认为输出 -->

      <attribute>
       <name>pagecount</name>
       <rtexprvalue>false</rtexprvalue>
       <type>boolean</type>
      </attribute>

    <!-- 参数:当前页码的显示样式,如果不指定,将按默认值 -->

      <attribute>
       <name>curPageNumberStyle</name>
       <rtexprvalue>false</rtexprvalue>
       <type>String</type>
      </attribute>

      <!-- 参数:带链接的页码的显示样式,如果不指定,将按默认值 -->

      <attribute>
       <name>linkPageNumberStyle</name>
       <rtexprvalue>false</rtexprvalue>
       <type>String</type>
      </attribute>

    </tag>
    </taglib>

    由于request作用的不同,所以在n个JSP页面上调用<t:p/>都不会出输出重复的内容,故实现最大的简化。

    到此为止已经完成了通用分页过程


原文转载完
======

问答部分:

A:提一个小问题,加入说,我想在分页加上CSS,这样是分页和页面整体更加协调,比如 下一页 的链接, 不要下划线,鼠标放上,字体变其他颜色等等问题,是不是就需要打开jar或者class文件进行修改,编译,测试,发布呢。如果每一个项目都有这样的要求,但却是不一样的CSS style 这样会通用吗?
OK,我还需要加一个跳转文本框,一个按钮。或者,加一个下拉框,进行页面跳转,怎么办?再洗修改jar or class吗?


Q:标签库很灵活的,你可以将css样式作为标签的属性传到标签库中去,就像下面的两个参数一个:
<!-- 参数:是否输出页数信息 -->
<attribute>
<name>totalcount</name>
<rtexprvalue>false</rtexprvalue>
<type>boolean</type>
</attribute>
<!-- 参数:是否输出总记录信息 -->
<attribute>
<name>pagecount</name>
<rtexprvalue>false</rtexprvalue>
<type>boolean</type>
</attribute>
或者在PagePring类硬编码,如:
sb.append("<a href=\""+page.getUrl()+"pageindex="+cur+"\" class=\"pagenumber\" title=\"第"+cur+"页\">"+cur+"</a>&nbsp;");
这个 class="pagenumber"就是css样式文件中配置的,你在jsp页面上加一个css文件就可以。
修改样式的方法非常灵活。


跳转文本框也很容易实现,按钮点击后实际上触发的是一个get请求,所以利用javascript将文本框里值作页码参数值发送一次GET请求,与鼠标点击页码是一回事。根据这一思想,将代码放到PagePring类中,JSP页面就会输出你要跳转文本框

 

A:你好,我在使用你的方法时候,为什么能正常显示那些页码和总页数等,但实际选择“下一页”或选任意页面的时候,页数链接是对的,但真实的数据却没有变动,还是默认的第一页的那些显示数据,为什么?


Q:在控制器里new一个Page对象时,其构造方法有三个参数,第一个是当前页码,第二个是每页显示记录数,第三个是URL地址,这个很重要,它直接指明页码上要跳到的地址。例如:
Page page = new Page(pageIndex,20,"articleList.do");
则 jsp显示出来的页码上会出现:articleList.do?pageIndex=1,articleList.do?pageIndex=2,articleList.do?pageIndex=... 这样的连接。新改版的标签库支持伪静态URL地址,例如:
Page page = new Page(pageIndex,20,"articleList_*.do");
则jsp显示出来的页码上会出现:articleList_1.html,articleList_2.html,articleList_3.html,...这样的连接。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值