Spring支持标准的JDO 1.0/2.0 API作为数据访问策略,同样遵循类似于Spring对Hibernate的支持风格。对应的支持与整合类位于 org.springframework.orm.jdo
包中。
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。
每一个基于JDO的DAO类都需要通过IoC来注入一个 PersistenceManagerFactory
,你可以通过Setter方式注入,也可以用构造函数方式注入。
这样的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 PersistenceManagerFactory persistenceManagerFactory; public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) { this.persistenceManagerFactory = pmf; } public Collection loadProductsByCategory(final String category) throws DataAccessException { JdoTemplate jdoTemplate = new JdoTemplate(this.persistenceManagerFactory); return (Collection) 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实例不仅是线程安全的,同时它也是可重用的,因而他们可以作为外部对象的实例变量而被持有。对于那些简单的诸如 find
、load
、makePersistent
或者 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
和相关的异常转化。
我们可以直接操作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
进行编码那样,非常适合在application context中进行配置:
<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
,
如果这是一个具备事务管理功能的 PersistenceManager
,close()
调用在此时将被忽略。
如果你的数据访问代码总是在一个处于活跃状态的事务中执行(或者至少在一个活跃的事务同步中),那么你能够非常安全地忽略 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
的转化。
将事务管理纳入到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.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"> ... <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
的机制。
作为高级特性,JdoTemplate
和 JdoTransactionManager
都支持一个用户自定义的 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。