JPA
Spring JPA (位於 org.springframework.orm.jpa
包下) 為 Java Persistence API 提供了全面的支援,其方式類似於與 Hibernate 的整合,同時也感知底層實現以提供附加功能。
在 Spring 環境中設定 JPA 的三種選項
Spring JPA 支援提供了三種設定 JPA EntityManagerFactory
的方式,應用程式使用 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 提供的一個類,它允許根據環境是 Web 容器還是應用伺服器,以特定方式插入 JPA ClassTransformer
例項。透過 代理 掛接 ClassTransformer
通常效率不高。代理作用於整個虛擬機器,檢查載入的每個類,這在生產伺服器環境中通常是不希望的。
Spring 為各種環境提供了多種 LoadTimeWeaver
實現,允許 ClassTransformer
例項僅應用於每個類載入器,而不是每個虛擬機器。
關於 LoadTimeWeaver
實現及其設定(無論是通用的還是為各種平臺(如 Tomcat、JBoss 和 WebSphere)定製的)的更多資訊,請參見 AOP 章節中的 Spring 配置。
如 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,使用此技術,依賴於 instrument 的 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 類。此外,由於 Spring 容器能夠理解 JPA 註解,注入會自動應用。從非侵入性的角度來看,這很有吸引力,並且對 JPA 開發者來說可能感覺更自然。
基於 @Autowired
實現 DAO(通常結合建構函式注入)
@PersistenceUnit
和 @PersistenceContext
只能在方法和欄位上宣告。那麼,透過建構函式和其他 @Autowired
注入點提供 JPA 資源呢?
只要目標被定義為一個 Bean(例如,透過 LocalContainerEntityManagerFactoryBean
),就可以輕鬆地透過建構函式和 @Autowired
欄位/方法注入 EntityManagerFactory
。注入點按型別原樣匹配原始的 EntityManagerFactory
定義。
然而,@PersistenceContext
風格的共享 EntityManager
引用並非開箱即用地可用於常規依賴注入。為了使其能夠進行 @Autowired
所需的型別匹配,請考慮為您的 EntityManagerFactory
定義 companion 定義一個 SharedEntityManagerBean
:
<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
定義匹配的限定符(Qualifier),以便透過 @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 一起使用)。 |
有關 JpaDialect
和 JpaVendorAdapter
的操作及其在 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 環境設定的。
原生 Hibernate 設定和原生 Hibernate 事務用於 JPA 互動
原生 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
)。
在 |