第 11 章 使用ORM工具进行数据访问

11.1. 简介

Spring在资源管理,DAO实现支持以及实物策略等方面提供了与Hibernate, JDO和iBATIS SQL映射的集成。 对Hibernate,Spring使用了很多IoC的方便的特性提供了一流的支持,帮助你处理很多典型的Hibernate整合的问题。所有的这些都遵守Spring通用的事务和DAO异常体系.

当您选择使用O/R映射来创建数据访问应用程序的时候,Spring的增加部分就会向您提供重要的支持。首先你应该了解的是,一旦你使用了Spring对O/R映射的支持,你不需要亲自作所有的事情。在决定花费力气,冒着风险建造类似的内部底层结构之前,我们都建议您考虑和利用Spring的解决方案。不管你使用的是何种技术,大部分的O/R映射支持都可以以library样式被使用,因为所有的东西都是被设计成一组可重复利用的JavaBeans。在ApplicationContext和BeanFactory中使用更是提供了配置和部署简单的好处,因此,这一章里的大多数例子都是在ApplicationContext中配置。

使用Spring构建你的ORM应用的好处包括:

  • 避免绑定特殊的技术,允许mix-and-match的实现策略。 虽然Hibernate非常强大,灵活,开源而且免费,但它还是使用了自己的特定的API。此外有人也许会争辩:iBatis更轻便而且在不需要复杂的O/R映射策略的应用中使用也很优秀。能够选择的话,使用标准或抽象的API来实现主要的应用需求,通常是更好的。尤其,当你可能会因为功能,性能或其他方面的原因而需要切换到另一个实现的时候。举例来说,Spring对Hibernate事务和异常的抽象,以及能够让你轻松交换mapper和DAO对象(实现数据访问功能)的IoC机制,这两个特性可以让你在不牺牲Hibernate性能的情况下,在你的应用程序中隔离Hibernate的相关代码。处理DAO的高层次的service代码不需要知道DAO的具体实现。这个方法可以很容易使用mix-and-match方案互不干扰地实现数据访问层(比如在一些地方用Hibernate,一些地方使用JDBC,其他地方使用iBatis),mix-and-match有利于处理遗留下来的代码以及利用各种技术(JDBC,Hibernate,iBatis)的长处.

  • 测试简单. Spring的IoC使得很容易替换掉不同的实现,Hibernate SessionFacotory的位置,datasource, 事务管理, 映射对象的实现。这样就很容易隔离测试持久化相关代码的各个部分。

  • 通用的资源管理。 Spring的application context能够处理诸如Hibernate 的SessionFactory, JDBC的datasource,iBatis的SQLMaps配置对象以及其他相关资源的定位和配置。这使得这些配置的值很容易被管理和修改。Spring提供了有效,简单和安全的Hibernate Session处理。一般的使用Hibernate的代码则需要使用同一个Hibernate Session对象以确保有效和恰当地事务处理。而Spring让我们可以很容易透明地创建和绑定一个session到当前线程;你可以使用以下两种办法之一:声明式的AOP方法拦截器,或通过使用一个外部的template包装类在Java代码层次实现。这样,Spring就解决了在很多Hibernate论坛上出现的使用问题。

  • 异常包装。 Spring能够包装Hibernate异常,把它们从专有的,checked exception变为一组抽象的runtime exception。这样你就可以仅仅在恰当的层处理大部分的不可恢复的异常,使你避免了很多讨厌的catch/throw以及异常声明。你还是可以在你需要的地方捕捉和处理异常。回想一下JDBC异常(包括与DB相关的方言)被转变为同样的异常体系,这就意味着你可以在一致的编程模型中处理JDBC操作。

  • 综合的事务管理 。 Spring允许你包装你的ORM代码,通过使用声明式的AOP方法拦截器或者在代码级别使用外部的template包装类。不管使用哪一种,事务相关的语义都会为你处理,万一有异常发生也会帮你做适当的事务操作(比如rollback)。就象我们下面要讨论的一样,你能够使用和替换各种transaction managers,却不会使你的Hibernate相关的代码受到影响。更好的是,JDBC相关的代码可以完全和Hibernate代码integrate transactionaly。这对于处理那些没有用Hibernate或iBatis实现的功能非常有用。

