12.3. JDO

Spring支持标准的JDO 1.0/2.0 API作为数据访问策略,同样遵循类似于Spring对Hibernate的支持风格。对应的支持与整合类位于 org.springframework.orm.jdo 包中。

12.3.1. 建立PersistenceManagerFactory

Spring提供了一个 LocalPersistenceManagerFactoryBean 类,允许你通过Spring的application context来定义一个本地的JDO PersistenceManagerFactory

<beans>

  <bean id="myPmf" class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean">
    <property name="configLocation" value="classpath:kodo.properties"/>
  </bean>

</beans>

作为可选方案,PersistenceManagerFactory 也可以通过直接实例化一个 PersistenceManagerFactory 的实现类得到。 一个JDO PersistenceManagerFactory 的实现类遵循JavaBeans的模式,它非常像一个JDBC DataSource 的实现类,这使得它天然的非常适合作为一个Spring的bean定义。 这种创建风格通常支持一个Spring定义的JDBC DataSource,将它传入到对应的实现类的connectionFactory的属性中进行bean的创建。具体的例子参见开源的JDO实现JPOX(http://www.jpox.org):

<beans>

 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
   <property name="driverClassName" value="${jdbc.driverClassName}"/>
   <property name="url" value="${jdbc.url}"/>
   <property name="username" value="${jdbc.username}"/>
   <property name="password" value="${jdbc.password}"/>
 </bean>

 <bean id="myPmf" class="org.jpox.PersistenceManagerFactoryImpl" destroy-method="close">
   <property name="connectionFactory" ref="dataSource"/>
   <property name="nontransactionalRead" value="true"/>
 </bean>

</beans>

一个JDO的 PersistenceManagerFactory 能够同样在一个J2EE应用服务器的JNDI环境下被创建。 这通常由特定的JDO实现所提供的JCA连接器来完成。Spring标准的 JndiObjectFactoryBean 也能够被用来获取和暴露这个 PersistenceManagerFactory。 当然,通常在一个EJB环境之外,在JNDI中持有 PersistenceManagerFactory 的实例没有什么特别吸引人的好处,因而我们一般只在有非常充足的理由时选择这种建立方式。 请参看Hibernate章节中“容器资源 vs 本地资源”这一节的讨论,那里的讨论同样适用于JDO。

12.3.2. JdoTemplateJdoDaoSupport

每一个基于JDO的DAO类都需要通过IoC来注入一个 PersistenceManagerFactory。 这样的DAO类可以在 PersistenceManagerFactory 的帮助下来操作原生的JDO API进行编程, 但是通常来说我们更愿意使用Spring提供的 JdoTemplate

<beans>
  
  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="persistenceManagerFactory" ref="myPmf"/>
  </bean>
  
</beans>
public class ProductDaoImpl implements ProductDao {
  
    private JdoTemplate jdoTemplate;

    public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
        this.jdoTemplate = new JdoTemplate(pmf);
    }

    public Collection loadProductsByCategory(final String category) throws DataAccessException {
        return (Collection) this.jdoTemplate.execute(new JdoCallback() {
            public Object doInJdo(PersistenceManager pm) throws JDOException {
                Query query = pm.newQuery(Product.class, "category = pCategory");
                query.declareParameters("String pCategory"); 
                List result = query.execute(category);
                // do some further stuff with the result list
                return result;
            }
        });
    }
}

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

public class ProductDaoImpl extends JdoDaoSupport implements ProductDao {
  
    public Collection loadProductsByCategory(String category) throws DataAccessException {
        return getJdoTemplate().find(
            Product.class, "category = pCategory", "String category", new Object[] {category});
    }
}

作为不使用Spring的 JdoTemplate 来实现DAO的替代解决方案,你依然可以通过在原生JDO API的级别对那些基于Spring的DAO进行编程,此时你必须明确地打开和关闭一个 PersistenceManager。 正如在相应的Hibernate章节描述的一样,这种做法的主要优点在于你的数据访问代码可以在整个过程中抛出checked exceptions。JdoDaoSupport 为这种情况提供了多种函数支持, 包括获取和释放一个具备事务管理功能的 PersistenceManager 和相关的异常转化。

12.3.3.  基于原生的JDO API实现DAO

我们可以直接操作JDO API来实现DAO,通过直接使用一个注入的 PersistenceManagerFactory,而无需对Spring产生的任何依赖。 一个相应的DAO实现看上去就像下面这样:

public class ProductDaoImpl implements ProductDao {

    private PersistenceManagerFactory persistenceManagerFactory;

    public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
        this.persistenceManagerFactory = pmf;
    }

    public Collection loadProductsByCategory(String category) {
        PersistenceManager pm = this.persistenceManagerFactory.getPersistenceManager();
        try {
            Query query = pm.newQuery(Product.class, "category = pCategory");
            query.declareParameters("String pCategory"); 
            return query.execute(category);
        }
        finally {
          pm.close();
        }
    }
}

上面我们所列出的DAO完全遵循IoC,它如同使用Spring的 JdoTemplate 进行编码那样,非常适合在Spring容器中进行配置:

<beans>

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="persistenceManagerFactory" ref="myPmf"/>
  </bean>

</beans>

