?
This document uses PHP Chinese website manual Release
Spring支持標(biāo)準(zhǔn)的JDO 1.0/2.0 API作為數(shù)據(jù)訪問(wèn)策略,同樣遵循類似于Spring對(duì)Hibernate的支持風(fēng)格。對(duì)應(yīng)的支持與整合類位于 org.springframework.orm.jdo
包中。
Spring提供了一個(gè) LocalPersistenceManagerFactoryBean
類,允許你通過(guò)Spring的application context來(lái)定義一個(gè)本地的JDO PersistenceManagerFactory
:
<beans> <bean id="myPmf" class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean"> <property name="configLocation" value="classpath:kodo.properties"/> </bean> </beans>
作為可選方案,PersistenceManagerFactory
也可以通過(guò)直接實(shí)例化一個(gè) PersistenceManagerFactory
的實(shí)現(xiàn)類得到。
一個(gè)JDO PersistenceManagerFactory
的實(shí)現(xiàn)類遵循JavaBeans的模式,它非常像一個(gè)JDBC DataSource
的實(shí)現(xiàn)類,這使得它天然的非常適合作為一個(gè)Spring的bean定義。
這種創(chuàng)建風(fēng)格通常支持一個(gè)Spring定義的JDBC DataSource
,將它傳入到對(duì)應(yīng)的實(shí)現(xiàn)類的connectionFactory的屬性中進(jìn)行bean的創(chuàng)建。具體的例子參見(jiàn)開(kāi)源的JDO實(shí)現(xiàn)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>
一個(gè)JDO的 PersistenceManagerFactory
能夠同樣在一個(gè)J2EE應(yīng)用服務(wù)器的JNDI環(huán)境下被創(chuàng)建。
這通常由特定的JDO實(shí)現(xiàn)所提供的JCA連接器來(lái)完成。Spring標(biāo)準(zhǔn)的 JndiObjectFactoryBean
也能夠被用來(lái)獲取和暴露這個(gè) PersistenceManagerFactory
。
當(dāng)然,通常在一個(gè)EJB環(huán)境之外,在JNDI中持有 PersistenceManagerFactory
的實(shí)例沒(méi)有什么特別吸引人的好處,因而我們一般只在有非常充足的理由時(shí)選擇這種建立方式。
請(qǐng)參看Hibernate章節(jié)中“容器資源 vs 本地資源”這一節(jié)的討論,那里的討論同樣適用于JDO。
每一個(gè)基于JDO的DAO類都需要通過(guò)IoC來(lái)注入一個(gè)
PersistenceManagerFactory
。
這樣的DAO類可以在 PersistenceManagerFactory
的幫助下來(lái)操作原生的JDO API進(jìn)行編程,
但是通常來(lái)說(shuō)我們更愿意使用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;
}
});
}
}
一個(gè)回調(diào)的實(shí)現(xiàn)能夠有效地在任何JDO數(shù)據(jù)訪問(wèn)中使用。
JdoTemplate
會(huì)確保當(dāng)前的 PersistenceManager
對(duì)象的正確打開(kāi)和關(guān)閉,并自動(dòng)參與到事務(wù)管理中去。
Template實(shí)例不僅是線程安全的,同時(shí)它也是可重用的,因而他們可以作為外部對(duì)象的實(shí)例變量而被持有。
對(duì)于那些簡(jiǎn)單的諸如 find
、load
、
makePersistent
或者 delete
操作的調(diào)用,
JdoTemplate
提供可選擇的快捷函數(shù)來(lái)替換這種回調(diào)的實(shí)現(xiàn)。
不僅如此,Spring還提供了一個(gè)簡(jiǎn)便的 JdoDaoSupport
基類,
這個(gè)類提供了 setPersistenceManagerFactory(..)
方法來(lái)接受一個(gè)
PersistenceManagerFactory
對(duì)象,
同時(shí)提供了 getPersistenceManagerFactory()
和 getJdoTemplate()
給子類使用。
綜合了這些,對(duì)于那些典型的業(yè)務(wù)需求,就有了一個(gè)非常簡(jiǎn)單的DAO實(shí)現(xiàn):
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
來(lái)實(shí)現(xiàn)DAO的替代解決方案,你依然可以通過(guò)在原生JDO API的級(jí)別對(duì)那些基于Spring的DAO進(jìn)行編程,此時(shí)你必須明確地打開(kāi)和關(guān)閉一個(gè) PersistenceManager
。
正如在相應(yīng)的Hibernate章節(jié)描述的一樣,這種做法的主要優(yōu)點(diǎn)在于你的數(shù)據(jù)訪問(wèn)代碼可以在整個(gè)過(guò)程中拋出checked exceptions。JdoDaoSupport
為這種情況提供了多種函數(shù)支持,
包括獲取和釋放一個(gè)具備事務(wù)管理功能的 PersistenceManager
和相關(guān)的異常轉(zhuǎn)化。
我們可以直接操作JDO API來(lái)實(shí)現(xiàn)DAO,通過(guò)直接使用一個(gè)注入的 PersistenceManagerFactory
,而無(wú)需對(duì)Spring產(chǎn)生的任何依賴。
一個(gè)相應(yīng)的DAO實(shí)現(xiàn)看上去就像下面這樣:
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
進(jìn)行編碼那樣,非常適合在Spring容器中進(jìn)行配置:
<beans> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="persistenceManagerFactory" ref="myPmf"/> </bean> </beans>
這類DAO的主要問(wèn)題在于他們每次總是從工廠中得到一個(gè)新的 PersistenceManager
實(shí)例。為了依然能夠訪問(wèn)受到Spring管理的、具備事務(wù)管理功能的 PersistenceManager
,
不妨考慮一下在目標(biāo) PersistenceManagerFactory
之前,
定義一個(gè) TransactionAwarePersistenceManagerFactoryProxy
(Spring已經(jīng)提供),
然后將這個(gè)代理注入到你的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>
這樣,你的數(shù)據(jù)訪問(wèn)代碼就可以通過(guò) PersistenceManagerFactory.getPersistenceManager()
方法得到一個(gè)具備事務(wù)管理功能的 PersistenceManager
。
而這一方法將通過(guò)代理類的處理:在從工廠獲取一個(gè)新的 PersistenceManager
實(shí)例之前檢查一下當(dāng)前具備事務(wù)管理功能的 PersistenceManager
,
如果這是一個(gè)具備事務(wù)管理功能的 PersistenceManager
,
close()
調(diào)用在此時(shí)將被忽略。
如果你的數(shù)據(jù)訪問(wèn)代碼總是在一個(gè)處于活躍狀態(tài)的事務(wù)中執(zhí)行(或者至少在一個(gè)活躍的事務(wù)同步中),
那么你能夠非常安全地忽略 PersistenceManager.close()
的調(diào)用和整個(gè)
finally
塊的代碼。這將使你的DAO實(shí)現(xiàn)變得比較簡(jiǎn)潔:
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); } }
對(duì)于這種依賴于活躍事務(wù)的DAO,比較推薦的做法是將
TransactionAwarePersistenceManagerFactoryProxy
中的"allowCreate"標(biāo)志關(guān)閉,從而強(qiáng)制活躍事務(wù)。
<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訪問(wèn)方式的主要優(yōu)勢(shì)在于它僅僅依賴于JDO API本身而無(wú)需引入任何的Spring的類。 從無(wú)入侵性的角度來(lái)看,這一點(diǎn)非常吸引人。同時(shí),對(duì)于JDO開(kāi)發(fā)人員來(lái)說(shuō)也更自然。
然而,這樣的DAO訪問(wèn)方式會(huì)拋出 JDOException
,
這是一個(gè)無(wú)需聲明或捕獲的unchecked exception。這意味著,DAO的調(diào)用者只能以普通的錯(cuò)誤來(lái)處理這些異常,
除非完全依賴JDO自身的異常體系。因而,除非你將DAO的調(diào)用者綁定到具體的實(shí)現(xiàn)策略上去,
否則你將無(wú)法捕獲特定的異常原因(諸如樂(lè)觀鎖異常)。這種折中平衡或許可以被接受,
如果你的應(yīng)用完全基于JDO或者無(wú)需進(jìn)行特殊的異常處理。
總體來(lái)說(shuō),DAO可以基于JDO的原生API實(shí)現(xiàn),同時(shí),它依舊需要能夠參與到Spring的事務(wù)管理中。
這對(duì)于那些已經(jīng)對(duì)JDO非常熟悉的人來(lái)說(shuō)很有吸引力,因?yàn)檫@種方式更加自然。
不過(guò),這種DAO將拋出 JDOException
,因而,
如果有必要的話需要明確地去做由 JDOException
到Spring的
DataAccessException
的轉(zhuǎn)化。
將事務(wù)管理納入到Service操作的執(zhí)行中,你可以使用Spring通用的聲明式的事務(wù)管理功能, 參加下面的例子:
<?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要求你必須在一個(gè)活躍的事務(wù)中修改一個(gè)持久化對(duì)象。
與Hibernate相比,在JDO中并沒(méi)有一種類似脫離事務(wù)刷出(non-transactional flush)的概念。
基于這種原因,你所選擇的JDO實(shí)現(xiàn)需要被建立在特定的環(huán)境中,尤其是它需要為JTA同步做明確的創(chuàng)建,
由此來(lái)自行檢測(cè)一個(gè)JTA事務(wù)。這一點(diǎn)對(duì)于本地事務(wù)不是必要的,由于它已經(jīng)被Spring的
JdoTransactionManager
處理,但是對(duì)于需要參與到JTA事務(wù)中的情況,是必須的
(無(wú)論是由Spring的 JtaTransactionManager
、EJB CMT或者普通的JTA所驅(qū)動(dòng)的事務(wù))。
JdoTransactionManager
能夠?qū)⒁粋€(gè)JDO事務(wù)暴露給訪問(wèn)相同的JDBC
DataSource
的JDBC訪問(wèn)代碼。前提條件是,被注冊(cè)的
JdoDialect
能夠支持獲取底層的JDBC Connection
。
這一功能默認(rèn)被基于JDBC的JDO 2.0 實(shí)現(xiàn)。對(duì)于JDO 1.0的實(shí)現(xiàn),必須注冊(cè)一個(gè)用戶自定義的
JdoDialect
。具體參見(jiàn)下一節(jié)有關(guān) JdoDialect
的機(jī)制。
作為高級(jí)特性,JdoTemplate
和 interfacename
都支持一個(gè)用戶自定義的 JdoDialect
作為“jdoDialect”的bean屬性進(jìn)行注入。
在這種情況下,DAO將不再接收 PersistenceManagerFactory
的引用作為參數(shù),
而是接收一個(gè)完整的 JdoTemplate
實(shí)例(也就是將它注入到
JdoDaoSupport
的"jdoTemplate"屬性中去)。一個(gè) JdoDialect
實(shí)現(xiàn)能夠激活一些由Spring支持的高級(jí)特性,這通常由特定的實(shí)現(xiàn)供應(yīng)商指定:
執(zhí)行特定的事務(wù)語(yǔ)義(例如用戶自定義的事務(wù)隔離級(jí)別和事務(wù)超時(shí))
獲取具備事務(wù)功能的JDBC Connection
(暴露給基于JDBC的DAO)
應(yīng)用查詢超時(shí)功能(自動(dòng)地從Spring管理的事務(wù)超時(shí)中計(jì)算)
及時(shí)刷出 PersistenceManager
(使得事務(wù)變化對(duì)于基于JDBC的數(shù)據(jù)訪問(wèn)代碼可見(jiàn))
從 JDOExceptions
到Spring的 DataAccessExceptions
的高級(jí)轉(zhuǎn)化
這對(duì)于JDO 1.0的實(shí)現(xiàn)有特別的價(jià)值,由于這些特性都沒(méi)有在其標(biāo)準(zhǔn)的API中包含。
對(duì)于JDO 2.0,其中的絕大多數(shù)的特性已經(jīng)以標(biāo)準(zhǔn)的方式被支持。因而,Spring的 DefaultJdoDialect
在默認(rèn)情況下(Spring 1.2后)使用相應(yīng)的JDO 2.0 API函數(shù)。對(duì)于特殊的事務(wù)語(yǔ)義和異常的高級(jí)轉(zhuǎn)化這樣的高級(jí)特性,
獲取和使用JDO實(shí)現(xiàn)供應(yīng)商特定的 JdoDialect
子類還是比較有價(jià)值的。
更多有關(guān)它的操作以及它如何在Spring的JDO支持中使用的詳細(xì)信息請(qǐng)參看
JdoDialect
的Javadoc。