<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>sunchaohui_koko</title>
    <description></description>
    <link>http://sunchaohui-koko.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>如何使用spring的作用域：</title>
        <author>sunchaohui_koko</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://sunchaohui-koko.javaeye.com">sunchaohui_koko</a>&nbsp;
          链接：<a href="http://sunchaohui-koko.javaeye.com/blog/206340" style="color:red;">http://sunchaohui-koko.javaeye.com/blog/206340</a>&nbsp;
          发表时间: 2008年06月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          如何使用spring的作用域：<br />&lt;bean id="role" class="spring.chapter2.maryGame.Role" scope="singleton"/><br />这里的scope就是用来配置spring bean的作用域，它标识bean的作用域。<br />在spring2.0之前bean只有2种作用域即：singleton(单例)、non-singleton（也称prototype）, Spring2.0以后，增加了session、request、global session三种专用于Web应用程序上下文的Bean。因此，默认情况下Spring2.0现在有五种类型的Bean。当然，Spring2.0对Bean的类型的设计进行了重构，并设计出灵活的Bean类型支持，理论上可以有无数多种类型的Bean，用户可以根据自己的需要，增加新的Bean类型，满足实际应用需求。<br />1、singleton作用域<br />当一个bean的作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例，并且所有对bean的请求，只要id与该bean定义相匹配，则只会返回bean的同一实例。换言之，当把一个bean定义设置为singleton作用域时，Spring IOC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存（singleton cache）中，并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例，这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的，单例设计模式表示一个ClassLoader中只有一个class存在，而这里的singleton则表示一个容器对应一个bean，也就是说当一个bean被标识为singleton时候，spring的IOC容器中只会存在一个该bean。<br />配置实例：<br />&lt;bean id="role" class="spring.chapter2.maryGame.Role" scope="singleton"/> <br />或者<br />&lt;bean id="role" class="spring.chapter2.maryGame.Role" singleton="true"/><br />2、prototype<br /> prototype作用域部署的bean，每一次请求（将其注入到另一个bean中，或者以程序的方式调用容器的getBean()方法）都会产生一个新的bean实例，相当与一个new的操作，对于prototype作用域的bean，有一点非常重要，那就是Spring不能对一个prototype bean的整个生命周期负责，容器在初始化、配置、装饰或者是装配完一个prototype实例后，将它交给客户端，随后就对该prototype实例不闻不问了。不管何种作用域，容器都会调用所有对象的初始化生命周期回调方法，而对prototype而言，任何配置好的析构生命周期回调方法都将不会被调用。清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源，都是客户端代码的职责。（让Spring容器释放被singleton作用域bean占用资源的一种可行方式是，通过使用bean的后置处理器，该处理器持有要被清除的bean的引用。）<br />配置实例：<br />&lt;bean id="role" class="spring.chapter2.maryGame.Role" scope="prototype"/><br />或者<br />&lt;beanid="role" class="spring.chapter2.maryGame.Role" singleton="false"/><br /> <br />3、request <br />request表示该针对每一次HTTP请求都会产生一个新的bean，同时该bean仅在当前HTTP request内有效，配置实例：<br />request、session、global session使用的时候首先要在初始化web的web.xml中做如下配置：<br />如果你使用的是Servlet 2.4及以上的web容器，那么你仅需要在web应用的XML声明文件web.xml中增加下述ContextListener即可：<br /> &lt;web-app><br /> ...<br />  &lt;listener><br />&lt;listener-class>org.springframework.web.context.request.RequestContextListener&lt;/listener-class><br /> &lt;/listener><br /> ...<br />&lt;/web-app><br />，如果是Servlet2.4以前的web容器,那么你要使用一个javax.servlet.Filter的实现：<br />&lt;web-app><br /> ..<br /> &lt;filter><br />    &lt;filter-name>requestContextFilter&lt;/filter-name> <br />    &lt;filter-class>org.springframework.web.filter.RequestContextFilter&lt;/filter-class><br /> &lt;/filter> <br /> &lt;filter-mapping><br />    &lt;filter-name>requestContextFilter&lt;/filter-name> <br />    &lt;url-pattern>/*&lt;/url-pattern><br /> &lt;/filter-mapping><br />  ...<br />&lt;/web-app><br />接着既可以配置bean的作用域了：<br />&lt;bean id="role" class="spring.chapter2.maryGame.Role" scope="request"/><br />4、session<br />session作用域表示该针对每一次HTTP请求都会产生一个新的bean，同时该bean仅在当前HTTP session内有效，配置实例：<br />配置实例：<br />和request配置实例的前提一样，配置好web启动文件就可以如下配置：<br />&lt;bean id="role" class="spring.chapter2.maryGame.Role" scope="session"/><br />5、global session<br />global session作用域类似于标准的HTTP Session作用域，不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念，它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果你在web中使用global session作用域来标识bean，那么web会自动当成session类型来使用。<br />配置实例：<br />和request配置实例的前提一样，配置好web启动文件就可以如下配置：<br />&lt;bean id="role" class="spring.chapter2.maryGame.Role" scope="global session"/><br />6、自定义bean装配作用域<br />在spring2.0中作用域是可以任意扩展的，你可以自定义作用域，甚至你也可以重新定义已有的作用域（但是你不能覆盖singleton和prototype），spring的作用域由接口org.springframework.beans.factory.config.Scope来定义，自定义自己的作用域只要实现该接口即可，下面给个实例：<br />我们建立一个线程的scope，该scope在表示一个线程中有效，代码如下：<br />publicclass MyScope implements Scope { <br />     privatefinal ThreadLocal threadScope = new ThreadLocal() {<br />          protected Object initialValue() {<br />            returnnew HashMap(); <br />          } <br />    }; <br />     public Object get(String name, ObjectFactory objectFactory) { <br />        Map scope = (Map) threadScope.get(); <br />        Object object = scope.get(name); <br />        if(object==null) { <br />          object = objectFactory.getObject(); <br />          scope.put(name, object); <br />        } <br />        return object; <br />     } <br />     public Object remove(String name) { <br />        Map scope = (Map) threadScope.get(); <br />        return scope.remove(name); <br />     }<br />     publicvoid registerDestructionCallback(String name, Runnable callback) { <br />     }<br />    public String getConversationId() {<br />       // TODO Auto-generated method stub<br />       returnnull;<br />    } <br />          }
          <br/>
          <span style="color:red;">
            <a href="http://sunchaohui-koko.javaeye.com/blog/206340#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 20 Jun 2008 15:59:54 +0800</pubDate>
        <link>http://sunchaohui-koko.javaeye.com/blog/206340</link>
        <guid>http://sunchaohui-koko.javaeye.com/blog/206340</guid>
      </item>
      <item>
        <title>11.4  持久层设计</title>
        <author>sunchaohui_koko</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://sunchaohui-koko.javaeye.com">sunchaohui_koko</a>&nbsp;
          链接：<a href="http://sunchaohui-koko.javaeye.com/blog/205214" style="color:red;">http://sunchaohui-koko.javaeye.com/blog/205214</a>&nbsp;
          发表时间: 2008年06月18日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          章节试读·支持作者从购买正版开始 『 阅读首页 』 <br /><br />作者、书名、出版社 关键字：  <br /><br /><br /><br />第11章 Spring 2.0实战:Live在线书店(节选)  11.4 持久层设计 <br /><br />上一页      返回书目      比价购买      下一页<br />　　<br />11.4  持久层设计<br />在第5章中,我们已经初步介绍了Hibernate这个功能强大的ORM框架,Live在线书店仍然采用Hibernate作为持久化解决方案。DAO模式仍是持久层的标准模式,不同的是,我们不采用Spring提供的现成的DAO体系,而是设计一个类型安全的泛型DAO,通过泛型DAO,能够将公共代码以范型方式放入范型DAO超类中,从而进一步减少代码量。<br /><br />对于每一个Domain Object来说,至少要实现基本的CRUD操作,即Create(创建)、Retrieve(获取)、Update(更新)和Delete(删除)4种操作。我们将这4种操作全部以泛型方式实现,大大简化了各个子类的编码,同时还获得了类型安全的特性。<br /><br />泛型DAO的核心是定义一个GenericDao接口,申明CRUD操作。<br /><br />public interface GenericDao&lt;T> {<br /><br />    // 通过主键查询T:<br /><br />    T query(Serializable id);<br /><br />    // 创建新的T:<br /><br />    void create(T t);<br /><br />    // 删除T:<br /><br />    void delete(T t);<br /><br />    // 更新T:<br /><br />    void update(T t);<br /><br />}<br /><br />然后,提供一个默认的GenericHibernateDao实现类,实现所有的CRUD操作,但不必实现GenericDao接口。<br /><br />public abstract class GenericHibernateDao&lt;T> {<br /><br />    private final Class&lt;T> clazz;<br /><br />    protected HibernateTemplate hibernateTemplate;<br /><br />    public GenericHibernateDao(Class&lt;T> clazz) {<br /><br />        this.clazz = clazz;<br /><br />    }<br /><br />    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {<br /><br />        this.hibernateTemplate = hibernateTemplate;<br /><br />    }<br /><br />    // 根据主键查询T:<br /><br />    public T query(Serializable id) {<br /><br />        // 用get()而不用load()是因为load()方法返回的是延迟加载的对象,<br /><br />        // 可能造成LazyInitializationException:<br /><br />        T t = (T)hibernateTemplate.get(clazz, id);<br /><br />        if(t==null)<br /><br />            throw new DataRetrievalFailureException("Object not found.");<br /><br />        return t;<br /><br />    }<br /><br />    // 创建T:<br /><br />    public void create(T t) {<br /><br />        hibernateTemplate.save(t);<br /><br />    }<br /><br />    // 删除T:<br /><br />    public void delete(T t) {<br /><br />        hibernateTemplate.delete(t);<br /><br />    }<br /><br />    // 更新T:<br /><br />    public void update(T t) {<br /><br />        hibernateTemplate.update(t);<br /><br />    }<br /><br />}<br /><br />GenericHibernateDao受到Hibernate的唯一限制是必须获得域对象的Class实例,由于无法直接调用T.class,因此,一个变通的方法是从构造方法的参数中传入Class实例,对于子类的继承会稍微麻烦一点。<br /><br />对于真正的DAO接口,由GenericDao接口扩展,保证了类型安全。例如,对于BookDao,由于扩展自GenericDao&lt;Book>,因此,定义的CRUD操作即为类型安全的,此外,还可以定义其他查询操作。<br /><br />public class BookDaoImpl extends GenericHibernateDao&lt;Book><br /><br />        implements BookDao {<br /><br />    public BookDaoImpl() {<br /><br />        super(Book.class);<br /><br />    }<br /><br />    // 基本的CRUD操作已经实现了!<br /><br />    // 定义额外的query操作:<br /><br />    public List&lt;Book> query(final Category c, final Page page) {<br /><br />        // TODO:<br /><br />    }<br /><br />}<br /><br />BookDao的实现类BookDaoImpl实现了BookDao接口,这是类型安全的。此外,BookDaoImpl还从GenericHibernateDao扩展而来,因此,基本的CRUD操作已经全部实现了,BookDaoImpl只需实现BookDao额外定义的查询操作。由于Spring提供的HibernateTemplate已被注入到GenericHibernateDao中,因此,BookDaoImpl可以直接使用HibernateTemplate来实现额外定义的查询操作。<br /><br />这个泛型DAO的详细模式如图11-11所示。<br /><br /><br /><br />泛型DAO模式的最大的好处是消除了每个DAO对象中重复的CRUD操作,这些重复的CRUD操作被统一放入GenericHibernateDao,以泛型方式实现了。<br /><br />子类如果不希望继承超类的某个方法,例如,CommentDaoImpl不希望客户端去调用update()方法,就可以简单地覆盖它,直接抛出UnsupportedOperationException异常。<br /><br />@Override<br /><br />public void update(Comment t) {<br /><br />    throw new UnsupportedOperationException();<br /><br />}<br /><br />将覆写的方法加上注解@Override,维护源代码时,很容易知道该方法覆盖了超类的相同签名的方法。使用Eclipse的菜单命令“Source”→“Override/Implements Methods...”生成的方法签名就会被自动标记为@Override。这是在Java 5中的一种良好的编程习惯。<br /><br />在持久层中,我们一共定义了5个DAO对象,其层次关系如图11-12所示。<br /><br /><br /><br />图11-12<br /><br />HibernateTemplate对象是注入到GenericHibernateDao&lt;T>中的,因此,所有的实现类都可以直接引用。注意到我们没有对FavoriteBook和OrderItem对象定义DAO操作,这两个对象的相关操作分别被定义在BookDao和OrderDao中。<br /><br />现在我们设计好了各个DAO组件,下一步就需要在Spring的XML配置文件中装配起来。对于持久层来说,需要装配的一共有以下组件。<br /><br />(1)DataSource:通过Spring提供的DriverManagerDataSource,我们可以很容易地配置一个DataSource供开发和测试使用。在实际部署时,在服务器上配置好DataSource,然后应用JndiObjectFactoryBean获得DataSource即可。<br /><br />&lt;bean id="dataSource"<br /><br />    class="org.springframework.jdbc.datasource.DriverManagerDataSource"><br /><br />    &lt;property name="driverClassName" value="${jdbc.driver}" /><br /><br />    &lt;property name="url" value="${jdbc.url}" /><br /><br />    &lt;property name="username" value="${jdbc.username}" /><br /><br />    &lt;property name="password" value="${jdbc.password}" /><br /><br />&lt;/bean><br /><br />JDBC连接的配置信息放在外部jdbc.properties文件中,应用第3章介绍的PropertyPlaceholderConfigurer可以很容易地引入到Spring的配置文件中。<br /><br />(2)SessionFactory:使用AnnotationSessionFactoryBean可以直接在Spring中配置一个SessionFactory,而不必使用Hibernate特有的hibernate.cfg.xml配置文件。<br /><br />&lt;bean id="sessionFactory"  class="org.springframework.orm.hibernate3. annotation.AnnotationSessionFactoryBean"><br /><br />    &lt;property name="dataSource" ref="dataSource" /><br /><br />    &lt;property name="annotatedClasses"><br /><br />        &lt;list><br /><br />            &lt;value>net.livebookstore.domain.Account&lt;/value><br /><br />            &lt;value>net.livebookstore.domain.Book&lt;/value><br /><br />            &lt;value>net.livebookstore.domain.Category&lt;/value><br /><br />            &lt;value>net.livebookstore.domain.Comment&lt;/value><br /><br />            &lt;value>net.livebookstore.domain.FavoriteBook&lt;/value><br /><br />            &lt;value>net.livebookstore.domain.Order&lt;/value><br /><br />            &lt;value>net.livebookstore.domain.OrderItem&lt;/value><br /><br />        &lt;/list><br /><br />    &lt;/property><br /><br />    &lt;property name="annotatedPackages"><br /><br />        &lt;list><br /><br />            &lt;value>net.livebookstore.domain&lt;/value><br /><br />        &lt;/list><br /><br />    &lt;/property><br /><br />    &lt;property name="hibernateProperties"><br /><br />        &lt;props><br /><br />            &lt;prop key="hibernate.dialect"><br /><br />                net.livebookstore.hibernate.CustomSQLDialect<br /><br />            &lt;/prop><br /><br />            &lt;prop key="hibernate.show_sql">true&lt;/prop><br /><br />            &lt;prop key="hibernate.jdbc.fetch_size">10&lt;/prop><br /><br />            &lt;prop key="hibernate.cache.provider_class"><br /><br />                org.hibernate.cache.HashtableCacheProvider<br /><br />            &lt;/prop><br /><br />        &lt;/props><br /><br />    &lt;/property><br /><br />    &lt;property name="eventListeners"><br /><br />        &lt;map><br /><br />            &lt;entry key="pre-update"><br /><br />                &lt;bean class="org.hibernate.validator.event.ValidatePreUpdate EventListener" /><br /><br />            &lt;/entry><br /><br />            &lt;entry key="pre-insert"><br /><br />                &lt;bean class="org.hibernate.validator.event.ValidatePreInsert EventListener" /><br /><br />            &lt;/entry><br /><br />        &lt;/map><br /><br />    &lt;/property><br /><br />&lt;/bean><br /><br />(3)HibernateTemplate:由于我们的每个DAO组件并没有从Spring的Hibernate DaoSupport中派生,因此,需要定义一个HibernateTemplate实例,然后注入到每个DAO组件中。<br /><br />&lt;bean id="hibernateTemplate"<br /><br />    class="org.springframework.orm.hibernate3.HibernateTemplate"><br /><br />    &lt;property name="sessionFactory" ref="sessionFactory" /><br /><br />    &lt;property name="fetchSize" value="10" /><br /><br />&lt;/bean><br /><br />(4)HibernateTransactionManager:用于管理Hibernate事务,在这里我们只需配置这个Bean,就可以直接使用声明式事务管理。<br /><br />&lt;bean id="transactionManager"<br /><br />    class="org.springframework.orm.hibernate3.HibernateTransactionManager"><br /><br />    &lt;property name="sessionFactory" ref="sessionFactory"/><br /><br />&lt;/bean><br /><br />(5)各DAO组件:由于我们直接在GenericHibernateDao中通过XDoclet注释注入了HibernateTemplate:<br /><br />public abstract class GenericHibernateDao&lt;T> {<br /><br />    protected HibernateTemplate hibernateTemplate;<br /><br />    /**<br /><br />     * @spring.property name="hibernateTemplate" ref="hibernateTemplate"<br /><br />     */<br /><br />    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {<br /><br />        this.hibernateTemplate = hibernateTemplate;<br /><br />    }<br /><br />    ...<br /><br />}<br /><br />因此,在各个DAO的定义处加上@spring.bean的注释,再运行Ant,即可自动生成DAO组件的配置信息并自动注入HibernateTemplate。<br /><br />11.4.1  与运算(&)的实现<br />由于Hibernate 3.2不支持“&”运算,但实际上大部分数据库都支持“&”运算,例如,MySQL支持“a & b”,而HSQLDB是通过“BITAND(a, b)”函数提供的“&”运算。因此,我们需要扩展Hibernate 3.2,使其支持“&”运算,这样才能根据Category对象获得当前分类及其子类的所有书籍。<br /><br />虽然Hibernate也支持直接执行原始的SQL语句,但是这样就丧失了O/R Mapping的能力,并且需要更多的转化工作。我们希望能直接在HQL语句中支持“&”运算。幸运的是,Hibernate框架的设计非常具有扩展性。Hibernate对不同数据库的“方言”支持就可以解析某种数据库的特定SQL函数,我们只需要利用Hibernate的自定义函数机制,自行编写一个bitand()函数,将其解析为数据库对应的SQL语句,即可实现“&”运算。<br /><br />Hibernate通过SQLFunction接口实现自定义SQL函数,我们定义一个BitAndFunction如下。<br /><br />public class BitAndFunction implements SQLFunction {<br /><br />    // 根据需要返回SQL数据类型:<br /><br />    public Type getReturnType(Type type, Mapping mapping) {<br /><br />        return Hibernate.INTEGER;<br /><br />    }<br /><br />    public boolean hasArguments() {<br /><br />        return true;<br /><br />    }<br /><br />    public boolean hasParenthesesIfNoArguments() {<br /><br />        return true;<br /><br />    }<br /><br />    public String render(List args, SessionFactoryImplementor factory) throws QueryException {<br /><br />        if (args.size() != 2) {<br /><br />            throw new IllegalArgumentException("BitAndFunction requires 2 arguments!"); <br /><br />        }<br /><br />        return args.get(0).toString() + " & " + args.get(1).toString();<br /><br />    }<br /><br />}<br /><br />对于HQL语句,上述自定义SQL函数会将“bitand(a,b)”翻译成“a & b”,这样,大多数支持“&”运算的数据库就可以正确执行。<br /><br />由于HSQLDB比较特殊,它不是通过“&”实现的与运算,而是提供了一个BITAND()函数,因此,再定义一个HsqlBitAndFunction。<br /><br />public class HsqlBitAndFunction extends BitAndFunction {<br /><br />    public String render(List args, SessionFactoryImplementor factory) throws QueryException {<br /><br />        return "BITAND(" + args.get(0).toString() + ", " + args.get(1).toString() + ")";<br /><br />    }<br /><br />}<br /><br />现在,我们需要将自定义函数注册到Hibernate中,最简单的方法是从相应的方言派生一个自定义的CustomSQLDialect,然后在构造方法中注册该BitAndFunction。<br /><br />public class CustomSQLDialect extends HSQLDialect {<br /><br />    public CustomSQLDialect() {<br /><br />        super();<br /><br />        LogFactory.getLog(CustomSQLDialect.class).info("Register bitand function for bit-and operation. (e.g.: where a & b = :c)");<br /><br />        if(HSQLDialect.class.isAssignableFrom(getClass()))<br /><br />            registerFunction("bitand", new HsqlBitAndFunction());<br /><br />        else<br /><br />            registerFunction("bitand", new BitAndFunction());<br /><br />    }<br /><br />}<br /><br />在Spring的Hibernate相关配置中,将dialect指定为CustomSQLDialect就可以实现“&”运算。<br /><br />11.4.2  分页的实现<br />分页是查询时最常见的操作。如果一次查询的数据过多,就很有必要分页显示给用户,一是因为一次查询数据量如果太大,例如,上万条记录,会对服务器造成很大的负担;二是用户希望看到的往往是最关心的少量数据,因此,应当尽量在前几页让用户看到他们最关心的数据。<br /><br />实现分页查询的关键是获得所有符合条件的记录总数,这样就能根据每页的数量计算出页数。为此,我们设计一个Page对象,初始化每页需要显示的记录数和要显示的页号。<br /><br />public class Page {<br /><br />    public static final int DEFAULT_PAGE_SIZE = 10;<br /><br />    private int pageIndex;<br /><br />    private int pageSize;<br /><br />    private int totalCount;<br /><br />    private int pageCount;<br /><br />    public Page(int pageIndex, int pageSize) {<br /><br />        if(pageIndex&lt;1)<br /><br />            pageIndex = 1;<br /><br />        if(pageSize&lt;1)<br /><br />            pageSize = 1;<br /><br />        this.pageIndex = pageIndex;<br /><br />        this.pageSize = pageSize;<br /><br />    }<br /><br />    public Page(int pageIndex) {<br /><br />        this(pageIndex, DEFAULT_PAGE_SIZE);<br /><br />    }<br /><br />    public int getPageIndex() { return pageIndex; }<br /><br />    public int getPageSize() { return pageSize; }<br /><br />    public int getPageCount() { return pageCount; }<br /><br />    public int getTotalCount() { return totalCount; }<br /><br />    public int getFirstResult() { return (pageIndex-1)*pageSize; }<br /><br />    public boolean getHasPrevious() { return pageIndex>1; }<br /><br />    public boolean getHasNext() { return pageIndex&lt;pageCount; }<br /><br />    public void setTotalCount(int totalCount) {<br /><br />        this.totalCount = totalCount;<br /><br />        pageCount = totalCount / pageSize + (totalCount%pageSize==0 ? 0 : 1);<br /><br />        if(totalCount==0) {<br /><br />            if(pageIndex!=1)<br /><br />                throw new IndexOutOfBoundsException("Page index out of range.");<br /><br />        }<br /><br />        else {<br /><br />            if(pageIndex>pageCount)<br /><br />                throw new IndexOutOfBoundsException("Page index out of range.");<br /><br />        }<br /><br />    }<br /><br />    public boolean isEmpty() {<br /><br />        return totalCount==0;<br /><br />    }<br /><br />}<br /><br />Page在初始化时就确定了pageSize和pageIndex属性,待查询到记录总数后,通过setTotalCount()方法就可以确定页数,从而通过getFirstResult()返回第一行记录的起始位置。<br /><br />为了在Hibernate中实现分页查询,还需要几个辅助方法。我们将这几个辅助方法放到GenericHibernateDao中,以方便子类调用。<br /><br />queryForObject()方法用于执行一次查询,并返回一个唯一结果。<br /><br />protected Object queryForObject(final String select, final Object[] values) {<br /><br />    HibernateCallback selectCallback = new HibernateCallback() {<br /><br />        public Object doInHibernate(Session session) {<br /><br />            Query query = session.createQuery(select);<br /><br />            if(values!=null) {<br /><br />                for(int i=0; i&lt;values.length; i++)<br /><br />                    query.setParameter(i, values[i]);<br /><br />            return query.uniqueResult();<br /><br />        }<br /><br />    };<br /><br />    return hibernateTemplate.execute(selectCallback);<br /><br />}<br /><br />queryForList(String, Object[], Page)方法实现一个分页查询。<br /><br />protected List queryForList(final String select, final Object[] values, final Page page) {<br /><br />    HibernateCallback selectCallback = new HibernateCallback() {<br /><br />        public Object doInHibernate(Session session) {<br /><br />            Query query = session.createQuery(select);<br /><br />            if(values!=null) {<br /><br />                for(int i=0; i&lt;values.length; i++)<br /><br />                    query.setParameter(i, values[i]);<br /><br />            }<br /><br />            return query.setFirstResult(page.getFirstResult())<br /><br />                        .setMaxResults(page.getPageSize())<br /><br />                        .list();<br /><br />        }<br /><br />    };<br /><br />    return (List) hibernateTemplate.executeFind(selectCallback);<br /><br />}<br /><br />另一个queryForList(String, String, Object[], Page)重载方法实现了一个完整的分页查询,第一个查询定义了如何获得记录总数,然后填充到Page对象,再根据第二个查询获得指定页的记录。<br /><br />protected List queryForList(final String selectCount, final String select, final Object[] values, final Page page) {<br /><br />    Long count = (Long)queryForObject(selectCount, values);<br /><br />    page.setTotalCount(count.intValue());<br /><br />    if(page.isEmpty())<br /><br />        return Collections.EMPTY_LIST;<br /><br />    return queryForList(select, values, page);<br /><br />}<br /><br />例如,要查询一本书的评论,由于评论可能很多,因此需要进行分页显示。queryComments()方法实现了这个功能。<br /><br />public List&lt;Comment> queryComments(Book book, Page page) {<br /><br />    return queryForList(<br /><br />        "select count(*) from Comment as c where c.book=?",<br /><br />        "select c from Comment as c where c.book=? order by c.createdDate desc",<br /><br />        new Object[] {book},<br /><br />        page<br /><br />    );<br /><br />}<br /><br />第一个“select count(*) from ...”查询定义了如何获得评论的总数,第二个“select from ...”查询根据Page对象的pageIndex和pageSize取出相应页面的评论。为了便于使用,这些辅助方法均定义在GenericHibernateDao中,子类可以方便地调用它们。<br /><br />需要注意的是,与Hibernate 3.1及其以前的版本不同,从Hibernate 3.2开始,使用count()等SQL函数返回的数据类型从Integer改为Long,这是为了兼容JPA标准。<br /><br />对于Hibernate来说,还提供了Criteria查询,通过DetachedCriteria,可以先定义查询,然后关联Session执行查询。Criteria可以通过投影操作方便地获得记录的总数,但是,投影操作和查询的Order条件是冲突的。为了实现通过一条DetachedCriteria同时得到记录总数和对应页数的记录,可以通过反射实现。为此,封装一个PaginationCriteria。<br /><br />class PaginationCriteria {<br /><br />    private static Field orderEntriesField = getField(Criteria.class, "orderEntries");<br /><br />    public static List query(Criteria c, Page page) {<br /><br />        // first we must detect if any Order defined:<br /><br />        // Hibernate code: private List orderEntries = new ArrayList();<br /><br />        List _old_orderEntries = (List)getFieldValue(orderEntriesField, c);<br /><br />        boolean restore = false;<br /><br />        if(_old_orderEntries.size()>0) {<br /><br />            restore = true;<br /><br />            setFieldValue(orderEntriesField, c, new ArrayList());<br /><br />        }<br /><br />        c.setProjection(Projections.rowCount());<br /><br />        int rowCount = ((Long)c.uniqueResult()).intValue();<br /><br />        page.setTotalCount(rowCount);<br /><br />        if(rowCount==0) {<br /><br />            // no need to execute query:<br /><br />            return Collections.EMPTY_LIST;<br /><br />        }<br /><br />        // query:<br /><br />        if(restore) {<br /><br />            // restore order conditions:<br /><br />            setFieldValue(orderEntriesField, c, _old_orderEntries);<br /><br />        }<br /><br />        return c.setFirstResult(page.getFirstResult())<br /><br />                .setMaxResults(page.getPageSize())<br /><br />                .setFetchSize(page.getPageSize())<br /><br />                .list();<br /><br />    }<br /><br />    private static Field getField(Class clazz, String fieldName) {<br /><br />        try {<br /><br />            return clazz.getDeclaredField(fieldName);<br /><br />        }<br /><br />        catch (Exception e) {<br /><br />            throw new RuntimeException(e);<br /><br />        }<br /><br />    }<br /><br />    private static Object getFieldValue(Field field, Object obj) {<br /><br />        field.setAccessible(true);<br /><br />        try {<br /><br />            return field.get(obj);<br /><br />        }<br /><br />        catch (Exception e) {<br /><br />            throw new RuntimeException(e);<br /><br />        }<br /><br />    }<br /><br />    private static void setFieldValue(Field field, Object target, Object value) {<br /><br />        field.setAccessible(true);<br /><br />        try {<br /><br />            field.set(target, value);<br /><br />        }<br /><br />        catch (Exception e) {<br /><br />            throw new RuntimeException(e);<br /><br />        }<br /><br />    }<br /><br />}<br /><br />然后,就可以通过一个DetachedCriteria实现分负查询。<br /><br />protected List queryForList(final DetachedCriteria dc, final Page page) {<br /><br />    HibernateCallback callback = new HibernateCallback() {<br /><br />        public Object doInHibernate(Session session) {<br /><br />            Criteria c = dc.getExecutableCriteria(session);<br /><br />            if(page==null)<br /><br />                return c.list();<br /><br />            return PaginationCriteria.query(c, page);<br /><br />        }<br /><br />    };<br /><br />    return hibernateTemplate.executeFind(callback);<br /><br />}<br /><br />我们定义的DAO对象全部是线程安全的,因此,可以在Spring中只定义一个实例,然后放心地在多个组件之间共享。所谓线程安全是指多个线程可以安全地执行某个对象实例的全部方法。由于方法的参数和方法内定义的局部变量的引用都存储在线程的堆栈中,因此各个线程之间互不影响。只有实例的字段是共享的,因此,只要保证实例的字段一经初始化就不再变化,这个实例就是线程安全的。<br /><br />GenericHibernateDao定义的字段只有Class和HibernateTemplate,其中,Class被定义为final类型,而HibernateTemplate一旦初始化完毕就不会更改,因此,这是一个线程安全的类,其子类只要保证各自的字段在运行期不变,也都是线程安全的。<br /><br />11.4.3  调试HQL语句<br />由于我们使用了Hibernate作为持久化机制,因此,在DAO中,大量使用了HQL查询。如何调试这些HQL语句是应用程序开发中必须要解决的,否则,我们只能一遍一遍地修改代码,再编译,重新运行。<br /><br />调试HQL语句有两个方法,一是打开Hibernate的“hibernate.show_sql”功能,就可以在控制台看到Hibernate最终生成的所有SQL语句,不过这仍然不太直观,因此,Hibernate还提供了HibernateTools插件,可以在Eclipse中方便地调试HQL。<br /><br />将HibernateTools-3.2.0.beta7.zip解压到Eclipse的安装目录,然后重新启动Eclipse,选择菜单“Window”→“Show View”→“Other...”,在弹出的对话框中找到Hibernate组,展开就可以看到HibernateTools插件提供的视图,如图11-13所示。<br /><br />还可以直接选择菜单“Window”→“Open Perspective”→“Other...”,打开Hibernate Console视角,如图11-14所示。<br /><br />在“Hibernate Configurations”视图中单击按钮添加一个Hibernate配置,在弹出的“Create Hibernate Console Configuration”对话框中填入如下内容。<br /><br />Name:livebookstore;<br /><br />Configuration file:\livebookstore\conf\unused\hibernate.cfg.xml;<br /><br />选中“Enable hibernate ejb3/annotations”。<br /><br />在Classpath中添加目录/livebookstore/web/WEB-INF/classes和jar文件/livebookstore/ lib/core/hsqldb.jar。<br /><br />然后,在Hibernate Configurations视图中选中livebookstore,单击右键,在弹出的快捷菜单中选择“HQL Editor”,即可输入HQL语句并查看运行结果,如图11-15所示。<br /><br /><br /><br />图11-15<br /><br /><br /><br />上一页      返回书目      比价购买      下一页
          <br/>
          <span style="color:red;">
            <a href="http://sunchaohui-koko.javaeye.com/blog/205214#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 18 Jun 2008 16:32:50 +0800</pubDate>
        <link>http://sunchaohui-koko.javaeye.com/blog/205214</link>
        <guid>http://sunchaohui-koko.javaeye.com/blog/205214</guid>
      </item>
      <item>
        <title>Struts2 Map 映射</title>
        <author>sunchaohui_koko</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://sunchaohui-koko.javaeye.com">sunchaohui_koko</a>&nbsp;
          链接：<a href="http://sunchaohui-koko.javaeye.com/blog/191915" style="color:red;">http://sunchaohui-koko.javaeye.com/blog/191915</a>&nbsp;
          发表时间: 2008年05月11日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          最近写了一段JSP，目的是想完成一段关系映射，选用了Map,如下。<br /><pre name="code" class="java">
&lt;s:iterator value="acList" id="item" status="stat">
	&lt;s:select name="reMap[%{#item.acUniqueId}]" list="pFormList" listKey="id" listValue="displayName" value="%{reMap[#item.acUniqueId]}" cssStyle="width:150px;font-size: 12px;">
	&lt;/s:select>
&lt;/s:iterator>
</pre><br />但是奇怪的是，当reMap的key([%{#item.acUniqueId}])太长的时候，Map就莫名其妙的错了。譬如有reMap[1234567890]=6就不能映射了，而reMap[1234]=4就可以。有那位能够解答一下呢？
          <br/>
          <span style="color:red;">
            <a href="http://sunchaohui-koko.javaeye.com/blog/191915#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 11 May 2008 11:50:06 +0800</pubDate>
        <link>http://sunchaohui-koko.javaeye.com/blog/191915</link>
        <guid>http://sunchaohui-koko.javaeye.com/blog/191915</guid>
      </item>
      <item>
        <title>http://unique5945.javaeye.com/blog/125924</title>
        <author>sunchaohui_koko</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://sunchaohui-koko.javaeye.com">sunchaohui_koko</a>&nbsp;
          链接：<a href="http://sunchaohui-koko.javaeye.com/blog/180876" style="color:red;">http://sunchaohui-koko.javaeye.com/blog/180876</a>&nbsp;
          发表时间: 2008年04月09日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <a href="http://unique5945.javaeye.com/blog/125924" target="_blank">http://unique5945.javaeye.com/blog/125924</a>
          <br/>
          <span style="color:red;">
            <a href="http://sunchaohui-koko.javaeye.com/blog/180876#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 09 Apr 2008 16:00:10 +0800</pubDate>
        <link>http://sunchaohui-koko.javaeye.com/blog/180876</link>
        <guid>http://sunchaohui-koko.javaeye.com/blog/180876</guid>
      </item>
  </channel>
</rss>