11.2. Hibernate

11.2.1. 资源管理

典型的应用经常会被重复的资源管理代码搞胡乱。 很多项目尝试创造自己的方案解决这个问题,有时会为了编程方便牺牲适当的故障处理。 对于恰当的资源处理Spring提倡令人瞩目的简单的解决方案:使用templating的IoC, 比如基础的class和回调接口,或者提供AOP拦截器。基础的类负责固定的资源处理, 以及将特定的异常转换为unchecked异常体系。Spring引进了DAO异常体系,可适用于任何数据访问策略。 对于直接使用JDBC的情况,前面章节提到的JdbcTemplate类负责处理connection, 正确地把SQLException 变为DataAccessException 体系(包括将与数据库相关的SQL错误代码变成有意义的异常类)。 它同时支持JTA和JDBC事务,通过它们各自的Spring transaction managers。 Spring同样也提供了对Hibernate和JDO的支持:一个HibernateTemplate/JdoTemplate类似于 JdbcTemplateHibernateInterceptor/JdoInterceptor,以及一个Hibernate/JDO transaction manager。 主要的目的是:能够清晰地划分应用层次而不管使用何种数据访问和事务技术;使应用对象之间的耦合松散。 业务对象(BO)不再依赖于数据访问和事务策略;不再有硬编码的资源lookup;不再有难于替换的singletons ;不再有自定义的服务注册。一个简单且坚固的方案连接了应用对象,并且使它们可重用尽可能地不依赖容器 。虽然所有的数据访问技术都能独立使用,但是与Spring application context结合更好一些, 它提供了基于xml的配置和普通的与Spring 无关的JavaBean实例。在典型的Spring app中, 很多重要的对象都是JavaBeans:数据访问template,数据访问对象(使用template),transaction managers, 业务对象(使用数据访问对象和transaction managers),web view resolvers, web controller(使用业务对象)等等。

11.2.2. 在 Application Context中的Bean 声明

为了避免将应用对象贴紧硬编码的资源lookup, Spring允许你像定义普通bean一样在application context中定义诸如 JDBC DataSource,Hibernate SessionFactory 的资源。 需要访问这些资源的应用对象只需要持有这些预定义实例的引用。 下面的代码演示如何创建一个JDBC DataSource和Hibernate SessionFactory : :

<beans>

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

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

   ...

</beans>

你可以将一个JNDI定位的DataSource换为一个本地定义的如DBCP的BasicDataSource,如下面的代码: :

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

当然你也可以把本地的SessionFactory换为JNDI定位的, 但是如果不是在EJB上下文中,这是不需要的。(查看“容器资源 vs 本地资源”一节)

11.2.3. 反向控制: 模板和回调的使用

对于可以成为定制的数据访问对象或业务对象的方法来说, 基本的模板编程模型看起来像下面所示的代码那样。 对于外部对象没有任何实现特定接口的要求,它只需要提供一个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 bean="mySessionFactory"/>
        </property>
    </bean>

    ...

</beans>
public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

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

    public List loadProductsByCategory(final String category) {
        HibernateTemplate hibernateTemplate =
            new HibernateTemplate(this.sessionFactory);

        return (List) hibernateTemplate.execute(
            new HibernateCallback() {
                public Object doInHibernate(Session session) throws HibernateException {
                    List result = session.find(
                        "from test.Product product where product.category=?",
                        category, Hibernate.STRING);
                    // do some further stuff with the result list
                    return result;
                }
            }
        );
    }
}

一个callback的实现能在任何的Hibernate数据访问中有效使用。 HibernateTemplate会确保Session正确的打开和关闭,并且会自动参与事务。 Template实例是线程安全的,可重用的,因此可以作为外部类的实例变量而被保持。 对于简单的一步操作,例如简单的find,load, saveOrUpdate,或者delete调用, HibernateTemplate提供了可选的简单方法可以用来替换这种一行的callback实现。 此外,Spring提供了一个简便的HibernateDaoSupport基类, 这个基类提供了setSessionFactory方法来接受一个SessionFactory,getSessionFactory 以及getHibernateTemplate方法是子类使用的。这样对于典型的需求就可以有很简单的DAO实现:

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public List loadProductsByCategory(String category) {
        return getHibernateTemplate().find(
            "from test.Product product where product.category=?", category,
            Hibernate.STRING);
    }
}

