12.2. Hibernate

我们将首先从Hibernate (http://www.hibernate.org/)开始,通过讲解Hibernate在Spring环境中的使用来阐述Spring框架对于O/R Mapping工具的整合方式。 本章节将涉及到许多细节问题,并向你展示各种不同的DAO实现方式和事务划分。这其中的绝大多数模式能够被Spring支持的其他O/R Mapping工具所使用。 这一章节的其他部分将为你讲述其他的O/R Mapping工具,并给出一些简短的例子。

下面的讨论将主要集中在Hibernate 3:这也是目前最新版本的Hibernate产品。对于Spring已经支持的Hibernate 2.x也将被继续支持。 在下面的例子中都使用了Hibernate 3的类与配置。而所有这些示例(绝大多数)都对Hibernate 2.x依然有效,只要你使用相应的Hibernate 2.x 支持包: org.springframework.orm.hibernate。在这个包中,有类似org.springframework.orm.hibernate3包中对应于Hibernate 2.x的支持。 除此之外,为了遵循Hibernate 3在包结构上的改变,所有使用 org.hibernate 作为包结构的类实例需要替换成 net.sf.hibernate 从而适应那些包结构的改变(正如在示例中的一样)。

12.2.1. 资源管理

典型的业务程序经常会被重复的资源管理代码搞得混乱。很多项目都试图创建自己的方案来解决这个问题,有时会为了编程方便而牺牲恰当的错误处理。 对于恰当的资源管理,Spring提倡一种瞩目而又简洁的解决方案:使用模板化的IoC,诸如基础构建类、回调接口以及使用AOP拦截器。 基础构建类负责恰当的资源处理,以及将特定的异常代码转换为unchecked exception体系。Spring引进了DAO异常体系,可适用于任何数据访问策略。 对于直接使用JDBC的情况,前面章节提到的 JdbcTemplate 类负责处理connection,并正确地把 SQLException 变为 DataAccessException 体系,包括将与数据库相关的SQL错误代码变成有意义的异常类。 Spring同时通过他们各自的事务管理器支持JTA和JDBC事务。

Spring同样也提供了对Hibernate和JDO的支持,包括 HibernateTemplate / JdoTemplate 类似于 JdbcTemplateHibernateInterceptor / JdoInterceptor 以及一个 Hibernate / JDO 事务管理器。 这样做的目的是为了能够清晰地划分应用程序层次而不管使用何种数据访问和事务管理技术,从而降低各个应用程序对象之间的耦合。 业务逻辑不再依赖于特定的数据访问与事务策略;不再有硬编码的资源查找、不再有难以替换的singletons、不再有用户自定义的服务注册。 Spring提供了一个简单且稳固的方案使得各种应用逻辑对象连接在一起,使这些对象可重用,并尽可能不依赖容器。 所有的数据访问技术都能独立使用,但是他们在Spring提供的基于XML配置且无需依赖Spring的普通JavaBean下会与application Context整合的更好。 在典型的Spring应用程序中,很多重要的对象都是JavaBeans:数据访问template、数据访问对象(使用template)、事务管理器、业务逻辑对象(使用数据访问对象和事务管理器)、web视图解析器、web控制器(使用业务对象)等等。

12.2.2. 在Spring的application context中创建 SessionFactory

为了避免硬编码的资源查找与应用程序对象紧密耦合,Spring允许你在application context中以bean的方式定义诸如JDBC DataSource或者Hibernate SessionFactory 的数据访问资源。 任何需要进行资源访问的应用程序对象只需要持有这些事先定义好的实例的引用(DAO定义在下一章节介绍),下面的代码演示如何创建一个JDBC DataSource 和Hibernate SessionFactory

<beans>

  <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
  </bean>

  <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="mappingResources">
      <list>
        <value>product.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.MySQLDialect
      </value>
    </property>
  </bean>

   ...
</beans>

将一个本地定义的,如Jakarta Commons DBCP的 BasicDataSource 切换为一个JNDI定位的DataSource(通常由J2EE Server管理),仅仅需要改变配置:

<beans>

  <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myds"/>
  </bean>

  ...
</beans>

你也可以访问一个JNDI定位的Hibernate SessionFactory,通过使用Spring的 JndiObjectFactoryBean 来暴露和获取。 当然,如果在EJB上下文之外,这是不必要的。

12.2.3.  HibernateTemplate

对于特定的数据访问对象或业务对象的方法来说,基本的模板编程模型看起来像下面所示的代码那样。 对于这些外部对象来说,没有任何实现特定接口的要求,仅仅要求提供一个Hibernate SessionFactory。 它可以从任何地方得到,不过比较适宜的方法是从Spring的application context中得到的bean引用:通过简单的 setSessionFactory(..) 这个bean的setter方法。 下面的代码展示了在application context中一个DAO的定义,它引用了上面定义的 SessionFactory,同时展示了一个DAO方法的具体实现。

<beans>
  ...

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

</beans>
public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(final String category) throws DataAccessException {
        HibernateTemplate ht = new HibernateTemplate(this.sessionFactory);
        return (Collection) ht.execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Query query = session.createQuery(
                    "from test.Product product where product.category=?");
                query.setString(0, category);
                return query.list();
            }
        });
    }
}

