Java 永續性/事務
一個 事務 是一個操作集,要麼全部失敗,要麼全部成功。事務是永續性中的一個基本部分。資料庫事務由一組 SQL DML (資料操作語言)操作組成,這些操作作為單個單元提交或回滾。物件級事務是指將對一組物件進行的一組更改作為單個單元提交到資料庫。
JPA 提供了兩種事務機制。當在 Java EE 中使用時,JPA 提供了與 JTA (Java 事務 API)的整合。JPA 還提供自己的 EntityTransaction 實現,用於 Java SE 以及在 Java EE 中的非託管模式下使用。JPA 中的事務始終位於物件級別,這意味著對永續性上下文中的所有永續性物件進行的所有更改都是事務的一部分。
資源本地事務用於 JSE 或 Java EE 中的應用程式管理(非託管)模式。要使用資源本地事務,persistence.xml 中的 transaction-type 屬性應設定為 RESOURCE_LOCAL。如果資源本地事務與 DataSource 一起使用,則應使用 <non-jta-data-source> 元素來引用已配置為非 JTA 管理的伺服器 DataSource。
本地 JPA 事務透過 EntityTransaction 類定義。它包含基本的交易 API,包括 begin、commit 和 rollback。
從技術上講,在 JPA 中,EntityManager 從建立的那一刻起就處於事務中。因此,begin 有點多餘。在呼叫 begin 之前,不能呼叫某些操作,例如 persist、merge、remove。仍然可以執行查詢,並且可以更改已查詢的物件,儘管這在 JPA 規範中有點未定義,通常它們將被提交,但是最好在對物件進行任何更改之前呼叫 begin。通常最好為每個事務建立一個新的 EntityManager,以避免在永續性上下文中保留過時物件,並允許以前管理的物件進行垃圾回收。
在成功執行 commit 後,EntityManager 可以繼續使用,並且所有管理的物件都保持管理狀態。但是,通常最好 close 或 clear EntityManager,以允許進行垃圾回收並避免過時資料。如果提交失敗,則管理的物件被視為分離狀態,並且 EntityManager 被清除。這意味著提交失敗無法捕獲和重試,如果發生失敗,則必須重新執行整個事務。以前管理的物件也可能處於不一致狀態,這意味著某些物件的鎖定版本可能已增加。如果事務已標記為回滾,提交也將失敗。這可以透過呼叫 setRollbackOnly 顯式完成,或者如果任何查詢或查詢操作失敗,則必須設定。這可能是一個問題,因為某些查詢可能會失敗,但可能不希望導致整個事務回滾。
rollback 操作將僅回滾資料庫事務。永續性上下文中的管理物件將成為分離狀態,並且 EntityManager 將被清除。這意味著先前讀取的任何物件都不應再使用,並且不再是永續性上下文的一部分。對物件的更改將保持原樣,物件的更改不會被還原。
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd" version="1.0">
<persistence-unit name="acme" transaction-type="RESOURCE_LOCAL">
<non-jta-data-source>acme</non-jta-data-source>
</persistence-unit>
</persistence>
EntityManager em = createEntityManager();
em.getTransaction().begin();
Employee employee = em.find(Employee.class, id);
employee.setSalary(employee.getSalary() + 1000);
em.getTransaction().commit();
em.close();
JTA 事務用於 Java EE 中的託管模式(EJB)。要使用 JTA 事務,persistence.xml 中的 transaction-type 屬性應設定為 JTA。如果 JTA 事務與 DataSource 一起使用,則應使用 <jta-datasource> 元素來引用已配置為 JTA 管理的伺服器 DataSource。
JTA 事務透過 JTA UserTransaction 類定義,或者更可能透過 SessionBean 的使用/方法隱式定義。在 SessionBean 中,每個 SessionBean 方法預設使用 TransactionAttribute TransactionAttributeType.REQUIRED,因此呼叫它會啟動 JTA 事務(如果當前沒有 JTA 事務正在進行)。如果一個方法是第一個需要事務的呼叫方法,它將在完成時提交更改。
JTA 事務可以在 SessionBean 方法之間共享,因此,如果透過它的業務介面從另一個已經啟動了事務的方法呼叫一個方法,第二個方法將在現有的事務中執行。此外,第二個方法只能透過持久化物件或執行其他操作來更改 EntityManager 的狀態,當它將控制權返回給呼叫方法時,它會委託給它執行實際的 commit。要在與正在進行的任何事務隔離的事務中執行 DB 操作,SessionBean 方法可以被註釋為 TransactionAttribute TransactionAttributeType.REQUIRES_NEW。可以與 SessionBean 方法關聯的 TransactionAttributeType 為
| 型別 | 描述 | 異常 | 提交策略 |
|---|---|---|---|
| REQUIRED | 需要事務:如果呼叫該方法的客戶端已與事務上下文相關聯,則該方法將託管在客戶端的上下文中,否則將啟動新事務 | 無 | 在完成時執行,如果未託管在另一個方法的事務上下文中 |
| REQUIRES_NEW | 需要單獨的事務:在任何情況下都啟動事務,如果透過已與事務上下文相關聯的客戶端呼叫該方法,則現有事務將被掛起 | 無 | 在完成時執行 |
| MANDATORY | 需要現有事務:它要求呼叫該方法的客戶端已與事務上下文相關聯,並且該方法將託管在客戶端的上下文中 | 如果客戶端在未與事務關聯的情況下呼叫該方法,則會丟擲異常 | 委託給託管上下文 |
| NOT_SUPPORTED | 不使用任何事務:不會啟動事務,如果呼叫該方法的客戶端已與事務上下文相關聯,則現有事務將被掛起 | 無直接異常。如果在方法內部嘗試了 persist() 等操作,則會丟擲異常 |
不執行 |
| NEVER | 如果事務已存在,則無法執行:客戶端必須在任何事務上下文之外呼叫 | 如果呼叫該方法的客戶端與事務上下文相關聯,則會丟擲異常。如果在方法內部嘗試了 persist() 等操作,則會丟擲異常 |
不執行 |
| SUPPORTS | 不需要事務,但如果呼叫該方法的客戶端已與事務上下文相關聯,則該方法將在事務上下文中執行 | 無直接異常。如果在方法內部嘗試了 persist() 等操作,並且呼叫該方法的客戶端沒有與事務上下文相關聯,則會丟擲異常 |
不需要 |
UserTransaction 可以透過大多數應用程式伺服器中的 JNDI 查詢獲取,也可以從 EJB 2.0 風格的 SessionBean 中的 EJBContext 獲取。
JTA 事務可以在 Java EE 中使用兩種模式。在 Java EE 託管模式下,例如注入到 SessionBean 中的 EntityManager,EntityManager 引用表示每個事務的新永續性上下文。這意味著在一個事務中讀取的物件在事務結束時會成為分離狀態,並且不應再使用,或者需要合併到下一個事務中。在託管模式下,您永遠不會建立或關閉 EntityManager。
第二種模式允許 EntityManager 被應用程式管理(通常從注入的 EntityManagerFactory 獲取,或直接從 JPA Persistence 獲取)。這允許永續性上下文在事務邊界記憶體活,並遵循類似於資源本地的正常 EntityManager 生命週期。如果 EntityManager 在活動 JTA 事務的上下文中建立,它將自動成為 JTA 事務的一部分,並隨 JTA 事務一起提交/回滾。否則,它必須使用 EntityManager.joinTransaction() 加入 JTA 事務以提交/回滾。
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd" version="1.0">
<persistence-unit name="acme" transaction-type="JTA">
<jta-data-source>acme</jta-data-source>
</persistence-unit>
</persistence>
UserTransaction transaction = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");
transaction.begin();
EntityManager em = getEntityManager();
Employee employee = em.find(Employee.class, id);
employee.setSalary(employee.getSalary() + 1000);
transaction.commit();
EntityManager.joinTransaction() API 允許應用程式管理的 EntityManager 加入到活動 JTA 事務上下文。這允許 EntityManager 在 JTA 事務範圍之外建立,並將更改作為當前事務的一部分提交。這通常與 stateful SessionBean 一起使用,或者與 JSP 或 Servlet 一起使用,其中 EXTENDED EntityManager 在有狀態的架構中使用。有狀態的架構是伺服器在客戶端連線上儲存資訊直到客戶端會話結束的一種架構,它不同於無狀態的架構,其中在客戶端請求之間伺服器上不儲存任何資訊(每個請求都單獨處理)。
有狀態和無狀態架構都有優缺點。使用有狀態架構和EXTENDED EntityManager 的優點之一是,您不必擔心合併物件。您可以在一個請求中讀取物件,讓客戶端修改它們,然後將它們作為新事務的一部分提交。這就是joinTransaction 的用途。這個問題是,您通常希望在沒有活動 JTA 事務時建立EntityManager,否則它將在該事務中提交。但是,即使它確實提交,您仍然可以繼續使用它並加入未來的事務。您必須避免使用事務性 API,例如merge 或remove,直到您準備提交事務。
joinTransaction 僅與 JTA 管理的 EntityManager(persistence.xml 中的 JTA 事務型別)一起使用。對於RESOURCE_LOCAL EntityManager,您可以在需要時提交 JPA 事務。
EntityManager em = getEntityManagerFactory().createEntityManager();
Employee employee = em.find(Employee.class, id);
employee.setSalary(employee.getSalary() + 1000);
UserTransaction transaction = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");
transaction.begin();
em.joinTransaction();
transaction.commit();
有時希望處理永續性錯誤,並恢復和重試事務。這通常需要大量的應用程式知識來了解哪些失敗了,系統處於什麼狀態,以及如何修復它。
不幸的是,JPA 沒有提供處理提交失敗或錯誤處理的直接方法。當事務提交失敗時,事務將自動回滾,永續性上下文清除,所有管理的物件都分離。不僅沒有辦法處理提交失敗,而且如果在提交之前查詢中發生任何錯誤,事務將被標記為回滾,因此實際上沒有辦法處理任何錯誤。這是因為任何查詢都可能潛在地改變資料庫的狀態,因此 JPA 不知道資料庫是否處於無效或不一致的狀態,因此必須回滾事務。同樣,如果提交失敗,在永續性上下文中註冊的物件的狀態也可能不一致(例如,部分提交的物件的樂觀鎖版本已遞增),因此永續性上下文將被清除以避免進一步的錯誤。
一些 JPA 提供程式可能會提供擴充套件的 API 來允許處理提交失敗或處理查詢中的錯誤。
有一些方法可以通用地處理提交失敗和其他錯誤。使用RESOURCE_LOCAL 事務時,錯誤處理通常更容易,而不是使用 JTA 事務。
錯誤處理的一種方法是在提交失敗後,將每個管理的物件呼叫merge 到一個新的EntityManager 中,然後嘗試提交新的EntityManager。一個問題可能是,任何分配的 ID 或分配或遞增的樂觀鎖版本可能需要重置。此外,如果原始EntityManager 是EXTENDED,任何正在使用的物件仍將分離,需要重置。
另一種更復雜的方法是始終使用非事務性EntityManager。當要提交時,建立一個新的EntityManager,將非事務性物件合併到其中,並提交新的EntityManager。如果提交失敗,只有新的EntityManager 的狀態可能不一致,原始EntityManager 將不受影響。這允許更正問題,並將EntityManager 重新合併到另一個新的EntityManager 中。如果提交成功,任何提交的更改都可以合併回原始EntityManager,然後可以繼續像平常一樣使用它。此解決方案需要相當多的開銷,因此僅應在確實需要錯誤處理並且 JPA 提供程式沒有提供其他選擇時使用。
JPA 和 JTA 不支援巢狀事務。
巢狀事務用於為在較大事務範圍內執行的操作子集提供事務保證。這樣做允許您獨立於較大事務提交和中止操作子集。
巢狀事務的使用規則如下
在巢狀(子)事務處於活動狀態時,父事務可能不會執行任何操作,除了提交或中止,或者建立更多子事務。
提交巢狀事務不會影響父事務的狀態。父事務仍然未提交。但是,父事務現在可以看到子事務所做的任何修改。當然,這些修改對於所有其他事務來說仍然是隱藏的,直到父事務也提交。
同樣,中止巢狀事務不會影響父事務的狀態。中止的唯一結果是,無論是父事務還是任何其他事務都將看不到在巢狀事務保護下執行的任何資料庫修改。
如果父事務在具有活動子事務的情況下提交或中止,則子事務將以與父事務相同的方式解決。也就是說,如果父事務中止,則子事務也中止。如果父事務提交,則子事務執行的任何修改也將提交。
巢狀事務持有的鎖不會在該事務提交時釋放。相反,它們現在由父事務持有,直到父事務提交為止。
巢狀事務執行的任何資料庫修改在父事務提交之前,都不會在更大的包含事務之外可見。
您可以使用巢狀事務實現的巢狀深度僅受記憶體限制。