11.2.4. 利用AOP拦截器(Interceptor)取代Template

Spring的AOP HibernateInterceptor是作为HibernateTemplate的替换, 它在一个委托的try/catch块中直接写Hibernate代码, 以及在application context中的一个interceptor定义,从而代替callback的实现。 下面演示了在application context中DAO,interceptor以及proxy定义,同时还有一个DAO方法的具体实现:

<beans>

    ...

    <bean id="myHibernateInterceptor" 
        class="org.springframework.orm.hibernate.HibernateInterceptor">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

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

    <bean id="myProductDao" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>product.ProductDao</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myHibernateInterceptor</value>
                <value>myProductDaoTarget</value>
            </list>
        </property>
    </bean>

    ...

</beans>
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public List loadProductsByCategory(final String category) throws MyException {
        Session session = SessionFactoryUtils.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 SessionFactoryUtils.convertHibernateAccessException(ex);
        }
    }
}

这个方法必须要有一个HibernateInterceptor才能工作。 getSession方法中的”false”标志为了确保Session已经存在, 否则SessionFactoryUtils会创建一个新的。 如果线程中已经存在一个 SessionHolder比如一个HibernateTransactionManager事务 ,那SessionFactoryUtils 就肯定会自动参与进来。HibernateTemplate 在内部使用SessionFactoryUtils, 他们是一样的底层结构。 HibernateInterceptor最主要的优点是允许在数据访问代码中抛出checked应用异常, 而HibernateTemplate在回调中严格限制只能抛出未检查异常。 值得注意的是我们可以把各种check以及抛出应用异常推迟到回调之后。 Interceptor的主要缺点是它需要在context进行特殊的装配。HibernateTemplate的简便方法在很多场景下更简单些

11.2.5. 编程式的事务划分

在这些低层次的数据访问服务之上,可以在应用的高层次上划分事务, 让事务横跨多个操作。这里对于相关的业务对象(BO)同样没有实现接口的限制, 它只需要一个Spring的PlatformTransactionManager。同SessionFactory一样, 这个manager可以来自任何地方, 但是最好是作为一个经由setTransactionManager 方法设置的bean引用, 例如,用setProductDao方法来设置productDAO. 下面演示了在Srping application context中一个transaction manager和一个业务对象的定义, 以及具体的业务方法是如何实现的:

<beans>

    ...

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

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
    </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.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.execute(
            new TransactionCallbackWithoutResult() {
                public void doInTransactionWithoutResult(TransactionStatus status) {
                    List productsToChange = productDAO.loadProductsByCategory(category);
                    ...
                }
            }
        );
    }
}

11.2.6. 声明式的事务划分

作为可选的,我们可以使用Spring的AOP TransactionInterceptor来替换事务划分的手工代码, 这需要在application context中定义interceptor。 这个方案使得你可以把业务对象从每个业务方法中重复的事务划分代码中解放出来。 此外,像传播行为和隔离级别等事务概念能够在配置文件中改变,而不会影响业务对象的实现。

<beans>

    ...

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

    <bean id="myTransactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="transactionAttributeSource">
            <value>
                product.ProductService.increasePrice*=PROPAGATION_REQUIRED
                product.ProductService.someOtherBusinessMethod=PROPAGATION_MANDATORY
            </value>
        </property>
    </bean>

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

    <bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>product.ProductService</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myTransactionInterceptor</value>
                <value>myProductServiceTarget</value>
            </list>
        </property>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

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

    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDAO.loadProductsByCategory(category);
        ...
    }

    ...

}

