JPA
Spring JPA(在 org.springframework.orm.jpa
套件下提供)以類似於與 Hibernate 整合的方式,為 Java Persistence API 提供全面的支援,同時也了解底層實作,以便提供額外功能。
在 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
項目,並為這些持久化單元定義環境命名 Context 位置。
在這種情況下,整個持久化單元部署,包括持久化類別的織入(位元組碼轉換),都由 Jakarta EE 伺服器負責。 JDBC DataSource
是透過 META-INF/persistence.xml
檔案中的 JNDI 位置定義的。 EntityManager
交易與伺服器的 JTA 子系統整合。 Spring 僅使用取得的 EntityManagerFactory
,透過相依性注入將其傳遞給應用程式物件,並管理持久化單元的交易(通常透過 JtaTransactionManager
)。
如果您在同一個應用程式中使用多個持久化單元,則此類 JNDI 擷取的持久化單元的 Bean 名稱應與應用程式用來參考它們的持久化單元名稱相符(例如,在 @PersistenceUnit
和 @PersistenceContext
註解中)。
使用 LocalContainerEntityManagerFactoryBean
您可以在基於 Spring 的應用程式環境中使用此選項來獲得完整的 JPA 功能。 這包括 Web 容器(例如 Tomcat)、獨立應用程式以及具有複雜持久化需求的整合測試。
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 容器還是應用程式伺服器。 透過 代理程式 掛鉤 ClassTransformer
通常效率不高。 代理程式針對整個虛擬機器工作,並檢查載入的每個類別,這在生產伺服器環境中通常是不理想的。
Spring 為各種環境提供了許多 LoadTimeWeaver
實作,讓 ClassTransformer
實例僅適用於每個類別載入器,而不適用於每個 VM。
請參閱 AOP 章節中的 Spring 組態,以更深入了解 LoadTimeWeaver
實作及其設定,無論是通用的還是針對各種平台(例如 Tomcat、JBoss 和 WebSphere)自訂的。
如 Spring 組態 中所述,您可以使用 @EnableLoadTimeWeaving
註解或 context:load-time-weaver
XML 元素來組態 Context 範圍的 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 如何組態,透過使用此技術,依賴檢測的 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
Proxy 可以注入到其他應用程式組件中,甚至能夠回應 EntityManagerFactoryInfo
組態檢查。 但是,一旦其他組件存取實際的 JPA 供應商(例如,呼叫 createEntityManager
),這些呼叫就會封鎖,直到背景引導完成。 特別是,當您使用 Spring Data JPA 時,請務必也為其儲存庫設定延遲引導。
從 6.2 開始,JPA 初始化會在 Context 重新整理完成之前強制執行,並等待非同步引導在此時完成。 這使得完全初始化的資料庫基礎架構的可用性可預測,並允許在 ContextRefreshedEvent
Listener 等中進行自訂的後初始化邏輯。 不建議將此類應用程式層級資料庫初始化放入 @PostConstruct
方法或類似方法中; 最好將其放在 Lifecycle.start
(如果適用)或 ContextRefreshedEvent
Listener 中。
基於 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 應用程式 Context。 此外,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
的替代方案,請考慮在您的應用程式 Context 組態中使用 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 的主要問題在於,它總是透過 factory 建立新的 EntityManager
。您可以請求注入事務性的 EntityManager
(也稱為「共享 EntityManager」,因為它是實際事務性 EntityManager 的共享、執行緒安全的代理),而不是 factory,來避免這種情況。以下範例示範如何做到這一點
-
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 管理的 singleton bean。擴展 EntityManager
實例僅適用於有狀態的組件,例如駐留在 session 中的組件,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 )。 |
有關其操作以及如何在 Spring 的 JPA 支持中使用的更多詳細資訊,請參閱 JpaDialect
和 JpaVendorAdapter
javadoc。
使用 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 session。
因此,在許多情況下,這種原生 Hibernate 設定可以作為標準 JPA LocalContainerEntityManagerFactoryBean
和 JpaTransactionManager
組合的替代方案,允許在同一個本地交易中與 SessionFactory.getCurrentSession()
(以及 HibernateTemplate
) 和 @PersistenceContext EntityManager
互動。這種設定還提供更強大的 Hibernate 整合和更靈活的配置,因為它不受 JPA bootstrap 合約的約束。
在這種情況下,您不需要 HibernateJpaVendorAdapter
配置,因為 Spring 的原生 Hibernate 設定提供了更多功能 (例如,自訂 Hibernate Integrator 設定、Hibernate 5.3 bean 容器整合以及更強大的唯讀交易最佳化)。最後但並非最不重要的一點是,您還可以透過 LocalSessionFactoryBuilder
表示原生 Hibernate 設定,與 @Bean
風格的配置無縫整合 (不涉及 FactoryBean
)。
在 |