JPA
Spring JPA在org.springframework.orm.jpa
包中已經可用,Spring JPA用了Hibernate集成相似的方法來提供更易於理解的JPA支持,與此同時,瞭解了JPA底層實現,可以理解更多的Spring JPA特性。
Spring中JPA配置的三個選項
Spring JPA支持提供了三種配置JPAEntityManagerFactory
的方法,之後通過EntityManagerFactory
來獲取對應的實體管理器。
LocalEntityManagerFactoryBean
通常只有在簡單的部署環境中使用此選項,例如在獨立應用程序或者進行集成測試時,纔會使用這種方式。
LocalEntityManagerFactoryBean
創建一個適用於應用程序且僅使用JPA進行數據訪問的簡單部署環境的EntityManagerFactory
。工廠bean會使用JPAPersistenceProvider
自動檢測機制,並且在大多數情況下,僅要求開發者指定持久化單元的名稱:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="myPersistenceUnit"/>
</bean>
</beans>
這種形式的JPA部署是最簡單的,同時限制也很多。開發者不能引用現有的JDBCDataSource
bean定義,並且不支持全局事務。而且,持久化類的織入(weaving)(字節碼轉換)是特定於提供者的,通常需要在啓動時指定特定的JVM代理。該選項僅適用於符合JPA Spec的獨立應用程序或測試環境。
從JNDI中獲取EntityManagerFactory
在部署到J2EE服務器時可以使用此選項。檢查服務器的文檔來了解如何將自定義JPA提供程序部署到服務器中,從而對服務器進行比默認更多的個性化定製。
從JNDI獲取EntityManagerFactory
(例如在Java EE環境中),只需要在XML配置中加入配置信息即可:
<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>
此操作將採用標準J2EE引導:J2EE服務器自動檢測J2EE部署描述符(例如web.xml)中persistence-unit-ref條目和持久性單元(實際上是應用程序jar中的META-INF/persistence.xml文件),併爲這些持久性單元定義環境上下文位置。
在這種情況下,整個持久化單元部署(包括持久化類的織入(weaving)(字節碼轉換))都取決於J2EE服務器。JDBC DataSource
通過META-INF/persistence.xml文件中的JNDI位置進行定義; 而EntityManager
事務與服務器JTA子系統集成。 Spring僅使用獲取的EntityManagerFactory
,通過依賴注入將其傳遞給應用程序對象,通常通過JtaTransactionManager
來管理持久性單元的事務。
如果在同一應用程序中使用多個持久性單元,則這種JNDI檢索的持久性單元的bean名稱應與應用程序用於引用它們的持久性單元名稱相匹配,例如@PersistenceUnit
和@PersistenceContext
註釋。
LocalContainerEntityManagerFactoryBean
在基於Spring的應用程序環境中使用此選項來實現完整的JPA功能。這包括諸如Tomcat的Web容器,以及具有複雜持久性要求的獨立應用程序和集成測試。
LocalContainerEntityManagerFactoryBean
可以完全控制EntityManagerFactory
的配置,同時適用於需要細粒度定製的環境。 LocalContainerEntityManagerFactoryBean
會基於persistence.xml
文件,dataSourceLookup
策略和指定的loadTimeWeaver
來創建一個PersistenceUnitInfo
實例。因此,可以在JNDI之外使用自定義數據源並控制織入(weaving)過程。以下示例顯示LocalContainerEntityManagerFactoryBean
的典型Bean定義:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="someDataSource"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
</beans>
下面的例子是一個典型的persistence.xml文件:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
<mapping-file>META-INF/orm.xml</mapping-file>
<exclude-unlisted-classes/>
</persistence-unit>
</persistence>
<exclude-unlisted-classes />
標籤表示不會進行註解實體類的掃描。指定的顯式true
值 -<exclude-unlisted-classes>true</exclude-unlisted-classes/>
- 也意味着不進行掃描。<exclude-unlisted-classes> false</exclude-unlisted-classes>
則會觸發掃描;但是,如果開發者需要進行實體類掃描,建議開發者簡單地省略<exclude-unlisted-classes>
元素。
LocalContainerEntityManagerFactoryBean
是最強大的JPA設置選項,允許在應用程序中進行靈活的本地配置。它支持連接到現有的JDBCDataSource
,支持本地和全局事務等。但是,它對運行時環境施加了需求,其中之一就是如果持久性提供程序需要字節碼轉換,就需要有織入(weaving)能力的類加載器。
此選項可能與J2EE服務器的內置JPA功能衝突。在完整的J2EE環境中,請考慮從JNDI獲取EntityManagerFactory
。或者,在開發者的LocalContainerEntityManagerFactoryBean
定義中指定一個自定義persistenceXmlLocation
,例如META-INF/my-persistence.xml,並且只在應用程序jar文件中包含有該名稱的描述符。因爲J2EE服務器僅查找默認的META-INF/persistence.xml文件,所以它會忽略這種自定義持久性單元,從而避免了與Spring驅動的JPA設置之間發生衝突。 (例如,這適用於Resin 3.1)
何時需要加載時間織入?
並非所有JPA提供商都需要JVM代理。Hibernate就是一個不需要JVM代理的例子。如果開發者的提供商不需要代理或開發者有其他替代方案,例如通過定製編譯器或Ant
任務在構建時應用增強功能,則不用使用加載時間編織器。
LoadTimeWeaver
是一個Spring提供的接口,它允許以特定方式插入JPAClassTransformer
實例,這取決於環境是Web容器還是應用程序服務器。 通過代理掛載ClassTransformers
通常性能較差。代理會對整個虛擬機進行操作,並檢查加載的每個類,這是生產服務器環境中最不需要的額外負載。
Spring爲各種環境提供了一些LoadTimeWeaver
實現,允許ClassTransformer
實例僅適用於每個類加載器,而不是每個VM。
有關LoadTimeWeaver
的實現及其設置的通用或定製的各種平臺(如Tomcat,WebLogic,GlassFish,Resin和JBoss)的更多瞭解,請參閱AOP章節中的Spring配置一節。
如前面部分所述,開發者可以使用@EnableLoadTimeWeaving
註解或者load-time-weaver
XML元素來配置上下文範圍的LoadTimeWeaver
。所有JPALocalContainerEntityManagerFactoryBeans
都會自動拾取這樣的全局織入器。這是設置加載時間織入器的首選方式,爲平臺(WebLogic,GlassFish,Tomcat,Resin,JBoss或VM代理)提供自動檢測功能,並將織入組件自動傳播到所有可以感知織入者的Bean:
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
開發者也可以通過LocalContainerEntityManagerFactoryBean
的loadTimeWeaver
屬性來手動指定專用的織入器:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</property>
</bean>
無論LTW如何配置,使用這種技術,依賴於儀器的JPA應用程序都可以在目標平臺(例如:Tomcat)中運行,而不需要代理。這尤其重要的是當主機應用程序依賴於不同的JPA實現時,因爲JPA轉換器僅應用於類加載器級,彼此隔離。
處理多個持久化單元
例如,對於依賴存儲在類路徑中的各種JARS中的多個持久性單元位置的應用程序,Spring將PersistenceUnitManager
作爲中央倉庫來避免可能昂貴的持久性單元發現過程。默認實現允許指定多個位置,這些位置將通過持久性單元名稱進行解析並稍後檢索。(默認情況下,搜索classpath下的META-INF/persistence.xml文件。)
<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocations">
<list>
<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
<value>classpath:/my/package/**/custom-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"/>
<property name="persistenceUnitName" value="myCustomUnit"/>
</bean>
在默認實現傳遞給JPA provider之前,是允許通過屬性(影響全部持久化單元)或者通過PersistenceUnitPostProcessor
以編程(對選擇的持久化單元進行)進行對PersistenceUnitInfo
進行自定義的。如果沒有指定PersistenceUnitManager
,則由LocalContainerEntityManagerFactoryBean
在內部創建和使用。
基於JPA的EntityManagerFactory和EntityManager來實現DAO
雖然
EntityManagerFactory
實例是線程安全的,但EntityManager
實例不是。注入的JPAEntityManager
的行爲類似於從JPA Spec中定義的應用程序服務器的JNDI環境中提取的EntityManager
。它將所有調用委託給當前事務的EntityManager
(如果有);否則,它每個操作返回的都是新創建的EntityManager
,通過使用不同的EntityManager
來保證使用時的線程安全。
通過注入的方式使用EntityManagerFactory
或EntityManager
來編寫JPA代碼,是不需要依賴任何Spring定義的類的。如果啓用了PersistenceAnnotationBeanPostProcessor
,Spring可以在實例級別和方法級別識別@PersistenceUnit
和@PersistenceContext
註解。使用@PersistenceUnit
註解的純JPA DAO實現可能如下所示:
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的實現是沒有任何依賴的,而且很適合與Spring的應用程序上下文進行集成。而且,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
,可以考慮在應用程序上下文配置中使用Spring上下文annotation-config
XML元素。這樣做會自動註冊所有Spring標準後置處理器,用於初始化基於註解的配置,包括CommonAnnotationBeanPostProcessor
等。
<beans>
<!-- post-processors for all standard config annotations -->
<context:annotation-config/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
這樣的DAO的主要問題是它總是通過工廠創建一個新的EntityManager
。開發者可以通過請求事務性EntityManager
(也稱爲共享EntityManager,因爲它是實際的事務性EntityManager的一個共享的,線程安全的代理)來避免這種情況。
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
註解具有可選的屬性類型,默認值爲PersistenceContextType.TRANSACTION
。此默認值是開發者所需要接收共享的EntityManager
代理。替代方案PersistenceContextType.EXTENDED
則完全不同:該方案會返回一個所謂擴展的EntityManager
,該EntityManager
不是線程安全的,因此不能在併發訪問的組件(如Spring管理的單例Bean)中使用。擴展實體管理器僅應用於狀態組件中,比如持有會話的組件,其中EntityManager
的生命週期與當前事務無關,而是完全取決於應用程序。
方法和實例變量級別注入
指示依賴注入(例如@PersistenceUnit
和@PersistenceContext
)的註解可以應用於類中的實例變量或方法,也就是表達式方法級注入和實例變量級注入。實例變量級註釋簡潔易用,而方法級別允許進一步處理注入的依賴關係。在這兩種情況下,成員的可見性(public
,protected
,private
)並不重要。
類級註解怎麼辦?
在J2EE平臺上,它們用於依賴關係聲明,而不是資源注入。
注入的EntityManager
是由Spring管理的(Spring可以意識到正在進行的事務)。重要的是要注意,因爲通過註解進行注入,即使新的DAO實現使用通過方法注入的EntityManager
而不是EntityManagerFactory
的注入的,在應用程序上下文XML中不需要進行任何修改。
這種DAO風格的主要優點是它只依賴於Java Persistence API;不需要導入任何Spring的實現類。而且,Spring容器可以識別JPA註解來實現自動的注入和管理。從非侵入的角度來看,這種風格對JPA開發者來說可能更爲自然。
Spring驅動的JPA事務
如果開發者還沒有閱讀聲明式事務管理,強烈建議開發者先行閱讀,這樣可以更詳細地瞭解Spring的對聲明式事務支持。
JPA的推薦策略是通過JPA的本地事務支持的本地事務。 Spring的JpaTransactionManager
提供了許多來自本地JDBC事務的功能,例如針對任何常規JDBC連接池(不需要XA要求)指定事務的隔離級別和資源級只讀優化等。
Spring JPA還允許配置JpaTransactionManager
將JPA事務暴露給訪問同一DataSource
的JDBC訪問代碼,前提是註冊的JpaDialect
支持檢索底層JDBC連接。Spring爲EclipseLink和Hibernate JPA實現提供了實現。有關JpaDialect
機制的詳細信息,請參閱下一節。
JpaDialect和JpaVendorAdapter
作爲高級功能,JpaTransactionManager
和AbstractEntityManagerFactoryBean
的子類支持自定義JpaDialect
,將其作爲Bean傳遞給jpaDialect
屬性。JpaDialect
實現可以以供應商特定的方式使能Spring支持的一些高級功能:
- 應用特定的事務語義,如自定義隔離級別或事務超時
- 爲基於JDBC的DAO導出事務性JDBC連接
- 從
PersistenceExceptions
到SpringDataAccessExceptions
的異常轉義
這對於特殊的事務語義和異常的高級翻譯特別有價值。但是Spring使用的默認實現(DefaultJpaDialect
)是不提供任何特殊功能的。如果需要上述功能,則必須指定適當的方言纔可以。
作爲一個更廣泛的供應商適應設施,主要用於Spring的全功能
LocalContainerEntityManagerFactoryBean
設置,JpaVendorAdapter
將JpaDialect
的功能與其他提供者特定的默認設置相結合。指定HibernateJpaVendorAdapter
或EclipseLinkJpaVendorAdapter
是分別爲Hibernate或EclipseLink自動配置EntityManagerFactory
設置的最簡單方便的方法。但是請注意,這些提供程序適配器主要是爲了與Spring驅動的事務管理一起使用而設計的,即爲了與JpaTransactionManager
配合使用的。
有關其操作的更多詳細信息以及在Spring的JPA支持中如何使用,請參閱JpaDialect
和JpaVendorAdapter
的Javadoc。
爲JPA配置JTA事務管理
作爲JpaTransactionManager
的替代方案,Spring還允許通過JTA在J2EE環境中或與獨立的事務協調器(如Atomikos)進行多資源事務協調。除了用Spring的JtaTransactionManager
替換JpaTransactionManager
,還有需要以下一些操作:
- 底層JDBC連接池是需要具備XA功能,並與開發者的事務協調器集成的。這在J2EE環境中很簡單,只需通過JNDI導出不同類型的
DataSource
即可。有關導出DataSource
等詳細信息,可以參考應用服務器文檔。類似地,獨立的事務協調器通常帶有特殊的XA集成的DataSource
實現。 - 需要爲JTA配置JPA
EntityManagerFactory
。這是特定於提供程序的,通常通過在LocalContainerEntityManagerFactoryBean
的特殊屬性指定爲”jpaProperties”。在使用Hibernate的情況下,這些屬性甚至是需要基於特定的版本的;請查閱Hibernate文檔以獲取詳細信息。 - Spring的
HibernateJpaVendorAdapter
會強制執行某些面向Spring的默認設置,例如在Hibernate 5.0中匹配Hibernate自己的默認值的連接釋放模式“on-close”,但在5.1 / 5.2中不再存在。對於JTA設置,不要聲明HibernateJpaVendorAdapter
開始,或關閉其prepareConnection
標誌。或者,將Hibernate 5.2的hibernate.connection.handling_mode
屬性設置爲DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
以恢復Hibernate自己的默認值。有關WebLogic的相關說明,請參考Hibernate的虛假應用服務器警告一節。 - 或者,可以考慮從應用程序服務器本身獲取
EntityManagerFactory
,即通過JNDI查找而不是本地聲明的LocalContainerEntityManagerFactoryBean
。服務器提供的EntityManagerFactory
可能需要在服務器配置中進行特殊定義,減少了部署的移植性,但是EntityManagerFactory
將爲開箱即用的服務器JTA環境設置。