HibernateInterceptor一样,TransactionInterceptor 允许任何checked的应用异常在callback代码中抛出, 而TransactionTemplate 在callback中严格要求unchecked异常。 TransactionTemplate 会在一个unchecked异常抛出时触发一个rollback, 或者当事务被应用程序通过TransactionStatus标记为rollback-only。 TransactionInterceptor默认进行同样的操作,但是它允许对每个方法配置rollback方针。 一个简便可选的创建声明式事务的方法是:TransactionProxyFactoryBean, 特别是在没有其他AOP interceptor牵扯到的情况下。对一个特定的目标bean, TransactionProxyFactoryBean用事务配置自己联合proxy定义。 这样就把配置工作减少为配置一个目标bean以及一个 proxy bean的定义(少了interceptor的定义)。 此外你也不需要指定事务方法定义在哪一个接口或类中。

<beans>

    ...

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

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

    <bean id="myProductService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="target">
            <ref bean="myProductServiceTarget"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
                <prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
            </props>
        </property>
    </bean>

</beans>

11.2.7. 事务管理策略

TransactionTemplateTransactionInterceptor 都是将真正的事务处理代理给一个PlatformTransactionManager实例, 比如在Hibernate应用中它可以是一个HibernateTransactionManager (对于单独一个的Hibernat SessionFactory, 实质上使用一个ThreadLocal的Session)或一个JtaTransactionManager (代理给容器的JTA子系统)。 你甚至可以使用自定义的PlatformTransactionManager的实现。 所以呢,如果你的应用需要分布式事务的时候, 将原来的Hibernate事务管理转变为JTA之类的,只不过是改变配置文件的事情。 简单地,将Hibernate transaction manager替换为Spring的JTA transaction实现。 事务的划分和数据访问代码则不需要改变,因为他们使用的是通用的事务管理API。 对于横跨多个Hibernate SessionFacotry的分布式事务, 只需简单地将JtaTransactionManager 同多个LocalSessionFactoryBean 定义结合起来作为一个事务策略。 你的每一个DAO通过bean属性得到各自的SessionFactory引用。 如果所有的底层JDBC datasource都是支持事务的容器, 那么只要一个业务对象使用了 JtaTransactionManager策略, 它就可以横跨多个DAO和多个session factories来划分事务,而不需要特殊的对待.

<beans>

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

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

    <bean id="mySessionFactory1" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
            </props>
        </property>
        <property name="dataSource">
            <ref bean="myDataSource1"/>
        </property>
    </bean>

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

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

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

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

    <bean id="myProductServiceTarget" class="product.ProductServiceImpl">
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
        <property name="inventoryDao">
            <ref bean="myInventoryDao"/>
        </property>
    </bean>

    <bean id="myProductService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="target">
            <ref bean="myProductServiceTarget"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
                <prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
            </props>
        </property>
    </bean>

</beans>

HibernateTransactionManagerJtaTransactionManager 都使用了与容器无关的Hibernate事务管理器的lookup或JCA连接器(只要事务不是用EJB发起的), 从而考虑到在适当JVM级别上的cache处理。 而且HibernateTransactionManager 能够为普通的JDBC访问代码输出JDBC Connection。 这就可以使得混合的Hibernate/JDBC数据访问可以不用JTA而在高层次上进行事务划分, 只要它们使用的是同一个数据库!

注释, 对于使用TransactionProxyFactoryBean 声明性划分事务属于可选方式,请参考for an alternative approach to using 第 7.4.1 节 “BeanNameAutoProxyCreator,另一种声明方式”.

11.2.8. 使用Spring管理的应用Bean

一个Spring application context的定义能够被很多种不同的上下文实现所读取, 比如FileSystemXmlApplicationContextClassPathXmlApplicationContext以及XmlWebApplicationContext。 默认的情况下,一个web应用程序会从”WEB-INF/applicationContext.xml”中得到root context。 在所有的Spring应用中,XML文件中定义的application context会把所有相关的application beans连接起来, 包括Hibernate session factory以及数据访问和业务对象(就像上面定义的那些bean)。 它们中的大部分都不会意识到被Spring容器所管理,甚至在同其他bean合作的时候, 因为它们仅仅遵循JavaBeans的规范。一个bean属性及可以表示值参数,也可以是其他的合作bean。 下面的bean定义能够作为Spring web MVC context的一部分,在最基础的 application context中访问business beans。

<bean id="myProductList" class="product.ProductListController">
    <property name="productService">
        <ref bean="myProductService"/>
    </property>
</bean>