一个回调实现能够有效地在任何Hibernate数据访问中使用。HibernateTemplate 会确保当前Hibernate的 Session 对象的正确打开和关闭,并直接参与到事务管理中去。 Template实例不仅是线程安全的,同时它也是可重用的。因而他们可以作为外部对象的实例变量而被持有。对于那些简单的诸如find、load、saveOrUpdate或者delete操作的调用,HibernateTemplate 提供可选择的快捷函数来替换这种回调的实现。 不仅如此,Spring还提供了一个简便的 HibernateDaoSupport 基类,这个类提供了 setSessionFactory(..) 方法来接受一个 SessionFactory 对象,同时提供了 getSessionFactory()getHibernateTemplate() 方法给子类使用。 综合了这些,对于那些典型的业务需求,就有了一个非常简单的DAO实现:

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public Collection loadProductsByCategory(String category) throws DataAccessException {
        return getHibernateTemplate().find(
            "from test.Product product where product.category=?", category);
    }
}

12.2.4. 不使用回调的基于Spring的DAO实现

作为不使用Spring的 HibernateTemplate 来实现DAO的替代解决方案,你依然可以用传统的编程风格来编写你的数据访问代码。 无需将你的Hibernate访问代码包装在一个回调中,只需符合Spring的通用的 DataAccessException 异常体系。 Spring的 HibernateDaoSupport 基类提供了访问与当前事务绑定的 Session 对象的函数,因而能保证在这种情况下异常的正确转化。 类似的函数同样可以在 SessionFactoryUtils 类中找到,但他们以静态方法的形式出现。 值得注意的是,通常将一个false作为参数(表示是否允许创建)传递到 getSession(..) 方法中进行调用。 此时,整个调用将在同一个事务内完成(它的整个生命周期由事务控制,避免了关闭返回的 Session 的需要)。

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public Collection loadProductsByCategory(String category)
            throws DataAccessException, MyException {

        Session session = getSession(getSessionFactory(), false);
        try {
            List result = session.find(
                "from test.Product product where product.category=?",
                category, Hibernate.STRING);
            if (result == null) {
                throw new MyException("invalid search result");
            }
            return result;
        }
        catch (HibernateException ex) {
            throw convertHibernateAccessException(ex);
        }
    }
}

这种直接使用Hibernate访问代码的主要好处在于它允许你在数据访问代码中抛出checked exception,而 HibernateTemplate 却受限于回调中的unchecked exception。 注意,你通常可以将这些应用程序的异常处理推迟到回调函数之后,这样,你依然可以正常使用 HibernateTemplate。 一般来说,HibernateTemplate 所提供的许多方法在许多情况下看上去更简单和便捷。

12.2.5. 基于Hibernate3的原生API实现DAO

