|
JPA事務(wù)總結(jié) 原文摘自:JPA數(shù)據(jù)庫持久層開發(fā)實(shí)踐 作者:馮曼菲 事務(wù)管理是JPA中另一項(xiàng)重要的內(nèi)容,了解了JPA中的事務(wù)管理,能夠進(jìn)一步掌握J(rèn)PA的使用。事務(wù)管理是對一系列操作的管理,它最終只有兩個結(jié) 果,要么成功,要么失敗。一旦失敗,所有的操作將回滾到初始狀態(tài)。一旦成功,才最終提交,最終持久化。事務(wù)管理對銀行系統(tǒng)最為典型。例如一個人去銀行取 款,他取款的錢此時大于銀行賬戶中的錢,此時交易失敗,所以取款不成功,事務(wù)回滾到操作前的狀態(tài)。 在JPA中,對于實(shí)體的“CRUD”基本操作,其中 涉及事務(wù)的是“C”、“U”和“D”,即“新建”、“更新”和“刪除”,因?yàn)檫@些操作都會影響數(shù)據(jù)庫中的數(shù)據(jù)變化,所以必須使用事務(wù)保證其一致性;對于 “R”查詢,只是查詢數(shù)據(jù),沒有對數(shù)據(jù)產(chǎn)生變化,所以并不需要控制事務(wù)。 所以,一說到事務(wù),讀者首先應(yīng)確定所使用的操作是否 需要關(guān)聯(lián)事務(wù),先要界定事務(wù)所有效使用的范圍。 11.4.1 事務(wù)與EntityManagerEntityManager對象的事務(wù)管理方式有兩 種,分別為JTA和RESOURCE_LOCAL,即Java Transaction API方法和本地的事務(wù)管理。 JPA中的事務(wù)類 型通過persistence.xml文件中的“transaction-type”元素配置。例如,配置事務(wù)為JTA方式的代碼如下所示。 <persistence> <persistence-unit name="demo" transaction-type="JTA"> //其他配置省略 </persistence-unit> </persistence> 如果使用 RESOURCE_LOCAL管理事務(wù),則配置代碼如下所示。 <persistence> <persistence-unit name="demo" transaction-type="RESOURCE_LOCAL"> //其他配置省略 </persistence-unit> </persistence> 除了在配置文件時指明了 事務(wù)的類型,不同的事務(wù)類型,不同類型的EntityManager對象,在代碼中控制事務(wù)也是不同的。表11-2為不同的運(yùn)行環(huán)境、不同的 EntityManager對象所支持的事務(wù)類型。 表11-2 事務(wù)類型與EntityManager
從表11-2中可以看 出,對于不同的EntityManager類型與所運(yùn)行的環(huán)境,所支持的事務(wù)類型是不一樣的。 其中兩種情況下最為簡單,一種是容器托管的 EntityManager只能運(yùn)行在EJB容器中,只能采用JTA的方式管理事務(wù);另一種是J2SE環(huán)境下,只能使用應(yīng)用托管的 EntityManager并且只能采用RESOURCE_LOCAL的方式管理事務(wù)。本節(jié)的事務(wù)只針對這兩種情況講述,而對于應(yīng)用托管的 EntityManager在EJB容器和Web容器中由于都可以選擇不同的事務(wù)管理方式,情況比較復(fù)雜,所以將在第11.5節(jié)中詳細(xì)講述。 11.4.2 JTA管理事務(wù)JTA事務(wù)(Java Transaction API)是J2EE規(guī)范中有關(guān)事務(wù)的標(biāo)準(zhǔn)。它是容器級別的事務(wù),只能運(yùn)行在J2EE服務(wù)器中。它的最大優(yōu)勢是可以支持分布式的事務(wù),如果系統(tǒng)采用的是分布 式的數(shù)據(jù)庫,那么只能選擇JTA管理EntityManager事務(wù)。 使用JTA管理EntityManager事務(wù)時, 需要注意以下幾個問題。 — JTA事務(wù)只能運(yùn)行在J2EE的環(huán)境中,即EJB容器中和Web容器中;而在J2SE環(huán)境中只能使用RESOURCE_LOCAL管理事務(wù)。 — 容器托管的EntityManager對象只能采用JTA的事務(wù),而不能采用RESOURCE_LOCAL事務(wù)。 在第11.3節(jié)中,已經(jīng)簡單了解了一些JTA事務(wù)與 EntityManager之間的關(guān)系,但當(dāng)Bean的方法中又調(diào)用了另一個Bean的方法時,那么此時事務(wù)傳播(Propagation)是如何進(jìn)行 的?下面就深入了解事務(wù)的傳播與持久化上下文的關(guān)系。 有這樣一個記錄日 志的會話Bean,它負(fù)責(zé)記錄相關(guān)的日志信息等,它有一個記錄日志的方法recordLog,代碼如下所示。 @Stateless public class LogService implements ILogService { @PersistenceContext(unitName = "jpaUnit") private EntityManager entityManager; /**記錄日志*/ public void recordLog(Integer id, String reason) { LogEO log = new LogEO(); log.setId(id); log.setReason(reason); entityManager.persist(log); } } 此時在 CustomerService的會話Bean中,addCustomer方法中需要新建客戶后,再調(diào)用日志組件來記錄日志信息,代碼如下所示。 @Stateless public class CustomerService implements ICustomerService { @PersistenceContext(unitName = "jpaUnit") private EntityManager entityManager; @EJB private ILogService logService ; public CustomerEO addCustomer(CustomerEO customer) { entityManager.persist(customer); logService.recordLog(customer.getId(), "新建Customer"); return customer; } } 此時 EntityManager對象是容器托管的,并且設(shè)置的事務(wù)類型為JPA。下面仔細(xì)分析一下,當(dāng)在一個EJB組件中調(diào)用另外一個EJB組件時,事務(wù)的傳 播與持久化上下文環(huán)境的關(guān)系。 — 當(dāng)客戶端調(diào)用addCustomer方法時,此時容器自動關(guān)聯(lián)一個JTA的事務(wù),一個事務(wù)開始,這里將該事務(wù)記為事務(wù)A。 — 當(dāng)調(diào)用persist方法持久化客戶時,EntityManager對象發(fā)現(xiàn)當(dāng)前有一個JTA的事務(wù)A,則此時將EntityManager對象的事務(wù)附 加到JTA的事務(wù)A中,并且創(chuàng)建了一個新的持久化上下文。 — 調(diào)用日志組件的recordLog方法,容器發(fā)現(xiàn)調(diào)用了另外一個EJB的方法,所以首先檢查當(dāng)前是否存在事務(wù),由于當(dāng)前狀態(tài)下存在事務(wù)A,所以將 recordLog方法的事務(wù)附加到事務(wù)A中(由于默認(rèn)情況下,CustomerService的事務(wù)類型是REQUIRED)。 — 當(dāng)進(jìn)入recordLog方法時,再次調(diào)用persist方法持久化日志時,由于此時EntityManager對象的事務(wù)是附加到JTA事務(wù)A中的,所 以仍與之前調(diào)用的persist方法時所在的持久化上下文相同,所以,可以直接調(diào)用持久化客戶后的customer.getId(),來獲得持久化客戶的 Id值。雖然在一個EJB組件中調(diào)用了另外一個EJB組件的方法,但兩次調(diào)用的persist方法所在的持久化上下文是相同的。 — recordLog方法結(jié)束,又回到addCustomer方法中,此時事務(wù)A提交,一個持久化上下文也就隨之結(jié)束了。 11.4.3 RESOURCE_LOCAL管理事務(wù)RESOURCE_LOCAL 事務(wù)數(shù)據(jù)庫本地的事務(wù)。它是數(shù)據(jù)庫級別的事務(wù),只能針對一種數(shù)據(jù)庫,不支持分布式的事務(wù)。對于中小型的應(yīng)用,可以采用RESOURCE_LOCAL管理 EntityManager事務(wù)。 使用RESOURCE_LOCAL管理 EntityManager事務(wù)時需要注意以下幾個問題。 — 在J2SE環(huán)境中,只能使用RESOURCE_LOCAL管理EntityManager事務(wù),并且EntityManager對象是以應(yīng)用托管方式獲得 的。 — 代碼中使用RESOURCE_LOCAL管理事務(wù)時,要通過調(diào)用EntityManager的getTransac- tion()方法獲得本地事務(wù)對象。 例如,在J2SE環(huán)境中,使用RESOURCE_LOCAL管理EntityManager事務(wù)的代碼如下所 示。 public class CustomerClient { public static void main(String[] args) { EntityManagerFactory emf = Persistence .createEntityManagerFactory("jpaUnit"); EntityManager entityManager = emf.createEntityManager(); try { /** 事務(wù)開始 */ entityManager.getTransaction().begin(); CustomerEO customer = new CustomerEO(); customer.setName("Janet"); customer.setEmail("janetvsfei@yahoo.com.cn"); customer.setAsset(100000.00); /** 事務(wù)提交 */ entityManager.getTransaction().commit(); } finally { entityManager.close(); emf.close(); } } } ★ 提示 ★ 采用RESOURCE_LOCAL管理事務(wù)時,要保證數(shù)據(jù)庫支持事務(wù)。例如使用MySQL時,需要設(shè)置數(shù)據(jù)庫的引擎類型為 “InnoDB”,而“MyISAM”類型是不支持事務(wù)的。 — 在代碼中,entityManager.getTransaction()方法獲得本地事務(wù)EntityTransaction對象,然后通過該對象提供 的方法來控制本地的事務(wù)。有關(guān)EntityTransaction的API將在下一節(jié)講述。 — 控制本地事務(wù)時,開始一個新事務(wù),使用begin()方法;事務(wù)完成后,使用commit()方法提交??刂剖聞?wù)時,并沒有調(diào)用rollback()方法 回滾,這是因?yàn)樵谑聞?wù)開始后,一旦有異常拋出,EntityTransaction對象將自動回滾,所以并不需要顯式地調(diào)用rollback()方法回 滾。 11.4.4 EntityTransaction API下面來看本地事務(wù) EntityTransaction中所定義的方法EntityTransaction API,以及它們的作用,如下所示。 EntityTransaction API package javax.persistence; public interface EntityTransaction { public void begin(); public void commit(); public void rollback(); public void setRollbackOnly(); public boolean getRollbackOnly(); public boolean isActive(); } 下面具體來看各個方法所 表示的意義,每個方法都從作用、方法參數(shù)、異常信息,以及返回值這幾個方面來講述。 — public void begin() 作用:聲明事務(wù)開始。 方法參數(shù):無。 異常信息:如果此時事務(wù)處于激活狀態(tài),即isActive()為true,將拋出 IllegalStateException異常。 返回值:無返回值。 — public void commit() 作用:提交事務(wù),事務(wù)所涉及的數(shù)據(jù)的更新將全部同步到數(shù)據(jù)庫中。 方法參數(shù):無。 異常信息:如果此時事務(wù)處于未激活狀態(tài),即isActive()為false,將拋出 IllegalState Exception異常;如果此時提交不成功,則拋出RollbackException異常。 返回值:無返回值。 — public void rollback() 作用:事務(wù)回滾。 方法參數(shù):無。 異常信息:如果此時事務(wù)處于未激活狀態(tài),即isActive()為false,將拋出 IllegalState Exception異常;如果此時回滾失敗,則拋出PersistenceException異常。 返回值:無返回值。 — public void setRollbackOnly() 作用:設(shè)置當(dāng)前的事務(wù)只能是回滾狀態(tài)。 方法參數(shù):無。 異常信息:如果此時事務(wù)處于未激活狀態(tài),即isActive()為false,將拋出 IllegalState Exception異常。 返回值:無返回值。 — public boolean getRollbackOnly() 作用:獲得當(dāng)前事務(wù)的回滾狀態(tài)。 方法參數(shù):無。 異常信息:如果此時事務(wù)處于未激活狀態(tài),即isActive()為false,將拋出 IllegalState Exception異常。 返回值:true表示只能回滾狀態(tài)。 — public boolean isActive () 作用:判斷當(dāng)前事務(wù)是否處于激活狀態(tài)。 方法參數(shù):無。 異常信息:如果發(fā)生了未知的異常,將拋出PersistenceException異常。 返回值:true表示當(dāng)前事務(wù)處于激活狀態(tài),false表示當(dāng)前事務(wù)未處于激活狀態(tài)。 11.5 應(yīng)用托管的EntityManager的持久化上下文通過表 11-2所總結(jié)的各種情況,應(yīng)用托管EntityManager對象在EJB容器中和Web容器中,可選擇的事務(wù)類型比較復(fù)雜,既可以支持JTA,又可以 支持RESOURCE_LOCAL。下面講述在這兩種情況下,如何控制事務(wù)。 11.5.1 無狀態(tài)的會話Bean與JTA事務(wù)(事務(wù)范圍)在會話 Bean里以注入的方式獲得EntityManagerFactory對象,不需要負(fù)責(zé)它的關(guān)閉,所以此時,只需要控制EntityManager的打開 和關(guān)閉。當(dāng)客戶端每次調(diào)用Bean中的方法時,都首先創(chuàng)建EntityManager對象,然后在方法結(jié)束前關(guān)閉EntityManager對象。 EntityManager對象的事務(wù)使用的是容器自動管理的事務(wù)JTA。 代碼如下所示。 @Stateless public class CustomerService implements ICustomerService {
@PersistenceUnit(unitName="jpaUnit") private EntityManagerFactory emf;
public CustomerEO findCustomerById(Integer customerId) { EntityManager em = emf.createEntityManager(); CustomerEO customer = em.find(CustomerEO.class, customerId); em.close(); return customer; } public void placeOrder(Integer customerId, OrderEO order) { EntityManager em = emf.createEntityManager(); CustomerEO customer = em.find(CustomerEO.class, customerId); customer.getOrders().add(order); em.merge(customer); em.close(); } } 11.5.2 無狀態(tài)的會話Bean與JTA事務(wù)(擴(kuò)展范圍)與上個 會話Bean中的管理方式不同,此時EntityManager對象為Bean的屬性,當(dāng)Bean初始化后,也就是標(biāo)注@PostConstruct方法 后,創(chuàng)建EntityManager對象;當(dāng)Bean銷毀前,也就是標(biāo)注@PreDestroy方法后,關(guān)閉EntityManager對象,所以 EntityManager對象是整個的Bean的聲明周期中。當(dāng)客戶端調(diào)用需要關(guān)聯(lián)事務(wù)的方法時,需要使用joinTransaction()方法合并 到上一次的事務(wù)中。 代碼如下所示。 @Stateless public class CustomerService implements ICustomerService {
@PersistenceUnit(unitName="jpaUnit") private EntityManagerFactory emf;
private EntityManager em;
@PostConstruct public void init (){ em = emf.createEntityManager(); }
public CustomerEO findCustomerById(Integer customerId) { /**查詢不需要關(guān)聯(lián)事務(wù)*/ CustomerEO customer = em.find(CustomerEO.class, customerId); em.clear(); return customer; } public void placeOrder(Integer customerId, OrderEO order) { /** *EntityManager 對象的作用范圍是這個Bean的生命周期 *所以,每次使用時要合并到上一次的事務(wù)中 */ em.joinTransaction(); CustomerEO customer = em.find(CustomerEO.class, customerId); customer.getOrders().add(order); em.merge(customer); /** * 手動脫離當(dāng)前事務(wù)和持久化上下文 */ em.flush(); em.clear(); } @PreDestroy public void destroy(){ em.close(); } } 11.5.3 有狀態(tài)的會話Bean與JTA事務(wù)同樣是 EntityManager對象在整個的Bean的聲明周期中,但由于會話Bean此時是有狀態(tài)的Bean,所以當(dāng)客戶端調(diào)用任何方法時,都處在同一個持 久化上下文中。所以每次并不需要調(diào)用clear()方法來手動地脫離當(dāng)前的上下文,但每次客戶端的調(diào)用仍需要使用joinTransaction()方法 合并到上一次的事務(wù)中。 代碼如下所示。 @Stateful public class CustomerService implements ICustomerService {
@PersistenceUnit(unitName="jpaUnit") private EntityManagerFactory emf;
private EntityManager em; private CustomerEO customer ;
@PostConstruct public void init (){ em = emf.createEntityManager(); }
public CustomerEO findCustomerById(Integer customerId) { customer = em.find(CustomerEO.class, customerId); return customer; } public void placeOrder(Integer customerId, OrderEO order) { em.joinTransaction(); customer.getOrders().add(order); } @Remove public void destroy(){ em.close(); } }
11.5.4 RESOURCE_LOCAL事務(wù)前面三 節(jié)的例子講述的是JTA事務(wù),當(dāng)在J2SE環(huán)境中,必須采用RESOURCE_LOCAL事務(wù),而且需要手動創(chuàng)建和關(guān)閉 EntityManagerFactory、EntityManager對象。關(guān)聯(lián)事務(wù)時要使用EntityManager對象的 getTransaction().begin()和getTransaction().commit()方法。 代碼如下所示。 public class CustomerService { private EntityManagerFactory emf; private EntityManager em;
public CustomerService (){ emf = Persistence.createEntityManagerFactory("jpaUnit"); em = emf.createEntityManager(); } private CustomerEO customer ;
public CustomerEO findCustomerById(Integer customerId) { customer = em.find(CustomerEO.class, customerId); return customer; } public void placeOrder(Integer customerId, OrderEO order) { em.getTransaction().begin(); customer.getOrders().add(order); em.getTransaction().commit(); } public void destroy(){ em.close(); emf.close(); } } |
||||||||||||||
|
|