Spring web controller所需要的所有业务或数据访问对象都是通过bean引用提供的, 所以在应用得上下文中就不再需要手动的bean lookup了。但是在与Struts一起使用, 或在一个EJB实现中甚至一个applet中使用,你仍然可以自己手动地look up一个bean (意思大概是Spring不会影响你原来的使用方式)。因此,Spring beans事实上能够在任何地方支持。 你只需要一个application context的引用,你可以在一个web 应用中通过一个servlet context的属性得到, 或者手动地从一个文件或class path resource创建实例。

ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
ProductService productService = (ProductService) context.getBean("myProductService");
ApplicationContext context =
    new FileSystemXmlApplicationContext("C:/myContext.xml");
ProductService productService =
    (ProductService) context.getBean("myProductService");
ApplicationContext context =
    new ClassPathXmlApplicationContext("myContext.xml");
ProductService productService =
    (ProductService) context.getBean("myProductService");

11.2.9. 容器资源 vs 本地资源

Spring的资源管理考虑到了在JNDI SessionFactory和local的SessionFactory之间的简单切换, 对于JNDI DataSource也是这样的,不需要修改一行代码。 把资源定义放在容器中还是放在应用程序本地中主要是由使用的事务策略决定的。 同Spring定义的本地SessionFactory相比,一个手动注册的JNDI SessionFactory并不会提供任何多余的好处。 如果通过Hibernate的JCA连接器进行注册,对于参与JTA事务有明显的好处, 尤其在EJB中。而Spring的transaction支持的一个重要好处就是不依赖于任何一个容器。 使用非JTA的策略配置,程序将会在独立的或测试的环境下同样正常工作。尤其在典型的单数据库事务情况下, 这将是JTA的轻便和强大的替换方案。当使用一个本地的EJB SLSB来驱动事务时, 尽管你可能只访问一个数据库而且仅仅通过CMT使用SLSBs的声明性事务,你仍然要依赖于EJB容器和JTA。 编程式使用JTA的替换方案依然需要J2EE环境。JTA不仅仅在自己这一方面而且在JNDI DataSource方面, 引入了容器依赖。对于非Spring的,JTA驱动的Hibernate事务,为了在适当的JVM层次上做caching, 你必须使用Hibernate JCA连接器或者额外的Hibernate事务代码及配置好的JTA事务。 Spring驱动的事务能够很好地使用本地定义的Hibernate SessionFacotry工作,就像使用本地的JDBC DataSource工作( 当然访问的必须是一个单独的数据库)。因此你只需要在面对分布式事务需求的时候退回到Spring的JTA事务策略。 必须要注意,一个JCA连接器需要特定容器的部署步骤,而且首先要支持JCA。 这要比使用本地资源定义和Spring驱动事务来部署一个简单的Web应用麻烦多了。 而且你通常需要企业版本的容器,比如WebLogic的Express版本并不提供JCA。 一个仅使用本地资源并且事务仅跨越一个数据库的Spring应用将会在任何一种J2EE Web容器中工作 (不需要JTA,JCA或者EJB),比如Tomcat,Resin, 甚至普通的Jetty。 更多的是,这样的中间层可以在桌面应用或测试用例中简单地重用。综上所述:如果你不使用EJB, 坚持使用本地SessionFactory的创建和Spring的HibernateTransactionManagerJtaTransactionManager。 你可得到包括适当的JVM层次上的caching以及分布式事务在内的所有好处,却不会有任何容器部署的麻烦。 通过JCA连接器的Hibernate SessionFactory 的JNDI注册仅仅在EJB中使用会带来好处。

11.2.10. 举例

Spring发行包中的Petclinic这个例子提供了可选的DAO实现和application context配置文件 (分别针对Hibernaet, JDBC, 和Apache OJB)。因此Petclinic可以作为可工作的示例应用, 阐明了在Spring web应用中如何使用Hibernate。它也利用了不同事务策略下的声明式事务划分。

11.3. JDO

ToDo

11.4. iBATIS

Spring通过org.springframework.orm.ibatis包来支持iBATIS SqlMaps 1.3.x和2.0。 iBATIS的支持非常类似于Hibernate的支持,它支持同样的template样式的编程, 而且同Hibernate一样,iBatis的支持也是和Spring的异常体系一起工作,他会让你喜欢上Spring所有的IoC特性。