Hibernate 3.0.1引入了一个新的特性:“带上下文环境的Session”。 这一特性使得Hibernate自身具备了每个事务绑定当前 Session 对象的功能。 这与Spring中每个Hibernate的 Session 与事务同步的功能大致相同。一个相应的基于原生的Hibernate API的DAO实现正如下例所示:

public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(String category) {
        return this.sessionFactory.getCurrentSession()
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list();
    }
}

这种Hibernate数据访问的风格与你在Hibernate文档和示例中见到的非常类似,除了DAO实现类中持有了一个 SessionFactory 的实例变量。 我们强烈推荐这种基于实例变量的DAO构建方式,而不是使用那种过去由Hibernate的示例程序中提到的 静态的 HibernateUtil 类。 (通常来说,不要在静态变量中保存任何资源信息除非 确实 有这个必要)。

上面我们所列出的DAO完全遵循IoC:它如同使用Spring的 HibernateTemplate 进行编程那样,适合在application context中进行配置。 具体来说,它使用了Setter注入;如果你愿意,完全可以使用Constructor注入方式替代。当然,这样的DAO还可以建立在一个普通的Java类中(诸如在Unit Test中): 仅仅需要初始化这个类, 调用 setSessionFactory(..) 方法设置你所期望的工厂资源。 以Spring的bean的定义方式,它看上去就像这样:

<beans>
  ...

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

</beans>

这种DAO访问方式的主要优势在于它仅仅依赖于Hibernate API本身而无需引入任何Spring的类。 从无入侵性的角度来看,这一点非常吸引人。同时,对于Hibernate开发人员来说也更自然。

然而,这样的DAO访问方式会抛出 HibernateException,这是一个无需声明或捕获的unchecked exception。 这意味着,DAO的调用者只能以普通的错误来处理这些异常,除非完全依赖Hibernate自身的异常体系。 因而,除非你将DAO的调用者绑定到具体的实现策略上去,否则你将无法捕获特定的异常原因(诸如乐观锁异常)。 这种折中平衡或许可以被接受,如果你的应用完全基于Hibernate或者无需进行特殊的异常处理。

幸运的是,Spring的 LocalSessionFactoryBean 可以在任何Spring的事务管理策略下, 提供对Hibernate的 SessionFactory.getCurrentSession() 函数的支持,它将返回当前受Spring事务管理(甚至是 HibernateTransactionManager 管理的)的 Session 对象。 当然,该函数的标准行为依然有效:返回当前与正在进行的JTA事务(无论是Spring的 JtaTransactionManager、EJB CMT或者普通的JTA)绑定的 Session 对象。

总体来说,DAO可以基于Hibernate3的原生API实现,同时,它依旧需要能够参与到Spring的事务管理中。 这对于那些已经对Hibernate非常熟悉的人来说很有吸引力,因为这种方式更加自然。 不过,此时的DAO将抛出HibernateException,因而,如果有必要的话,需要明确地去做由 HibernateException 到Spring的 DataAccessException 的转化。

12.2.6. 编程式的事务划分

我们可以在这些低层次的数据访问服务之上的应用程序进行更高层次的事务划分,从而让事务能够横跨多个操作。 这里对于相关的业务逻辑对象同样没有实现接口的限制,它只需要一个Spring的 PlatformTransactionManager。 同SessionFactory一样,它可以来自任何地方,但是最好是一个经由 setTransactionManager(..) 方法注入的bean的引用,正如使用 setProductDao 方法来设置 productDAO 一样。 下面演示了在Spring的application context中定义一个事务管理器和一个业务对象,以及具体的业务方法的实现:

<beans>
  ...

  <bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

  <bean id="myProductService" class="product.ProductServiceImpl">
    <property name="transactionManager" ref="myTxManager"/>
    <property name="productDao" ref="myProductDao"/>
  </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private PlatformTransactionManager transactionManager;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
        transactionTemplate.execute(
            new TransactionCallbackWithoutResult() {
                public void doInTransactionWithoutResult(TransactionStatus status) {
                    List productsToChange = productDAO.loadProductsByCategory(category);
                    // do the price increase... 
                }
            }
        );
    }
}