这类DAO的主要问题在于他们每次总是从工厂中得到一个新的 PersistenceManager 实例。为了依然能够访问受到Spring管理的、具备事务管理功能的 PersistenceManager, 不妨考虑一下在目标 PersistenceManagerFactory 之前, 定义一个 TransactionAwarePersistenceManagerFactoryProxy(Spring已经提供), 然后将这个代理注入到你的DAO中去。

<beans>

  <bean id="myPmfProxy"
      class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy">
    <property name="targetPersistenceManagerFactory" ref="myPmf"/>
  </bean>

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="persistenceManagerFactory" ref="myPmfProxy"/>
  </bean>

</beans>

这样,你的数据访问代码就可以通过 PersistenceManagerFactory.getPersistenceManager() 方法得到一个具备事务管理功能的 PersistenceManager。 而这一方法将通过代理类的处理:在从工厂获取一个新的 PersistenceManager 实例之前检查一下当前具备事务管理功能的 PersistenceManager, 如果这是一个具备事务管理功能的 PersistenceManagerclose() 调用在此时将被忽略。

如果你的数据访问代码总是在一个处于活跃状态的事务中执行(或者至少在一个活跃的事务同步中), 那么你能够非常安全地忽略 PersistenceManager.close() 的调用和整个 finally 块的代码。这将使你的DAO实现变得比较简洁:

public class ProductDaoImpl implements ProductDao {

    private PersistenceManagerFactory persistenceManagerFactory;

    public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
        this.persistenceManagerFactory = pmf;
    }

    public Collection loadProductsByCategory(String category) {
        PersistenceManager pm = this.persistenceManagerFactory.getPersistenceManager();
        Query query = pm.newQuery(Product.class, "category = pCategory");
        query.declareParameters("String pCategory"); 
        return query.execute(category);
    }
}

对于这种依赖于活跃事务的DAO,比较推荐的做法是将 TransactionAwarePersistenceManagerFactoryProxy 中的"allowCreate"标志关闭,从而强制活跃事务。

<beans>

  <bean id="myPmfProxy"
      class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy">
    <property name="targetPersistenceManagerFactory" ref="myPmf"/>
    <property name="allowCreate" value="false"/>
  </bean>

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="persistenceManagerFactory" ref="myPmfProxy"/>
  </bean>

</beans>

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

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

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

12.3.4. 事务管理

将事务管理纳入到Service操作的执行中,你可以使用Spring通用的声明式的事务管理功能, 参加下面的例子:

<?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.5.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

  <bean id="myTxManager" class="org.springframework.orm.jdo.JdoTransactionManager">
    <property name="persistenceManagerFactory" ref="myPmf"/>
  </bean>

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

  <tx:advice id="txAdvice" transaction-manager="txManager">
    <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>

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

</beans>

注意,JDO要求你必须在一个活跃的事务中修改一个持久化对象。 与Hibernate相比,在JDO中并没有一种类似脱离事务刷出(non-transactional flush)的概念。 基于这种原因,你所选择的JDO实现需要被建立在特定的环境中,尤其是它需要为JTA同步做明确的创建, 由此来自行检测一个JTA事务。这一点对于本地事务不是必要的,由于它已经被Spring的 JdoTransactionManager 处理,但是对于需要参与到JTA事务中的情况,是必须的 (无论是由Spring的 JtaTransactionManager、EJB CMT或者普通的JTA所驱动的事务)。

JdoTransactionManager 能够将一个JDO事务暴露给访问相同的JDBC DataSource 的JDBC访问代码。前提条件是,被注册的 JdoDialect 能够支持获取底层的JDBC Connection。 这一功能默认被基于JDBC的JDO 2.0 实现。对于JDO 1.0的实现,必须注册一个用户自定义的 JdoDialect。具体参见下一节有关 JdoDialect 的机制。

12.3.5. JdoDialect

作为高级特性,JdoTemplateinterfacename 都支持一个用户自定义的 JdoDialect 作为“jdoDialect”的bean属性进行注入。 在这种情况下,DAO将不再接收 PersistenceManagerFactory 的引用作为参数, 而是接收一个完整的 JdoTemplate 实例(也就是将它注入到 JdoDaoSupport 的"jdoTemplate"属性中去)。一个 JdoDialect 实现能够激活一些由Spring支持的高级特性,这通常由特定的实现供应商指定:

  • 执行特定的事务语义(例如用户自定义的事务隔离级别和事务超时)

  • 获取具备事务功能的JDBC Connection (暴露给基于JDBC的DAO)

  • 应用查询超时功能(自动地从Spring管理的事务超时中计算)

  • 及时刷出 PersistenceManager (使得事务变化对于基于JDBC的数据访问代码可见)

  • JDOExceptions 到Spring的 DataAccessExceptions 的高级转化

这对于JDO 1.0的实现有特别的价值,由于这些特性都没有在其标准的API中包含。 对于JDO 2.0,其中的绝大多数的特性已经以标准的方式被支持。因而,Spring的 DefaultJdoDialect 在默认情况下(Spring 1.2后)使用相应的JDO 2.0 API函数。对于特殊的事务语义和异常的高级转化这样的高级特性, 获取和使用JDO实现供应商特定的 JdoDialect 子类还是比较有价值的。

更多有关它的操作以及它如何在Spring的JDO支持中使用的详细信息请参看 JdoDialect 的Javadoc。