11.4.1. 1.3.x和2.0的概览和区别

Spring对iBATIS SqlMaps1.3和2.0都提供了支持。首先让我们来看一看两个之间的区别。

表 11.1. iBATIS SqlMaps supporting classes for 1.3 and 2.0

Feature1.3.x2.0
Creation of SqlMapSqlMapFactoryBeanSqlMapClientFactoryBean
Template-style helper classSqlMapTemplateSqlMapClientTemplate
Callback to use MappedStatementSqlMapCallbackSqlMapClientCallback
Super class for DAOsSqlMapDaoSupportSqlMapClientDaoSupport

11.4.2. 创建SqlMap

使用iBATIS SqlMaps涉及创建包含语句和结果对应关系的配置文件。 Spring会负责使用SqlMapFactoryBeanSqlMapClientFactoryBean来读取这些配置文件, 其中后一个类是同SqlMaps2.0联合使用的。

		
public class Account {
	private String name;
	private String email;
	
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getEmail() {
		return this.email;
	}
	
	public void setEmail(String email) {
		this.email = email;
	}
}

假设我们要映射这个类,我们就要创建如下的SqlMap。通过使用查询,我们稍后可以用email addersses来取得users. Account.xml:

<sql-map name="Account">
	<result-map name="result" class="examples.Account">
		<property name="name" column="NAME" columnIndex="1"/>
		<property name="email" column="EMAIL" columnIndex="2"/>
	</result-map>
	
	<mapped-statement name="getAccountByEmail" result-map="result">
		select
			  ACCOUNT.NAME,
			  ACCOUNT.EMAIL
		from ACCOUNT
		where ACCOUNT.EMAIL = #value#
	</mapped-statement>
	
	<mapped-statement name="insertAccount">
		insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#)
	</mapped-statement>
</sql-map>

在定义完SqlMap之后,我们必须为iBATIS创建一个配置文件 (sqlmap-config.xml):

<sql-map-config>

	<sql-map resource="example/Account.xml"/>

</sql-map-config>

iBATIS会从classpath读取资源,所以要确保Account.xml文件在classpath上面。

用Spring,我们现在能够很容易地通过 SqlMapFactoryBean创建SqlMap:

<bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean">
	<property name="configLocation"><value>WEB-INF/sqlmap-config.xml</value></property>
</bean>

11.4.3. 使用 SqlMapDaoSupport

SqlMapDaoSupport类是类似于HibernateDaoSupportJdbcDaoSupport的支持类。让我们实现一个DAO:

public class SqlMapAccountDao extends SqlMapDaoSupport implements AccountDao {

	public Account getAccount(String email) throws DataAccessException {
		Account acc = new Account();
		acc.setEmail();
		return (Account)getSqlMapTemplate().executeQueryForObject("getAccountByEmail", email);
	}

	public void insertAccount(Account account) throws DataAccessException {
		getSqlMapTemplate().executeUpdate("insertAccount", account);
	}
}

正如你所看到的,我们使用SqlMapTemplate来执行查询。Spring会在创建如下所示的SqlMapAccountDao的时候已经使用SqlMapFactoryBean为我们初始化了SqlMap。一切都准备就绪了:

<!-- for more information about using datasource, have a look at the JDBC chapter -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
	<property name="url"><value>${jdbc.url}</value></property>
	<property name="username"><value>${jdbc.username}</value></property>
	<property name="password"><value>${jdbc.password}</value></property>
</bean>

<bean id="accountDao" class="example.SqlMapAccountDao">
	<property name="dataSource"><ref local="dataSource"/></property>
	<property name="sqlMap"><ref local="sqlMap"/></property>
</bean>

11.4.4. 事务管理

在使用iBATIS的应用程序中增加声明式事务管理是相当的简单。 基本上你所需要做的唯一事情是: 在你的application context中增加一个transaction manager并且使用诸如 TransactionProxyFactoryBean声明式地设定你的事务界限。 关于这方面的更多情况可以在第 7 章 事务管理中找到。

TODO elaborate!