12.2.7. 声明式的事务划分

作为可选方案,我们可以使用Spring声明式的事务支持。声明式的事务支持通过配置Spring容器中的AOP Transaction Interceptor来替换事务划分的硬编码。 这将使你可以从每个业务方法中重复的事务划分代码中解放出来,真正专注于为你的应用添加有价值的业务逻辑代码。此外,类似传播行为和隔离级别等事务语义能够在配置文件中改变,而不会影响具体的业务对象的实现。

<beans>
    ...

  <bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

  <bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="product.ProductService"/>
    <property name="target">
        <bean class="product.DefaultProductService">
            <property name="productDao" ref="myProductDao"/>
        </bean>
    </property>
    <property name="interceptorNames">
      <list>
        <value>myTxInterceptor</value> <!-- the transaction interceptor (configured elsewhere) -->
      </list>
    </property>
  </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    // notice the absence of transaction demarcation code in this method 
				// Spring's declarative transaction infrastructure will be demarcating transactions on your behalf 
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDAO.loadProductsByCategory(category);
        ...
    }

    ...
}

Spring的 TransactionInterceptor 允许任何应用程序的checked exception在回调代码中抛出,而 TransactionTemplate 在回调中则严格要求被封装成unchecked exception。 TransactionTemplate 会在一个unchecked exception抛出时,或者当事务被应用程序通过 TransactionStatus 标记为rollback-only时触发一个数据库回滚操作。 TransactionInterceptor 默认进行同样的操作,但是它允许对每个方法配置自己的rollback策略。

下面列出的高级别的声明式的事务管理方案并没有使用 ProxyFactoryBean,它将使那些大量的业务对象需要被纳入到事务管理中时的配置变得非常简单。

注意

在你打算继续阅读下一部分之前,我们强烈推荐你想去阅读 第 9.5 节 “声明式事务管理” 这一章节。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

  <!-- SessionFactory, DataSource, etc. omitted -->

  <bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

  <aop:config>
    <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
  </aop:config>

  <tx:advice id="txAdvice" transaction-manager="myTxManager">
    <tx:attributes>
      <tx:method name="increasePrice*" propagation="REQUIRED"/>
      <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
      <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
  </tx:advice>

  <bean id="myProductService" class="product.SimpleProductService">
    <property name="productDao" ref="myProductDao"/>
  </bean>

</beans>

12.2.8. 事务管理策略

TransactionTemplateTransactionInterceptor 都将真正的事务处理委托给一个 PlatformTransactionManager 实例来处理。 在Hibernate应用中,它可以是一个 HibernateTransactionManager(对于单独一个的Hibernate SessionFactory 使用一个 ThreadLocalSession)或一个 JtaTransactionManager(代理给容器的JTA子系统)。 你甚至可以使用自定义的 PlatformTransactionManager 的实现。因而,在你的应用碰到了特定的分布式事务的部署需求时(类似将原来的Hibernate事务管理转变为JTA),仅仅需要改变配置而已:简单将Hibernate的事务管理器替换成JTA事务实现。 任何的事务划分和数据访问的代码都无需因此而改变,因为他们仅仅使用了通用的事务管理API。

对于横跨多个Hibernate SessionFacotry的分布式事务,只需简单地将 JtaTransactionManager 同多个 LocalSessionFactoryBean 的定义结合起来作为事务策略。 你的每一个DAO通过bean属性得到各自的 SessionFactory 引用。如果所有的底层JDBC数据源都是支持事务的容器,那么只要业务对象使用了 JtaTransactionManager 作为事务策略,它就可以横跨多个DAO和多个session factories来划分事务,而不需要做任何特殊处理。

