?
This document uses PHP Chinese website manual Release
(org.springframework.orm.jpa
包下的)Spring JPA以類似整合Hibernate或者JDO的方式,
提供了對 Java Persistence API
的全面支持,同時為提供附加的特性,必須了解底層的實現(xiàn)。
Spring JPA 提供了三種方法創(chuàng)建JPA EntityManagerFactory
:
LocalEntityManagerFactoryBean
負(fù)責(zé)創(chuàng)建一個適合于僅使用JPA進(jìn)行數(shù)據(jù)訪問的環(huán)境的
EntityManager
。
Factory bean將使用JPA PersistenceProvider
類的自動檢測機制(根據(jù)JPA的
Java SE啟動),而在絕大多數(shù)情況下,只需要指定persistence unit名稱:
<beans> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="myPersistenceUnit"/> </bean> </beans>
這種JPA部署方式最為簡單,但卻最受限制。例如,不能連接到現(xiàn)有的JDBCDataSource
,
并且不支持全局事務(wù)。甚至,持久化類的織入(字節(jié)碼轉(zhuǎn)換)也是特定于提供者的,經(jīng)常需要在啟動時指定一個特定的JVM代理。
總之,這種方法實際上只適用于獨立的應(yīng)用程序和測試環(huán)境(這正是JPA規(guī)范設(shè)計它的原因)。
僅在簡單部署環(huán)境中只使用這種方式,比如獨立的應(yīng)用程序和集成測試。
從JNDI獲取 EntityManagerFactory
(例如在Java EE 5環(huán)境中),僅通過修改XML配置即可實現(xiàn):
<beans> <jee:jndi-lookup id="entityManagerFactory" jndi-name="persistence/myPersistenceUnit"/> </beans>
在標(biāo)準(zhǔn)的Java EE 5啟動過程中,Java EE服務(wù)器自動檢測持久化單元(例如應(yīng)用程序文件包中的META-INF/persistence.xml
)
,以及Java EE部署描述符中定義給那些持久化單元命名上下文位置的環(huán)境的persistence-unit-ref
項(例如web.xml
)。
在這種情況下,整個持久化單元部署,包括持久化類的織入(字碼碼轉(zhuǎn)換)都取決于Java EE服務(wù)器。
JDBC DataSource
通過在META-INF/persistence.xml
文件中的JNDI位置進(jìn)行定義;EntityManager事務(wù)與服務(wù)器的JTA子系統(tǒng)整合。Spring僅僅用獲得的EntityManagerFactory
,
通過依賴注入將它傳遞給應(yīng)用程序?qū)ο?,并為它管理事?wù)(一般通過JtaTransactionManager
)。
注意,如果在同一個應(yīng)用程序中使用了多個持久化單元,JNDI獲取的這種持久化單元的bean名稱
應(yīng)該與應(yīng)用程序用來引用它們的持久化單元名稱相符(例如@PersistenceUnit
和
@PersistenceContext注解)。
在部署到Java EE 5服務(wù)器時使用該方法。關(guān)于如何將自定義JPA提供者部署到服務(wù)器,以及允許使用服務(wù)器提供的缺省提供者之外的JPA提供者,請查看服務(wù)器文檔的相關(guān)說明。
LocalContainerEntityManagerFactoryBean
提供了對JPA EntityManagerFactory
的全面控制,非常適合那種需要細(xì)粒度定制的環(huán)境。LocalContainerEntityManagerFactoryBean
將基于
persistence.xml
文件創(chuàng)建 PersistenceUnitInfo
類,并提供 dataSourceLookup
策略和 loadTimeWeaver
。
因此它可以在JNDI之外的用戶定義的數(shù)據(jù)源之上工作,并控制織入流程。
<beans> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="someDataSource"/> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> </property> </bean> </beans>
這是最為強大的JPA配置方式,允許在應(yīng)用程序中靈活進(jìn)行本地配置。它支持連接現(xiàn)有JDBC DataSource
,
支持本地事務(wù)和全局事務(wù)等等。然而,它也將需求強加到了運行時環(huán)境中,例如,如果持久化提供者需要字節(jié)碼轉(zhuǎn)換,則必須有織入ClassLoader的能力。
注意,這個選項可能與Java EE 5服務(wù)器內(nèi)建的JPA功能相沖突。因此,當(dāng)運行在完全Java EE 5環(huán)境中時,
要考慮從JNDI獲取EntityManagerFactory
。另一種可以替代的方法是,在
LocalContainerEntityManagerFactoryBean
定義中通過“persistenceXmlLocation”指定相關(guān)位置,
例如“META-INF/my-persistence.xml”,并且只將包含該名稱的描述符放在應(yīng)用程序包文件中。因為Java EE 5服務(wù)器將只
查找默認(rèn)的META-INF/persistence.xml
文件,它會忽略這種定制的持久化單元,因而避免與前面Spring
驅(qū)動的JPA配置沖突。(例如,適用于Rdsin 3.1)。
在基于Spring的應(yīng)用程序環(huán)境中使用該方式可獲得全部JPA功能。這包括web容器,如Tomcat, 以及獨立的應(yīng)用程序和包含復(fù)雜持久化需求的集成測試。
LoadTimeWeaver
接口由Spring提供,允許JPA ClassTransformer
實例
能夠根據(jù)環(huán)境(web容器/應(yīng)用服務(wù)器)以特定的方式插入。
通過Java 5 代理掛鉤
ClassTransformers
經(jīng)常是無效的 ―― 代理通常在 整個虛擬機 環(huán)境下工作,并且監(jiān)控
每一個 被加載的類 ―― 這在生產(chǎn)環(huán)境下一般是不提倡的。
Spring提供了大量用于不同環(huán)境的 LoadTimeWeaver
實現(xiàn)類,
允許 ClassTransformer
實例能夠僅用于每個classloader ,而不是每個虛擬機。
接下來的一節(jié)將討論在Tomcat以及使用Spring的VM代理情況下的典型JPA織入配置。關(guān)于設(shè)置常用加載時織入的詳細(xì)內(nèi)容, 請參見AOP一章中的第?6.8.4.5?節(jié) “Spring配置”一節(jié),它涵蓋了Tomcat、VM代理以及WebLogic、OC4J、GlassFish和Resin。
Apache Tomcat 缺省的ClassLoader(類裝載器)并不支持類的切換,
但是它允許使用用戶自定義的類裝載器。Spring提供了 TomcatInstrumentableClassLoader
類
(在org.springframework.instrument.classloading.tomcat
包中),這個類繼承自Tomcat的類裝載器
(WebappClassLoader
)并且允許JPA ClassTransformer
的實例來“增強”所有由它加載的類。
簡單說,JPA轉(zhuǎn)化器(JPA transformer)僅僅在(使用 TomcatInstrumentableClassLoader
的)特定web應(yīng)用程序中才能被使用。
為使用用戶自定義的類裝載器:
將 spring-tomcat-weaver.jar
復(fù)制到 $CATALINA_HOME/server/lib 下
(其中$CATALINA_HOME 表示Tomcat的安裝路徑)。
通過修改web application context使Tomcat使用用戶自定義的類裝載器(而不是默認(rèn)的類裝載器):
<Context path="/myWebApp" docBase="/my/webApp/location"> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/> </Context>
Tomcat 5.0.x 和 5.5.x 系列支持多個上下文路徑(context locations): 服務(wù)器配置文件($CATALINA_HOME/conf/server.xml), 默認(rèn)的上下文配置($CATALINA_HOME/conf/context.xml)會影響所有被部署的web應(yīng)用程序、 單獨部署在Server端的web應(yīng)用程序的配置($CATALINA_HOME/conf/[enginename]/[hostname]/my-webapp-context.xml) 或者與web應(yīng)用程序一起(your-webapp.war/META-INF/context.xml)。從效率的角度說, 我們推薦在web-app的內(nèi)部配置的方式,因為僅僅使用JPA的應(yīng)用程序會使用用戶自定義的類裝載器。 更多具體有關(guān)可用的上下文路徑的內(nèi)容請參見Tomcat 5.x的文檔。
注意,5.5.20之前的版本有一個XML配置解析的bug,造成 server.xml
中無法使用Loader
標(biāo)簽,無論是否指定了classloader,也不管這個classloader是官方的還是自定義的。
如果你正在使用的是Tomcat 5.5.20以上的版本,就可以將useSystemClassLoaderAsParent設(shè)置成
false
來解決這個問題:
<Context path="/myWebApp" docBase="/my/webApp/location"> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader" useSystemClassLoaderAsParent="false"/> </Context>
將spring-tomcat-weaver.jar
復(fù)制到$CATALINA_HOME/lib (where
$CATALINA_HOME表示Tomcat安裝根目錄的位置)。
通過編輯web應(yīng)用程序上下文文件,使Tomcat使用自定義的ClassLoader(而不是默認(rèn)的ClassLoader):
<Context path="/myWebApp" docBase="/my/webApp/location"> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/> </Context>
Tomcat 6.0.x (類似于5.0.x/5.5.x)系列支持幾種上下文路徑:(context locations): 服務(wù)器配置文件($CATALINA_HOME/conf/server.xml), 默認(rèn)的上下文配置($CATALINA_HOME/conf/context.xml)會影響所有被部署的web應(yīng)用程序、 單獨部署在Server端的web應(yīng)用程序的配置($CATALINA_HOME/conf/[enginename]/[hostname]/my-webapp-context.xml) 或者與web應(yīng)用程序一起(your-webapp.war/META-INF/context.xml)。從效率的角度說, 我們推薦在web-app的內(nèi)部配置的方式,因為僅僅使用JPA的應(yīng)用程序會使用用戶自定義的類裝載器。 更多具體有關(guān)可用的上下文路徑的內(nèi)容請參見Tomcat 5.x documentation。
Tomcat 5.0.x/5.5.x
Tomcat 6.0.x
所有Tomcat版本所需的最后一步,是在配置LocalContainerEntityManagerFactoryBean
的時,使用
相應(yīng)的LoadTimeWeaver
:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/> </property> </bean>
利用這種方法,依賴工具的JPA應(yīng)用程序無需代理就可以在Tomcat上運行。這在宿主應(yīng)用程序依賴不同的JPA實現(xiàn)時尤為重要, 因為JPA轉(zhuǎn)化器只適用于ClassLoader級別,它們之間是彼此隔離的。
如果Tomcat使用TopLink作為JPA提供者,請將核心的toplink jar包放在$CATALINA_HOME/shared/lib文件夾中,而不再放到war中。
對于需要類工具,同時現(xiàn)有的LoadTimeWeaver實現(xiàn)不提供這種支持的環(huán)境,JDK代理是唯一的解決方案。對于這種情況,Spring提供了
需要特定于Spring(但非常常用)的VM代理(spring-agent.jar
)的InstrumentationLoadTimeWeaver
:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> </property> </bean>
請注意在啟動虛擬機時,同時啟動Spring代理,方法是提供下列JVM選項:
-javaagent:/path/to/spring-agent.jar
自Spring 2.5,可以使用context:load-time-weaver
元素來配置
上下文范圍的LoadTimeWeaver
了。這種“全局”織入器由所有JPA
LocalContainerEntityManagerFactoryBeans
自動揀選。
這是配置加載時織入器的推薦方法,提供平臺(ebLogic, OC4J, GlassFish, Tomcat, Resin, VM agent)的自動檢測,以及織入器到所有織入器知道的bean的自動傳播。
<context:load-time-weaver/> <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> ... </bean>
關(guān)于如何建立常用加載時織入的詳細(xì)內(nèi)容,請參見第?6.8.4.5?節(jié) “Spring配置”一節(jié),它涵蓋了 Tomcat、VM代理,以及WebLogic, OC4J, GlassFish和Resin。
對于那些依靠多個持久化單元位置(例如存放在classpath中的多個jar中)的應(yīng)用程序,
Spring提供了作為中央倉庫的PersistenceUnitManager
,
避免了持久化單元查找過程(的潛在開銷)。缺省實現(xiàn)允許指定多個位置
(默認(rèn)情況下classpath會搜索META-INF/persistence.xml文件),它們會被解析然后通過持久化單元名稱被獲?。?/p>
<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocation">
<list>
<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
<value>classpath:/my/packagecustom-persistence.xml</value>
<value>classpath*:META-INF/persistence.xml</value>
</list>
</property>
<property name="dataSources">
<map>
<entry key="localDataSource" value-ref="local-db"/>
<entry key="remoteDataSource" value-ref="remote-db"/>
</map>
</property>
<!-- if no datasource is specified, use this one -->
<property name="defaultDataSource" ref="remoteDataSource"/>
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="pum"/>
</bean>
要注意的是,缺省實現(xiàn)允許在將持久化單元信息傳入JPA provider之前用
PersistenceUnitPostProcessor
(它允許選擇持久化單元)修改它們,
傳入的過程可以是通過屬性聲明式地傳入(影響其中所有的單元)或編程式地傳入。
如果沒有指定persistenceUnitManager,LocalContainerEntityManagerFactoryBean
會創(chuàng)建一個并在內(nèi)部使用它。
每個基于JPA的DAO都將通過依賴注入接收一個 EntityManagerFactory
實例。
這樣的DAO可以通過指定的 EntityManagerFactory
來操作原生JPA的API進(jìn)行數(shù)據(jù)訪問,
也可以直接使用Spring的 JpaTemplate
:
<beans> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> </beans>
public class JpaProductDao implements ProductDao {
private JpaTemplate jpaTemplate;
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.jpaTemplate = new JpaTemplate(emf);
}
public Collection loadProductsByCategory(final String category) throws DataAccessException {
return (Collection) this.jpaTemplate.execute(new JpaCallback() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query query = em.createQuery("from Product as p where p.category = :category");
query.setParameter("category", category);
List result = query.getResultList();
// do some further processing with the result list
return result;
}
});
}
}
JpaCallback
實現(xiàn)允許任意類型的JPA數(shù)據(jù)訪問。
JpaTemplate
將確保 EntityManager
正確地打開和關(guān)閉,并且能夠自動地參與到事務(wù)中去。
除此之外,JpaTemplate
能夠恰當(dāng)?shù)靥幚懋惓?,確保資源的及時清理以及必要時的事務(wù)回滾。
Template實例不僅是線程安全的,而且它是可重用的,因而它能夠作為實例變量被一個類持有。
注意, JpaTemplate
提供了簡單的諸如find、load、merge等操作的快捷函數(shù)來替代默認(rèn)的回調(diào)實現(xiàn)。
不僅如此,Spring還提供了一個方便的 JpaDaoSupport
基類,提供了
get/setEntityManagerFactory
方法以及 getJpaTemplate()
方法供子類調(diào)用:
public class ProductDaoImpl extends JpaDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { Map<String, String> params = new HashMap<String, String>(); params.put("category", category); return getJpaTemplate().findByNamedParams("from Product as p where p.category = :category", params); } }
除了直接使用Spring的 JpaTemplate
之外,也可以使用原生JPA的API來實現(xiàn)基于Spring的DAO,
此時你需要自行明確地處理EntityManager
。正如在相應(yīng)的Hibernate章節(jié)描述的一樣,這種做法的主要優(yōu)
點在于你的數(shù)據(jù)訪問代碼可以在整個過程中拋出checked exceptions。JpaDaoSupport
為這種情況提
供了多種函數(shù)支持,包括獲取和釋放一個具備事務(wù)管理功能的 EntityManager
和相關(guān)的異常轉(zhuǎn)化。
JpaTemplate主要以JdoTemplate和HibernateTemplate的同胞而存在,為使用它的人們提供相同的風(fēng)格。
對于新啟動的項目,要考慮采用原生的JPA風(fēng)格編寫數(shù)據(jù)訪問對象的代碼,基于通過JPA@PersistenceContext
注解
獲取的一個“共享EntityManager”引用(利用Spring的PersistenceAnnotationBeanPostProcessor
;詳情如下。)
雖然EntityManagerFactory
實例是線程安全的,
但EntityManager
實例確實不是這樣。被注入的JPA
EntityManager
的行為和從應(yīng)用服務(wù)器JNDI環(huán)境中獲取的沒有區(qū)別,
和JPA規(guī)范定義的一樣。如果存在一個被注入的JPA EntityManager
,
它會代理對當(dāng)前事務(wù)化的EntityManager
的所有調(diào)用;
否則每個操作都會創(chuàng)建一個EntityManager
,確保線程安全。
你完全可以使用原生的JPA的API進(jìn)行編程,而無需對Spring產(chǎn)生任何依賴,這可以通過一個被注入的
EntityManagerFactory
或 EntityManager
來完成。
注意,如果 PersistenceAnnotationBeanPostProcessor
被啟用,Spring就能夠識別字段或者方法級別的 @PersistenceUnit
和
@PersistenceContext
注解。相應(yīng)的DAO實現(xiàn)看起來像這樣:
public class ProductDaoImpl implements ProductDao { private EntityManagerFactory emf; @PersistenceUnit public void setEntityManagerFactory(EntityManagerFactory emf) { this.emf = emf; } public Collection loadProductsByCategory(String category) { EntityManager em = this.emf.createEntityManager(); try { Query query = em.createQuery("from Product as p where p.category = ?1"); query.setParameter(1, category); return query.getResultList(); } finally { if (em != null) { em.close(); } } } }
上述的DAO不對Spring產(chǎn)生任何依賴,而它就如同使用Spring的 JpaTemplate
那樣,仍然非常適合
在Spring的application context中進(jìn)行配置。此外,這樣的DAO可以利用注解來要求缺省EntityManagerFactory
的注入:
<beans>
<!-- bean post-processor for JPA annotations -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
注意:作為顯式定義PersistenceAnnotationBeanPostProcessor
的另一種方法,
可以考慮在應(yīng)用程序上下文配置中使用Spring 2.5的context:annotation-config
XML元素。
它會自動為基于注解的配置注冊Spring所有標(biāo)準(zhǔn)的后置處理器(包括CommonAnnotationBeanPostProcessor
等等)。
<beans>
<!-- post-processors for all standard config annotations -->
<context:annotation-config/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
這類DAO的主要問題在于每次總是從工廠中獲取一個新的 EntityManager
實例。
這一點可以通過對 EntityManager
而不是factory進(jìn)行注入來解決:
public class ProductDaoImpl implements ProductDao { @PersistenceContext private EntityManager em; public Collection loadProductsByCategory(String category) { Query query = em.createQuery("from Product as p where p.category = :category"); query.setParameter("category", category); return query.getResultList(); } }
注意:@PersistenceContext
注解有一個可選的屬性type
,它的默認(rèn)值是
PersistenceContextType.TRANSACTION
。該默認(rèn)需要你接收一個“共享EntityManager”代理。
另一種方式,PersistenceContextType.EXTENDED
則完全不同:它會產(chǎn)生一個所謂的“擴展EntityManager”,
這個不是線程安全的,因此不應(yīng)該用在當(dāng)前被訪問的組件中,例如Spring管理的singleton bean。
擴展的EntityManager只能用在有狀態(tài)的組件中,例如,在會話中,EntityManager的生命周期與當(dāng)前事務(wù)無關(guān),完全取決于應(yīng)用程序。
被注入的 EntityManager
由Spring管理(能夠感知當(dāng)前的事務(wù))。
值得注意的是,即使這種新的實現(xiàn)更傾向于方法級別的注入(使用 EntityManager
而不是 EntityManagerFactory
),
對于注解的使用,application context的XML配置則無需任何改變。
這種DAO風(fēng)格的主要優(yōu)點在于它僅僅依賴JPA,而無需依賴任何的Spring的類。除此之外,由于JPA的注解可被理解,注入能夠被Spring容器自動應(yīng)用。 從無入侵性的角度來說,這一點非常有吸引力,它對于JPA開發(fā)者來說也更自然。
然而,DAO不僅會拋出普通的 PersistenceException
異常(這是一個無需聲明和捕獲的unchecked exception),
還會拋出諸如 IllegalArgumentException
和 IllegalStateException
之類的異常。
這意味著,DAO的調(diào)用者只能以普通的錯誤來處理這些異常,除非完全依賴JPA自身的異常體系。因而,除非你將DAO的調(diào)用者綁定到具體的實現(xiàn)策略上去,
否則將無法捕獲特定的異常原因(諸如樂觀鎖異常)。這種折中平衡或許可以被接受,如果你的應(yīng)用完全基于JPA或者無需進(jìn)行特殊的異常處理。不過,
Spring提供了一個允許你進(jìn)行透明的異常轉(zhuǎn)化的解決方案:通過使用 @Repository
注解:
@Repository
public class ProductDaoImpl implements ProductDao {
// class body here...
}
<beans>
<!-- Exception
translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
后置處理器將自動地尋找所有的異常轉(zhuǎn)化器(PersistenceExceptionTranslator
接口的實現(xiàn)),
并通知所有標(biāo)有 @Repository
注解的bean,從而能夠使得被找到的異常轉(zhuǎn)化器能夠在拋出異常時進(jìn)行相應(yīng)的異常轉(zhuǎn)化工作。
總之:DAO能夠基于普通的Java持久層API和注解來實現(xiàn),但同樣也能享受到由Spring管理事務(wù)、IoC和透明的異常轉(zhuǎn)化 (轉(zhuǎn)化成為Spring的異常體系)等好處。