JPA
Spring JPA 可在 org.springframework.orm.jpa 包下使用,它為 Java Persistence API 提供了全面的支援,其方式類似於與 Hibernate 的整合,同時瞭解底層實現以提供額外的功能。
Spring 環境中 JPA 設定的三種選項
Spring JPA 支援提供三種設定 JPA EntityManagerFactory 的方式,應用程式使用它來獲取實體管理器。
使用 LocalEntityManagerFactoryBean
您只能在簡單的部署環境中使用此選項,例如獨立應用程式和整合測試。
LocalEntityManagerFactoryBean 建立一個適合簡單部署環境的 EntityManagerFactory,其中應用程式僅使用 JPA 進行資料訪問。該工廠 bean 使用 JPA PersistenceProvider 自動檢測機制(根據 JPA 的 Java SE 引導),並且在大多數情況下,您只需要指定持久化單元名稱。以下 XML 示例配置了這樣一個 bean
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="myPersistenceUnit"/>
</bean>
</beans>
這種形式的 JPA 部署最簡單,也最受限制。您無法引用現有的 JDBC DataSource bean 定義,並且不支援全域性事務。此外,持久化類的織入(位元組碼轉換)是提供者特定的,通常需要在啟動時指定特定的 JVM 代理。此選項僅適用於獨立應用程式和測試環境,JPA 規範就是為此而設計的。
從 JNDI 獲取 EntityManagerFactory
部署到 Jakarta EE 伺服器時可以使用此選項。請查閱伺服器文件,瞭解如何將自定義 JPA 提供程式部署到伺服器中,從而允許使用與伺服器預設提供程式不同的提供程式。
從 JNDI 獲取 EntityManagerFactory(例如在 Jakarta EE 環境中)只需更改 XML 配置,如下例所示
<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>
此操作假定為標準 Jakarta EE 引導。Jakarta EE 伺服器會自動檢測持久化單元(實際上是應用程式 jar 中的 META-INF/persistence.xml 檔案)以及 Jakarta EE 部署描述符(例如 web.xml)中的 persistence-unit-ref 條目,併為這些持久化單元定義環境命名上下文位置。
在這種情況下,整個持久化單元部署,包括持久化類的織入(位元組碼轉換),都由 Jakarta EE 伺服器負責。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 之外的自定義資料來源並控制織入過程。以下示例顯示了 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 設定選項,允許在應用程式內進行靈活的本地配置。它支援連結到現有的 JDBC DataSource,支援本地和全域性事務等。但是,它也對執行時環境提出要求,例如如果持久化提供程式要求位元組碼轉換,則需要一個支援織入的類載入器。
此選項可能與 Jakarta EE 伺服器內建的 JPA 功能衝突。在完整的 Jakarta EE 環境中,請考慮從 JNDI 獲取 EntityManagerFactory。或者,在 LocalContainerEntityManagerFactoryBean 定義上指定一個自定義的 persistenceXmlLocation(例如,META-INF/my-persistence.xml),並且僅在應用程式 jar 檔案中包含一個具有該名稱的描述符。由於 Jakarta EE 伺服器只查詢預設的 META-INF/persistence.xml 檔案,因此它會忽略此類自定義持久化單元,從而避免了與 Spring 驅動的 JPA 設定預先衝突。
LoadTimeWeaver 介面是 Spring 提供的一個類,它允許 JPA ClassTransformer 例項以特定方式插入,具體取決於環境是 Web 容器還是應用程式伺服器。透過代理掛鉤 ClassTransformers 通常效率不高。代理對整個虛擬機器起作用,並檢查載入的每個類,這在生產伺服器環境中通常是不希望的。
Spring 為各種環境提供了許多 LoadTimeWeaver 實現,允許 ClassTransformer 例項僅應用於每個類載入器,而不是每個 VM。
有關 LoadTimeWeaver 實現及其設定的更多詳細資訊,請參閱 AOP 章中的Spring 配置,無論是通用的還是針對各種平臺(如 Tomcat、JBoss 和 WebSphere)定製的。
如Spring 配置中所述,您可以使用 @EnableLoadTimeWeaving 註解或 context:load-time-weaver XML 元素配置一個上下文範圍的 LoadTimeWeaver。這樣一個全域性織入器會自動被所有 JPA LocalContainerEntityManagerFactoryBean 例項拾取。以下示例顯示了設定載入時織入器的首選方式,它提供了平臺自動檢測(例如,Tomcat 的支援織入的類載入器或 Spring 的 JVM 代理)以及織入器到所有感知織入的 bean 的自動傳播
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
但是,如果需要,您可以透過 loadTimeWeaver 屬性手動指定一個專用的織入器,如下例所示
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</property>
</bean>
無論 LTW 如何配置,透過使用這種技術,依賴於 Instrumentation 的 JPA 應用程式可以在目標平臺(例如 Tomcat)中執行,而無需代理。當託管應用程式依賴不同的 JPA 實現時,這一點尤為重要,因為 JPA 轉換器僅在類載入器級別應用,因此彼此隔離。
處理多個持久化單元
對於依賴多個持久化單元位置(例如,儲存在類路徑中各種 JAR 中)的應用程式,Spring 提供 PersistenceUnitManager 作為中央儲存庫,以避免可能很昂貴的持久化單元發現過程。預設實現允許指定多個位置。這些位置被解析並隨後透過持久化單元名稱檢索。(預設情況下,類路徑會搜尋 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>
預設實現允許自定義 PersistenceUnitInfo 例項(在將其提供給 JPA 提供程式之前),可以透過宣告方式(透過其屬性,影響所有託管單元)或透過程式設計方式(透過 PersistenceUnitPostProcessor,允許持久化單元選擇)進行。如果未指定 PersistenceUnitManager,則 LocalContainerEntityManagerFactoryBean 將在內部建立一個並使用它。
後臺引導
LocalContainerEntityManagerFactoryBean 透過 bootstrapExecutor 屬性支援後臺引導,如下例所示
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="bootstrapExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
</property>
</bean>
實際的 JPA 提供程式引導被交給指定的執行器,然後並行執行到應用程式引導執行緒。暴露的 EntityManagerFactory 代理可以注入到其他應用程式元件中,甚至能夠響應 EntityManagerFactoryInfo 配置檢查。但是,一旦實際的 JPA 提供程式被其他元件訪問(例如,呼叫 createEntityManager),這些呼叫將阻塞,直到後臺引導完成。特別是,當您使用 Spring Data JPA 時,請務必也為其儲存庫設定延遲引導。
從 6.2 版本開始,JPA 初始化在上下文重新整理完成之前強制執行,屆時等待非同步引導完成。這使得完全初始化的資料庫基礎設施的可用性是可預測的,並允許在 ContextRefreshedEvent 監聽器等中進行自定義的後初始化邏輯。不建議將此類應用程式級資料庫初始化放在 @PostConstruct 方法或其他類似的地方;最好將其放在 Lifecycle.start(如果適用)或 ContextRefreshedEvent 監聽器中。
基於 JPA 實現 DAO:EntityManagerFactory 和 EntityManager
儘管 EntityManagerFactory 例項是執行緒安全的,但 EntityManager 例項不是。注入的 JPA EntityManager 的行為類似於從應用程式伺服器的 JNDI 環境中獲取的 EntityManager,正如 JPA 規範所定義的那樣。它將所有呼叫委託給當前事務性 EntityManager(如果有)。否則,它會回退到每次操作新建立的 EntityManager,從而使其使用執行緒安全。 |
透過使用注入的 EntityManagerFactory 或 EntityManager,可以編寫純 JPA 程式碼,無需任何 Spring 依賴。如果啟用了 PersistenceAnnotationBeanPostProcessor,Spring 可以在欄位和方法級別理解 @PersistenceUnit 和 @PersistenceContext 註解。以下示例顯示了一個使用 @PersistenceUnit 註解的純 JPA DAO 實現
-
Java
-
Kotlin
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();
}
}
}
}
class ProductDaoImpl : ProductDao {
private lateinit var emf: EntityManagerFactory
@PersistenceUnit
fun setEntityManagerFactory(emf: EntityManagerFactory) {
this.emf = emf
}
fun loadProductsByCategory(category: String): Collection<*> {
val em = this.emf.createEntityManager()
val query = em.createQuery("from Product as p where p.category = ?1");
query.setParameter(1, category);
return query.resultList;
}
}
前面的 DAO 不依賴 Spring,並且仍然很好地融入 Spring 應用程式上下文。此外,DAO 利用註解來要求注入預設的 EntityManagerFactory,如下面的 bean 定義示例所示
<beans>
<!-- bean post-processor for JPA annotations -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
作為顯式定義 PersistenceAnnotationBeanPostProcessor 的替代方案,請考慮在應用程式上下文配置中使用 Spring context: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)而不是工廠來避免這種情況。以下示例演示瞭如何做到這一點
-
Java
-
Kotlin
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();
}
}
class ProductDaoImpl : ProductDao {
@PersistenceContext
private lateinit var em: EntityManager
fun loadProductsByCategory(category: String): Collection<*> {
val query = em.createQuery("from Product as p where p.category = :category")
query.setParameter("category", category)
return query.resultList
}
}
@PersistenceContext 註解有一個可選屬性 type,其預設值為 PersistenceContextType.TRANSACTION。您可以使用此預設值來接收共享的 EntityManager 代理。另一種選擇,PersistenceContextType.EXTENDED,則完全不同。這會導致所謂的擴充套件 EntityManager,它不是執行緒安全的,因此不得在併發訪問的元件中使用,例如 Spring 管理的單例 bean。擴充套件的 EntityManager 例項僅用於有狀態元件,例如,駐留在會話中,EntityManager 的生命週期不與當前事務繫結,而是完全由應用程式決定。
注入的 EntityManager 是 Spring 管理的(感知正在進行的事務)。儘管新的 DAO 實現使用 EntityManager 的方法級注入而不是 EntityManagerFactory,但由於使用註解,bean 定義中不需要任何更改。
這種 DAO 風格的主要優點是它只依賴於 Java Persistence API。不需要匯入任何 Spring 類。此外,由於 JPA 註解是可理解的,因此注入會自動由 Spring 容器應用。這從非侵入性的角度來看很有吸引力,並且對於 JPA 開發人員來說可能感覺更自然。
基於 @Autowired 實現 DAO(通常透過建構函式注入)
@PersistenceUnit 和 @PersistenceContext 只能在方法和欄位上宣告。那麼如何透過建構函式和其他 @Autowired 注入點提供 JPA 資源呢?
只要目標被定義為一個 bean(例如透過 LocalContainerEntityManagerFactoryBean),EntityManagerFactory 就可以很容易地透過建構函式和 @Autowired 欄位/方法注入。注入點按型別與原始 EntityManagerFactory 定義完全匹配。
然而,@PersistenceContext 風格的共享 EntityManager 引用並非開箱即用,可用於常規依賴注入。為了使其可用於 @Autowired 所需的基於型別的匹配,請考慮定義一個 SharedEntityManagerBean 作為您的 EntityManagerFactory 定義的伴隨
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
<bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="emf"/>
</bean>
或者,您可以基於 SharedEntityManagerCreator 定義一個 @Bean 方法
@Bean("em")
public static EntityManager sharedEntityManager(EntityManagerFactory emf) {
return SharedEntityManagerCreator.createSharedEntityManager(emf);
}
在有多個永續性單元的情況下,每個 EntityManagerFactory 定義都需要伴隨一個相應的 EntityManager bean 定義,最好帶有與不同的 EntityManagerFactory 定義匹配的限定符,以便透過 @Autowired @Qualifier("…") 區分永續性單元。
Spring 驅動的 JPA 事務
| 我們強烈建議您閱讀宣告式事務管理,如果您還沒有閱讀過,以獲取 Spring 宣告式事務支援的更詳細介紹。 |
JPA 的推薦策略是透過 JPA 的原生事務支援進行本地事務。Spring 的 JpaTransactionManager 提供了許多從本地 JDBC 事務中已知的功能(例如事務特定的隔離級別和資源級只讀最佳化),適用於任何常規 JDBC 連線池,而無需 JTA 事務協調器和支援 XA 的資源。
Spring JPA 還允許配置的 JpaTransactionManager 將 JPA 事務暴露給訪問同一 DataSource 的 JDBC 訪問程式碼,前提是註冊的 JpaDialect 支援檢索底層 JDBC Connection。Spring 為 EclipseLink 和 Hibernate JPA 實現提供了方言。有關 JpaDialect 的詳細資訊,請參閱下一節。
對於 JTA 風格的實際資源連線延遲檢索,Spring 為目標連線池提供了相應的 DataSource 代理類:請參閱 LazyConnectionDataSourceProxy。這對於 JPA 只讀事務特別有用,因為它們通常可以從本地快取處理,而不是訪問資料庫。
理解 JpaDialect 和 JpaVendorAdapter
作為一項高階功能,JpaTransactionManager 和 AbstractEntityManagerFactoryBean 的子類允許將自定義 JpaDialect 傳遞給 jpaDialect bean 屬性。JpaDialect 實現可以啟用 Spring 支援的以下高階功能,通常以供應商特定的方式
-
應用特定的事務語義(例如自定義隔離級別或事務超時)
-
檢索事務性 JDBC
Connection(用於暴露給基於 JDBC 的 DAO) -
將
PersistenceException高階轉換為 Spring 的DataAccessException
這對於特殊的事務語義和異常的高階轉換尤其有價值。預設實現(DefaultJpaDialect)不提供任何特殊能力,如果需要前面列出的功能,則必須指定相應的方言。
作為一種更廣泛的提供程式適配設施,主要用於 Spring 功能齊全的 LocalContainerEntityManagerFactoryBean 設定,JpaVendorAdapter 將 JpaDialect 的功能與其他提供程式特定的預設值結合起來。指定 HibernateJpaVendorAdapter 或 EclipseLinkJpaVendorAdapter 是分別為 Hibernate 或 EclipseLink 自動配置 EntityManagerFactory 設定最方便的方法。請注意,這些提供程式介面卡主要設計用於 Spring 驅動的事務管理(即,與 JpaTransactionManager 一起使用)。 |
使用 JTA 事務管理設定 JPA
作為 JpaTransactionManager 的替代方案,Spring 還允許透過 JTA 進行多資源事務協調,無論是在 Jakarta EE 環境中還是使用獨立的事務協調器(例如 Atomikos)。除了選擇 Spring 的 JtaTransactionManager 而不是 JpaTransactionManager 之外,您還需要採取一些進一步的步驟
-
底層的 JDBC 連線池需要支援 XA 並與您的事務協調器整合。這在 Jakarta EE 環境中通常很簡單,透過 JNDI 公開不同型別的
DataSource。有關詳細資訊,請參閱您的應用程式伺服器文件。類似地,獨立的事務協調器通常附帶特殊的 XA 整合DataSource變體。再次,請查閱其文件。 -
JPA
EntityManagerFactory設定需要配置為支援 JTA。這是提供者特定的,通常透過在LocalContainerEntityManagerFactoryBean上指定為jpaProperties的特殊屬性來完成。對於 Hibernate,這些屬性甚至是版本特定的。有關詳細資訊,請參閱您的 Hibernate 文件。 -
Spring 的
HibernateJpaVendorAdapter強制執行某些面向 Spring 的預設值,例如連線釋放模式on-close,它與 Hibernate 5.0 中 Hibernate 自身的預設值匹配,但在 Hibernate 5.1+ 中不再匹配。對於 JTA 設定,請確保將您的持久化單元事務型別宣告為“JTA”。或者,將 Hibernate 5.2 的hibernate.connection.handling_mode屬性設定為DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT以恢復 Hibernate 自身的預設值。有關相關說明,請參閱Hibernate 導致虛假應用程式伺服器警告。 -
或者,考慮從應用程式伺服器本身獲取
EntityManagerFactory(即,透過 JNDI 查詢而不是本地宣告的LocalContainerEntityManagerFactoryBean)。伺服器提供的EntityManagerFactory可能需要在伺服器配置中進行特殊定義(使部署可移植性降低),但已為伺服器的 JTA 環境設定。
用於 JPA 互動的原生 Hibernate 設定和原生 Hibernate 事務
原生 LocalSessionFactoryBean 設定與 HibernateTransactionManager 結合,允許與 @PersistenceContext 和其他 JPA 訪問程式碼進行互動。Hibernate SessionFactory 現在原生實現了 JPA 的 EntityManagerFactory 介面,而 Hibernate Session 控制代碼原生就是 JPA EntityManager。Spring 的 JPA 支援設施會自動檢測原生 Hibernate 會話。
因此,這種原生 Hibernate 設定在許多場景中可以替代標準的 JPA LocalContainerEntityManagerFactoryBean 和 JpaTransactionManager 組合,允許在同一個本地事務中與 SessionFactory.getCurrentSession()(以及 HibernateTemplate)以及 @PersistenceContext EntityManager 進行互動。這種設定還提供了更強的 Hibernate 整合和更靈活的配置,因為它不受 JPA 引導契約的限制。
在這種情況下,您不需要 HibernateJpaVendorAdapter 配置,因為 Spring 的原生 Hibernate 設定提供了更多功能(例如,自定義 Hibernate Integrator 設定,Hibernate 5.3 bean 容器整合,以及對只讀事務的更強最佳化)。最後但同樣重要的是,您還可以透過 LocalSessionFactoryBuilder 表達原生 Hibernate 設定,與 @Bean 風格的配置無縫整合(不涉及 FactoryBean)。
|
在 |