<beans>

  <bean id="myDataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName value="java:comp/env/jdbc/myds1"/>
  </bean>

  <bean id="myDataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myds2"/>
  </bean>

  <bean id="mySessionFactory1" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource1"/>
    <property name="mappingResources">
      <list>
        <value>product.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.MySQLDialect
        hibernate.show_sql=true
      </value>
    </property>
  </bean>

  <bean id="mySessionFactory2" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource2"/>
    <property name="mappingResources">
      <list>
        <value>inventory.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.OracleDialect
      </value>
    </property>
  </bean>

  <bean id="myTxManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory1"/>
  </bean>

  <bean id="myInventoryDao" class="product.InventoryDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory2"/>
  </bean>

  <!-- this shows the Spring 1.x style of declarative transaction configuration -->
  <!-- it is totally supported, 100% legal in Spring 2.x, but see also above for the sleeker, Spring 2.0 style -->
  <bean id="myProductService"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="myTxManager"/>
    <property name="target">
      <bean class="product.ProductServiceImpl">
        <property name="productDao" ref="myProductDao"/>
        <property name="inventoryDao" ref="myInventoryDao"/>
      </bean>
    </property>
    <property name="transactionAttributes">
      <props>
        <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
        <prop key="someOtherBusinessMethod">PROPAGATION_REQUIRES_NEW</prop>
        <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
      </props>
    </property>
  </bean>

</beans>

HibernateTransactionManagerJtaTransactionManager 都允许恰当的JVM级别的Hibernate缓存处理,而无需使用与容器相关的事务管理器或JCA连接器。(只要不是由EJB发起的事务)

对于特定的 DataSourceHibernateTransactionManager 能够为普通的JDBC访问代码提供Hibernate所使用的 Connection。 这一功能允许你可以完全混和使用Hibernate/JDBC进行数据访问而无需依赖JTA在高层次代码中进行事务划分,只要你依然访问的是同一个数据库! HibernateTransactionManager 能够自动地将Hibernate事务暴露为JDBC事务,如果你通过设置 DataSource来建立SessionFactory 对象(通过设置 LocalSessionFactoryBean 中的“dataSource”属性)。 另外一种配置方法是通过设置 HibernateTransactionManager 的“dataSource”属性,明确指定事务需要暴露的 DataSource

12.2.9. 容器资源 vs 本地资源

Spring的资源管理允许你简单地在一个JNDI的 SessionFactory 和一个本地的 SessionFactory 之间切换而无需更改任何一行应用程序代码。 把资源定义放在容器中还是放在应用程序本地中主要是由使用的事务策略决定的。与Spring定义的本地 SessionFactory 相比,一个手工注册的JNDI SessionFactory 并没有体现出多大的优势。 通过Hibernate的JCA连接器来部署一个 SessionFactory 提供了能使之参与到J2EE服务器管理架构的增值服务,不过除此之外也并没有增加实际的价值。

Spring对事务管理的支持有一个非常重要的好处:它不依赖于任何容器。使用非JTA的任何其他事务策略的配置,程序也能在独立的测试环境下正常工作。 尤其对于那些典型的单数据库事务情况下,这将是一个非常轻量级而又强大的JTA替代方案。当你使用一个本地的EJB SLSB来驱动事务时,即时你可能只访问一个数据库而且仅仅通过CMT使用SLSB的声明性事务,你仍然要依赖于EJB容器和JTA。 使用编程式JTA替换方案依然需要J2EE环境,JTA不仅对于JTA本身,还对于JNDI的 DataSource 实例引入了容器依赖。 对于非Spring环境的JTA驱动的Hibernate事务,你必须使用Hibernate JCA连接器或者额外的Hibernate事务代码及TransactionManagerLookup配置,来恰当地处理JVM级别的缓存。

Spring驱动的事务管理对于本地定义的 SessionFactory 能够工作得非常好,就像使用本地的JDBC DataSource 一样,当然前提必须是访问单个数据库。 因此当你真正面对分布式事务的需求时,可以马上回到Spring的JTA事务。必须要注意,一个JCA连接器需要特定容器的部署步骤,而且首先容器要支持JCA。 这要比使用本地资源定义和Spring驱动事务来部署一个简单的Web应用麻烦得多。而且你通常需要特定企业版本的容器,但是像类似WebLogic的Express版本并不提供JCA。 一个仅使用本地资源并且针对一个数据库的事务操作的Spring应用将可以在任何一种J2EE的Web容器中工作(不需要JTA、JCA或者EJB),比如Tomcat、Resin甚至普通的Jetty。 除此之外,这样的中间层可以在桌面应用或测试用例中被简单地重用。

