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