实例详解 EJB 中的六大事务传播属性--转
前言
事務 (Transaction) 是訪問并可能更新數(shù)據(jù)庫中各種數(shù)據(jù)項的一個程序執(zhí)行單元 (unit)。在關系數(shù)據(jù)庫中,一個事務可以是一條或一組 SQL 語句,甚至整個程序。它有通常被稱為 ACID 的原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持續(xù)性(Durability)四大特性:
原子性(Atomicity):一個事務是一個不可分割的工作單位,事務中包括的諸操作要么都做,要么都不做。
一致性(Consistency):事務必須是使數(shù)據(jù)庫從一個一致性狀態(tài)變到另一個一致性狀態(tài)。一致性與原子性是密切相關的。
隔離性(Isolation):一個事務的執(zhí)行不能被其他事務干擾。即一個事務內(nèi)部的操作及使用的數(shù)據(jù)對并發(fā)的其他事務是隔離的,并發(fā)執(zhí)行的各個事務之間不能互相干擾。
持久性(Durability):持續(xù)性也稱永久性(permanence),指一個事務一旦提交,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
在 Java EE 的應用開發(fā)中,事務的應用是必不可少的,同時由于方法調(diào)用的原因,比如方法 A 調(diào)用方法 B 的時候。如果方法 A 在事務環(huán)境中運行,那么方法 B 是否也要在事務中運行呢,方法 B 是要和方法 A 在同一個事務中運行還是新開起一個事物呢?等等。要弄清楚這些問題,就要牽涉到事務傳播屬性的問題,EJB 中針對不同的的情況提供了下面六種不同的事物傳播屬性:
Required:用該屬性標注的方法或組件總是在事務中運行。如果客戶端已經(jīng)在事務中,則在原事務中運行;如果沒有事務,則開啟一個新事務,在其中運行。
Requires_New:方法或組件總是在新開啟的事務中運行。如果客戶端已經(jīng)在事務中,則首先將原事務掛起,然后新開啟一個事務,在其中運行,新事務結束之后,原來事務從掛起點繼續(xù)執(zhí)行;如果沒有事務,則開啟一個新事務,在其中運行。
Supports:和 Required 屬性的不同點是,在沒有事務的環(huán)境中不會開啟一個新事務;如果存在事務的話則加入其中運行,這點和 Reuqired 相同。
Not_Supported:如果事務已經(jīng)存在的情況下,則原來的事務要掛起,然后調(diào)用標注該屬性的方法或組件,調(diào)用結束之后,繼續(xù)原來的事務;無事務環(huán)境中調(diào)用的時候,不開啟新事務,這點和 Supports 相同。
Mandatory:調(diào)用標注該屬性的方法或組件的客戶端,必須已經(jīng)在事務中,如果不在事務中則會拋出異常;如果已經(jīng)在事務中,則加入原來事務運行。和 Required 不同的是,該屬性不會自動開啟新的事務;
Never:用 Never 屬性標注的方法或組件,不能在事務中運行。如果調(diào)用該方法或組件的客戶端已經(jīng)在事務中,則拋出異常。
下面就實例詳細介紹一下 EJB 中這六種不同的事務傳播屬性。
回頁首
前期準備工作
首先,我們創(chuàng)建如下幾個類,來作為我們后續(xù)中的實例。
清單 1. Address 實體 Bean
@Entity public class Address implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String country; private String city; private String street; private String postCode; private String TsAttribute; @Temporal(javax.persistence.TemporalType.TIMESTAMP) private Date createTime; // Getters and Setters }我們在 Address 實體 Bean 中添加一些字段:
清單 2. Person 實體 Bean
@Entity public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private int age; private String TsAttribute; @Temporal(javax.persistence.TemporalType.TIMESTAMP) private Date createTime; // Getters and Setters }同樣我們在 Person 實體 Bean 中添加一些字段:
清單 3. 無狀態(tài)的 SessionBean CommonEJB
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createAddress() { Address address = new Address(); /* * * 對 address 對象* 的屬性進行賦值* */ em.persist(address); } }我們在 CommonEJB 中創(chuàng)建了一個名為 createAddress() 的業(yè)務方法,使用這個方法來持久化 Address 實體 bean,因此我們也使用 @PersistenceContext 注入了相應的持久化單元,我們將會將 Address 持久化到這個持久化單元對應的數(shù)據(jù)庫中。
清單 4. 無狀態(tài)的 SessionBean ClientEJB
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createPerson() { Person person = new Person(); /*** 對 person 對象* 的屬性進行賦值**/em.persist(person); commonEJB.createAddress();// 調(diào)用 CommonEJB 中的 createAddress 方法} }同樣,我們在 ClientEJB 創(chuàng)建了一個名為 createPerson() 的業(yè)務方法,使用這個方法來持久化 Person 實體 bean。稍有不同的是我們不僅注入了相應的持久化單元,而且注入了 CommonEJB 到這個 EJB 中,并且在 createPerson() 方法中調(diào)用了 CommonEJB 中的業(yè)務方法。
好了現(xiàn)在,我們所有的準備工作都已完成下面我們開始逐一介紹這六大事務傳播屬性。
回頁首
傳播屬性實例祥解
Required
當一個方法的事務傳播屬性被設置成為“Required”的時候,說明該方法需要在事務的環(huán)境中運行。如果調(diào)用該方法的客戶端不在事務中,這個時候,當該方法執(zhí)行的時候就會開啟一個新的事務;相反,如果調(diào)用該方法的方法已經(jīng)運行在一個事務之中,那么該方法就會加入原來的事務運行。
下面舉例說明一下
清單 5. 調(diào)用 ClientEJB 的 Main 函數(shù)
public class Main { @EJB private static ClientEJBRemote clientEJB; public static void main(String[] args) { clientEJB.createPerson(); } }我們使用 NetBeans 中獨有的技術來調(diào)用 EJB,這樣的話我們就不需要手動使用 JNDI 了,直接使用依賴注入的技術,在 main() 方法中注入我們要調(diào)用的 EJB,這里我們調(diào)用的是名為 ClientEJB 的 EJB。根據(jù)自 Java EE 5 引入的“Configuration by Exception”的原則,確省情況下這個 EJB 使用的是”Required”這個事務傳播屬性,根據(jù)”Required”事務傳播屬性的要求,ClientEJB 被 main() 方法調(diào)用的時候會開啟一個新的事務。
清單 6.ClientEJB 持久化 Person 實體 Bean
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createPerson() { Person person = new Person(); person.setId(100l); person.setFirstName("Leo"); person.setLastName("Wang"); person.setAge(88); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); System.out.println("----ClientEJB Excute completely!-------"); } }在 ClientEJB 這個無狀態(tài) EJB 中的 createPerson() 業(yè)務方法中我們持久化了一個 Person 對象到數(shù)據(jù)庫中。
清單 7.CommonEJB 持久化 Address 實體 Bean
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void createAddress() { Address address = new Address(); address.setId(200l); address.setCountry("China"); address.setCity("Beijing"); address.setStreet("Long Jin"); address.setPostCode("102208"); address.setTsAttribute("REQUIRED"); address.setCreateTime(new Date()); em.persist(address); System.out.println("------ClientEJB Excute completely!---------"); } }在 CommonEJB 這個無狀態(tài) EJB 中的 createAddress () 業(yè)務方法中我們持久化了一個 Person 對象到數(shù)據(jù)庫中。
TransactionAttribute 這個注解的定義如下:
清單 8.TransactionAttribute 注解定義
@Target(value={METHOD,TYPE}) @Retention(value=RUNTIME)從它的定義中可以看出,這個注解可以定義在類的層次上,也可以定義在方法上。如果兩個層次同時定義的話,定義在方法上的屬性會覆蓋定義在類層次上的屬性。EJB 中類層次上的和方法層次上的傳播屬性默認均為”Required”。
這里我們顯示地將 CommonEJB 中的 CreateAddress() 方法的傳播屬性設置成了”Required”,雖然我們沒有必要這么做,他確省就是”Required”。我們在 ClientEJB 中的 CreatePerson() 方法中調(diào)用了這個 CreateAddress() 方法,根據(jù)”Required”傳播屬性的定義,CreateAddress() 方法將會加入調(diào)用者 CreatePerson() 開啟的事務當中,成為其中的一部分。下面是這個程序運行的結果
圖 1.GlassFish 控制臺輸出
圖 1 顯示兩個方法均執(zhí)正常行完畢,沒有任何異常拋出。
圖 2. 實體 Bean Person 對應的數(shù)據(jù)庫表
CreatePerson() 方法正常執(zhí)行完畢后,ID 為 200 的人被持久化到數(shù)據(jù)庫中。
圖 3. 實體 Bean Address 對應的數(shù)據(jù)庫表
CreateAddress() 方法正常執(zhí)行完畢后,ID 為 100 的地址被持久化到數(shù)據(jù)庫中。
這就說明這兩個方法均在事務的環(huán)境中進行了持久化的操作,且沒有回滾。
下面我們使用依賴注入,將 SessionContext 注入到 ClientEJB,并在 createPerson() 中調(diào)用 setRollbackOnly() 將這個方法所在的事務設置成 Doomed 事務。Doomed 事務就是那些鐵定要回滾的事務,無論他進行了什么操作,無論成功與否,都要回滾,這就是他的宿命。
清單 9. 客戶端 createPerson() 回滾
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Resource private SessionContext ctx; @Override public void createPerson() { Person person = new Person(); person.setId(100l); person.setFirstName("Leo"); person.setLastName("Wang"); person.setAge(88); person.setHeight(170); person.setWeight(65); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); ctx.setRollbackOnly(); System.out.println("-----ClientEJB Excute completely!---------"); } }這個時候 我們保持 CommonEJB 不變,然后執(zhí)行這個程序。
使用同樣的辦法,我們將 ClientEJB 保持不變,將 CommonEJB 中的 createAddress() 方法所在的事務設置成 Doomed 事務,然后執(zhí)行程序。
清單 10. 被調(diào)用者 createAddress() 回滾
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Resource private SessionContext ctx; @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void createAddress() { Address address = new Address(); address.setId(200l); address.setCountry("China"); address.setCity("Beijing"); address.setStreet("Long Jin"); address.setPostCode("102208"); address.setCreateTime(new Date()); em.persist(address); ctx.setRollbackOnly(); System.out.println("--------ClientEJB Excute completely!--------"); } }上述兩種情況,從控制臺上我們均可以看出兩個方法正確執(zhí)行完畢,但是兩個方法中數(shù)據(jù)均沒有被持久化到數(shù)據(jù)庫中,這就是說,他們所在的事務一起進行了回滾。出現(xiàn)這樣的情況是因為,”Required”屬性是加入原有事務,也就是說它們處于同一個事物當中,一方滾另一方也回滾。
Requires_New
當一個方法的事務傳播屬性被設置成為“Requires_New”的時候。如果調(diào)用該方法的客戶端不在事務中,這個時候,當該方法執(zhí)行的時候就會開啟一個新的事務;相反,如果調(diào)用該方法的方法已經(jīng)運行在一個事務之中,那么該方法同樣會開啟一個新的事務,并在新事物中運行,而原來的事務則掛起,等待新開啟的事情執(zhí)行完畢之后再繼續(xù)執(zhí)行。
清單 11 .Requires_New 屬性中 ClientEJB 客戶端
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createPerson() { Person person = new Person(); person.setId(88l); person.setFirstName("Tom"); person.setLastName("Zhang"); person.setAge(88); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }我們?nèi)匀槐3?ClientEJB 的默認屬性不變,而僅僅將 CommonEJB 中的 createAddress() 方法的事務傳播屬性設置成”Requires_New”,如清單 12 所示
清單 12. Requires_New 屬性中 CommonEJB
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void createAddress() { Address address = new Address(); address.setId(55l); address.setCountry("China"); address.setCity("Shanghai"); address.setStreet("Long Jin"); address.setPostCode("102208"); address.setTsAttribute("REQUIRES_NEW"); address.setCreateTime(new Date()); em.persist(address); System.out.println("----------ClientEJB Excute completely!--------"); } }當我們運行程序的時候其結果如下,說明程序正確執(zhí)行完畢,數(shù)據(jù)也持久化到了數(shù)據(jù)庫中
圖 4 .Person 表
CreatePerson() 方法正常執(zhí)行完畢后,ID 為 88 的人被持久化到數(shù)據(jù)庫中。
圖 5 .Address 表
CreateAddress() 方法正常執(zhí)行完畢后,ID 為 55 的地址被持久化到數(shù)據(jù)庫中。
下面我們將 ClientEJB 設置為 Doomed 事務,而保持 CommonEJB 不變,看看是什么情況。
外圍事務回滾,不影響新開啟的事務。
清單 13. Requires_New 屬性中被設定為 Doomed 的 ClientEJB
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Resource SessionContext ctx; @Override public void createPerson() { Person person = new Person(); person.setId(88l); person.setFirstName("Tom"); person.setLastName("Zhang"); person.setAge(88); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); ctx.setRollbackOnly(); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }當我們執(zhí)行完程序之后,Glass Fish 控制臺顯示程序正確執(zhí)行沒有異常拋出,但是數(shù)據(jù)庫中顯示只有 Address 被持久化到了數(shù)據(jù)庫中。
圖 6.Address 表
這就因為 createPerson() 所在的事務進行了回滾,而 createAddress() 所在的事務沒有回滾。
內(nèi)圍事務回滾,不影響外圍事務。
下面我們將 CommonEJB 設置為 Doomed 事務,而保持 ClientEJB 不變,看看是什么情況。
清單 14.Requires_New 屬性中被設定為 Doomed 的 CommonEJB
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Resource SessionContext ctx; @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void createAddress() { Address address = new Address(); address.setId(55l); address.setCountry("China"); address.setCity("Shanghai"); address.setStreet("Long Jin"); address.setPostCode("102208"); address.setTsAttribute("REQUIRES_NEW"); address.setCreateTime(new Date()); em.persist(address); ctx.setRollbackOnly(); System.out.println("----------ClientEJB Excute completely!--------"); } }當我們正確執(zhí)行完程序之后,數(shù)據(jù)庫中只有 Person 的記錄,這就說明 createAddress() 所在的方法進行了回滾,而 createPerson() 沒有。
圖 7.Person 表
以上兩種情況說明,Require_New 開啟的是一個新事務,外圍事務也就是調(diào)用者或客戶端所在事務的回滾,不會影響到這個新開起的事務;同樣,新開起的事務的回滾與否對外圍事務也沒有任何影響。
Supports
當一個方法的事務傳播屬性被設置成為“Supports”的時候,如果調(diào)用該方法的客戶端不在事務中,這個時候,當該方法執(zhí)行的時候不會開啟一個新的事務,仍會在無事務的環(huán)境中運行;相反,如果調(diào)用該方法的方法已經(jīng)運行在一個事務之中,那么該方法就會加入原來的事務運行,這點和“Required”相同。
清單 15.Supports 屬性中 ClientEJB 客戶端
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createPerson() { Person person = new Person(); person.setId(33l); person.setFirstName("Jerry"); person.setLastName("Leoo"); person.setAge(22); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }我們?nèi)匀槐3?ClientEJB 的默認屬性不變,而僅僅將 CommonEJB 中的 createAddress() 方法的事務屬性設置成“Supports“。
清單 16.Supports 屬性中 CommonEJB
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override @TransactionAttribute(TransactionAttributeType.SUPPORTS) public void createAddress() { Address address = new Address(); address.setId(66l); address.setCountry("USA"); address.setCity("NewYork"); address.setStreet("Seventh Avenue"); address.setPostCode("123-456"); address.setTsAttribute("SUPPORTS"); address.setCreateTime(new Date()); em.persist(address); System.out.println("----------ClientEJB Excute completely!--------"); } }當我們執(zhí)行之后,可以看到兩個方法中的數(shù)據(jù)均持久化到了數(shù)據(jù)庫中
圖 8.Person 表
圖 9.Address 表
當調(diào)用者自己新開起一個事務,或已經(jīng)處于某個事務之中的時候,被調(diào)用者會加入調(diào)用者的事務。這樣調(diào)用者和被調(diào)用者就處于同一個事務之中,在任何一個方法內(nèi)出現(xiàn)引起事務回滾的事件,調(diào)用者和被調(diào)用者都要回滾。
當調(diào)用者不在事務中運行,而被調(diào)用者的事務傳播屬性為“SUPPORTS”時,被調(diào)用者也不會開啟新事務,仍舊在無事務的環(huán)境中運行,這個時候和普通得 Java 方法之間的調(diào)用毫無區(qū)別,任何一個 Java 程序員對這種情況都會司空見慣,這里也不再贅敘。
Not_Supported
當一個方法的事務傳播屬性被設置成為“Not_Supported”的時候,如果調(diào)用該方法的客戶端不在事務中,這個時候,當該方法執(zhí)行的時候也不會開啟一個新的事務,而仍是在無事務中的環(huán)境中運行,這點和用無事務的方法調(diào)用傳播屬性為”Suppots”的方法相同,不再贅敘;相反,如果調(diào)用該方法的客戶端已經(jīng)運行在一個事務之中,那么原來的事務則掛起,等待被調(diào)用者在無事務的環(huán)境中運行完畢之后,再繼續(xù)執(zhí)行原來掛起的事務,原來事務的回滾與否對被調(diào)用者均無影響。
清單 17.Not_Supported 屬性中 ClientEJB 客戶端
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createPerson() { Person person = new Person(); person.setId(123l); person.setFirstName("Marry"); person.setLastName("Bush"); person.setAge(22); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }ClientEJB 中 createPerson() 方法我們?nèi)匀皇褂媚J傳播屬性,而將 CommonEJB 中的 createAddress() 的傳播屬性設置成了 Not_Supported。
清單 18.Not_Supported 屬性中 CommonEJB
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void createAddress() { /** 此處省略了一些業(yè)務處理的代碼 ,* 因為 persist(Object obj) 必須在事務中執(zhí)行* 所以此處暫時不能使用**/System.out.println("----------ClientEJB Excute completely!--------"); } }運行程序完畢之后,從圖 10 中可以看到 ID 為 123 的 Person 已經(jīng)持久化到了數(shù)據(jù)庫中,說明 createPerson() 所在事務在調(diào)用 createAddress() 方法時,把事務掛起,當 createAddress() 執(zhí)行完畢,繼續(xù)事務的過程中完成了提交。
圖 10.Person 表
如下所示,我們將 createPerson() 方法新開起的事務設定成 Doomed 事務,執(zhí)行完畢后,由于事務的回滾,Person 數(shù)據(jù)并沒有持久化到數(shù)據(jù)庫中。而 createAddress() 方法一直在無事務的環(huán)境中運行,所以當外圍事務回滾的時候,對他并沒有人很影響。
清單 19.Not_Supported 屬性中 ClientEJB 客戶端
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Resource SessionContext ctx; @Override public void createPerson() { Person person = new Person(); person.setId(123l); person.setFirstName("Marry"); person.setLastName("Bush"); person.setAge(22); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); ctx.setRollbackOnly(); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }Mandatory
當一個方法的事務傳播屬性被設置成為“Mandatory”的時候,說明這個方法必須在一個事務環(huán)境下執(zhí)行。如果調(diào)用該方法的客戶端不在事務中,這個時候,調(diào)用該方法時,就會拋出 javax.ejb.EJBTransactionRequiredException 異常;相反,如果調(diào)用該方法的方法已經(jīng)運行在一個事務之中,那么該方法就會加入原來的事務運行,這點和“Required”相同。
清單 20.Mandatory 屬性中 ClientEJB 客戶端
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createPerson() { Person person = new Person(); person.setId(88l); person.setFirstName("Min"); person.setLastName("Zhao"); person.setAge(22); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }我們?nèi)匀槐3至?ClientEJB 中 createPerson() 方法的默認傳播屬性,而將 CommonEJB 中的 createAddress() 方法事務傳播屬性設置成了”Mandatory”。
清單 21.Mandatory 屬性中 CommonEJB
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override @TransactionAttribute(TransactionAttributeType.MANDATORY) public void createAddress() { Address address = new Address(); address.setId(66l); address.setCountry("Japan"); address.setCity("Tokyo"); address.setStreet("Seventh Avenue"); address.setPostCode("444-789"); address.setTsAttribute("MANDATORY"); address.setCreateTime(new Date()); em.persist(address); System.out.println("----------ClientEJB Excute completely!--------"); } }運行結果如下圖所示,說明兩個方法都是在事務中得到了執(zhí)行。由于使用“Mandatory”傳播屬性的方法是加入原來的外圍事務,也就是說它們處于同一個事務當中,所以在任何一個方法中如果調(diào)用 setRollbackOnly() 方法將事務設定成 Doomed 事務后,事務中的所有方法的持久化操作都會隨著事務的回滾而回滾。這里不再重復舉例。
圖 11.Person 表
圖 12.Address 表
如清單 22 我們把 ClientEJB 的傳播屬性修改為”Mandatory”,因為 Main() 不在事務中運行,所以在 Main() 方法中調(diào)用 ClientEJB 是就會拋出異常。
例如:
清單 22.Mandatory 屬性中 ClientEJB
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override @TransactionAttribute(TransactionAttributeType.MANDATORY) public void createPerson() { Person person = new Person(); person.setId(88l); person.setFirstName("Min"); person.setLastName("Zhao"); person.setAge(22); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }當我們再次運行的時候,控制臺就會拋出如下異常:
圖 13.EJBTransactionRequiredException
Never
當一個方法的事務傳播屬性被設置成為“Never”的時候,說明這個方法必須在無事務環(huán)境下執(zhí)行。如果調(diào)用該方法的方法不在事務中,該方法將會在無事務環(huán)境中運行;相反,如果調(diào)用該方法的方法已經(jīng)運行在一個事務之中,那么調(diào)用該方法的時候就會拋出“RemoteException”異常。
清單 23.Never 屬性中的 ClientEJB 客戶端
@Stateless public class ClientEJB implements ClientEJBRemote { @EJB private CommonEJBRemote commonEJB; @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override public void createPerson() { Person person = new Person(); person.setId(88l); person.setFirstName("Ying"); person.setLastName("Tong"); person.setAge(22); person.setTsAttribute("Required"); person.setCreateTime(new Date()); em.persist(person); commonEJB.createAddress(); System.out.println("------ClientEJB Excute completely!---------"); } }ClientEJB 中 createPerson() 方法采用了默認的傳播屬性,CommonEJB 中 createAddress() 方法使用了”Never”的傳播屬性。
清單 24.Never 屬性中的 CommonEJB
@Stateless public class CommonEJB implements CommonEJBRemote { @PersistenceContext(unitName = "Transaction-ejbPU") EntityManager em; @Override @TransactionAttribute(TransactionAttributeType.NEVER) public void createAddress() { Address address = new Address(); address.setId(66l); address.setCountry("Korea"); address.setCity("Souel"); address.setStreet("Tian Jian"); address.setPostCode("4444444"); address.setTsAttribute("NEVER"); address.setCreateTime(new Date()); em.persist(address); System.out.println("----------ClientEJB Excute completely!--------"); } }當我們運行的時候控制臺拋出了如下圖所示的異常,由于程序未能正確執(zhí)行,所以方法內(nèi)持久化操作均不能完成,數(shù)據(jù)庫中也沒有相應的數(shù)據(jù)。
圖 14.Never 中拋出異常
綜上所述,EJB 中事務的傳播屬性可以用如下表格進行概括:
表 1. 事務傳播屬性總表
| MANDATORY | 加入客戶端所在的事務 | 拋出?TransactionRequiredException?異常 |
| NEVER | 拋出?RemoteException?異常 | 方法在無事務環(huán)境中運行 |
| NOT_SUPPORTED | 客戶端的事務掛起,方法在無事務環(huán)境中運行,客戶端的事務繼續(xù)執(zhí)行 | 方法在無事務環(huán)境中運行 |
| REQUIRED | 加入客戶端所在的事務 | 為該方法新開起一個事務 |
| REQUIRES_NEW | 客戶端的事務掛起,為該方法新開起一個事務,客戶端的事務繼續(xù)執(zhí)行 | 為該方法新開起一個事務 |
| SUPPORTS | 加入客戶端所在的事務 | 方法在無事務環(huán)境中運行 |
Nested
Spring 中還有一個沒有在 EJB 標準中定義的 Nested 的事務傳播屬性,這個屬性和 Requires_New 極為類似,同樣被已在事務環(huán)境中的 Client 調(diào)用的時候須開啟一個新事務。唯一不同的就是 Nested 事務傳播屬性,更像一個嵌套屬性,也就是說它新開起的這個事物要依附于父事務,如果父事務提交或者回滾了,它也需要跟著提交或者回滾。而 Requies_New 新開起的事務和父事務沒有這種關系,它們是完全獨立的兩個事務,一方提交或者回滾不影響另一方。因為這個并不是 Java EE 標準中的事務傳播屬性,所以在這里也就不再贅敘了,有興趣的讀者可以參看相關資料。
回頁首
總結
邏輯層是 Java EE 項目開發(fā)中的核心層,而事物是邏輯層中的靈魂所在,所以掌握好邏輯層中的事物處理方式,對 Java EE 開發(fā)起到關鍵的作用,雖然開源領域中,事務處理的方式各有不同,但是在邏輯層事物傳播屬性中,上面所提到的六大傳播屬性,已經(jīng)被大家認可并廣泛接受,已經(jīng)被不同的邏輯層中間件普遍采用,使用已經(jīng)很廣,所以掌握了上面的那些傳播屬性的使用方法,我們就可以在以后 Java EE 項目邏輯層的開發(fā)中以不變應萬變了。
原文:http://www.ibm.com/developerworks/cn/java/j-lo-springejbtrans/
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/3833101.html
總結
以上是生活随笔為你收集整理的实例详解 EJB 中的六大事务传播属性--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 全面分析 Spring 的编程式事务管理
- 下一篇: 事务策略: 了解事务陷阱--转