考虑一下所有的情况:如果你不使用EJB,坚持使用本地 SessionFactory 以及Spring的 HibernateTransactionManager 或者 JtaTransactionManager, 你将获得包括适当的JVM级别上的缓存以及分布式事务在内的所有好处,而不会有任何容器部署的麻烦。通过JCA连接器的Hibernate的 SessionFactory 的JNDI注册仅仅在EJB中使用会带来好处。

12.2.10. 在应用服务器中使用Hibernate的注意点

在一些具有非常严格的XADataSource实现的JTA环境中(目前来说只是WebLogic和WebSphere的某些版本)当你将Hibernate配置成不感知JTA PlatformTransactionManager 对象时,容器可能会在应用服务器日志中报出一些警告和异常。 这些警告和异常通常会说:“正在被访问的连接不再有效,或者JDBC连接不再有效,可能由于事务不再处于激活状态”。下面是一个从WebLogic上抛出的异常:

java.sql.SQLException: The transaction is no longer active - status: 'Committed'.
   No further JDBC access is allowed within this transaction.

这样的错误警告很容易解决:只要简单的把Hibernate配置成感知到JTA PlatformTransactionManager 实例的存在即可,同时将他们进行同步(与Spring同步)。可以有两种方法达到这种效果:

  • 如果你已经在你的application context中定义并获取了JTA PlatformTransactionManager 对象(或许来自通过 JndiObjectFactoryBean 得到的JNDI)并已经将它注入到类似Spring的 JtaTransactionManager 中, 那么最简单的方式就是指定这个bean的引用作为 LocalSessionFactoryBeanjtaTransactionManager 属性。Spring将使这个对象被Hibernate所感知。

  • 多数情况下,你还没有得到JTA的 PlatformTransactionManager 实例(由于Spring的 JtaTransactionManager 能够自己找到它),所以你需要自行配置Hibernate并直接寻找资源。 正如Hibernate文档中提到的,这可以通过在Hibernate配置一个应用服务器特定的 TransactionManagerLookup 类来完成。

对于恰当的使用方法,你无需了解更多的东西。但是我们在这里将描述一下,对于将Hibernate配置为感知或者不感知JTA的 PlatformTransactionManager 对象这两种情况下,整个事务的执行顺序。

Hibernate被配置成不感知JTA PlatformTransactionManager 的情况下,当一个JTA事务提交时,整个事件的执行顺序如下:

  • JTA 事务提交

  • Spring的 JtaTransactionManager 同步到JTA事务,它被JTA事务管理器通过调用 afterCompletion 执行回调。

  • 在所有其他活动中,这将由Spring向Hibernate触发一个回调,通过Hibernate的 afterTransactionCompletion 回调(用于清除Hibernate缓存),然后紧跟着一个明确的Hibernate Session的 close() 调用。 这将使Hibernate试图去关闭JDBC的Connection。

  • 在某些环境中,Connection.close() 的调用将会触发一个警告或者错误信息。 这是由于特定的应用服务器将不再考虑Connection的可用性,因为此时事务已经被提交了。

Hibernate被配置成感知JTA的 PlatformTransactionManager 的情况下,当一个JTA事务提交时,整个事件的执行顺序如下:

  • JTA 事务准备提交

  • Spring的 JtaTransactionManager 同步到JTA事务,因而它被JTA事务管理器通过调用 beforeCompletion 执行回调。

  • Spring能感知到Hibernate自身被同步到JTA Transaction,因而表现得与先前那种情况有所不同。 假设Hibernate的 Session 需要被关闭,Spring将负责关闭它。

  • JTA 事务提交

  • Hibernate将与JTA transaction进行同步,因而它被JTA事务管理器通过调用 afterCompletion 执行回调,并且适时地清除缓存。