JTA和RESOURCE_LOCAL的區別


事務管理

事務管理是JPA中另一項重要的內容,瞭解了JPA中的事務管理,能夠進一步掌握JPA的使用。

事務管理是對一系列操作的管理,它最終只有兩個結果,要麼成功,要麼失敗。一旦失敗,所有的操作將回滾到初始狀態。一旦成功,才最終提交,最終持久化。事務管理對銀行系統最爲典型。例如一個人去銀行取款,他取款的錢此時大於銀行賬戶中的錢,此時交易失敗,所以取款不成功,事務回滾到操作前的狀態。

在JPA中,對於實體的“CRUD”基本操作,其中涉及事務的是“C”、“U”和“D”,即“新建”、“更新”和“刪除”,因爲這些操作都會影響數據庫中的數據變化,所以必須使用事務保證其一致性;對於“R”查詢,只是查詢數據,沒有對數據產生變化,所以並不需要控制事務。

所以,一說到事務,讀者首先應確定所使用的操作是否需要關聯事務,先要界定事務所有效使用的範圍。

11.4.1  事務與EntityManager

EntityManager對象的事務管理方式有兩種,分別爲JTA和RESOURCE_LOCAL,即Java Transaction API方法和本地的事務管理。

JPA中的事務類型通過persistence.xml文件中的“transaction-type”元素配置。例如,配置事務爲JTA方式的代碼如下所示。

<persistence>

   <persistence-unit name="demo" transaction-type="JTA">

       //其他配置省略

   </persistence-unit>

</persistence>

如果使用RESOURCE_LOCAL管理事務,則配置代碼如下所示。

<persistence>

   <persistence-unit name="demo" transaction-type="RESOURCE_LOCAL">

       //其他配置省略

   </persistence-unit>

</persistence>

除了在配置文件時指明瞭事務的類型,不同的事務類型,不同類型的EntityManager對象,在代碼中控制事務也是不同的。表11-2爲不同的運行環境、不同的EntityManager對象所支持的事務類型。

表11-2  事務類型與EntityManager

運行環境

類型

J2EE環境

J2SE環境

EJB容器

Web容器

應用託管的EntityManager

JTARESOURCE_LOCAL

JTARESOURCE_LOCAL

RESOURCE_LOCAL

容器託管的EntityManager

JTA

不支持

不支持

從表11-2中可以看出,對於不同的EntityManager類型與所運行的環境,所支持的事務類型是不一樣的。

其中兩種情況下最爲簡單,一種是容器託管的EntityManager只能運行在EJB容器中,只能採用JTA的方式管理事務;另一種是J2SE環境下,只能使用應用託管的EntityManager並且只能採用RESOURCE_LOCAL的方式管理事務。本節的事務只針對這兩種情況講述,而對於應用託管的EntityManager在EJB容器和Web容器中由於都可以選擇不同的事務管理方式,情況比較複雜,所以將在第11.5節中詳細講述。

11.4.2  JTA管理事務

JTA事務(Java Transaction API)是J2EE規範中有關事務的標準。它是容器級別的事務,只能運行在J2EE服務器中。它的最大優勢是可以支持分佈式的事務,如果系統採用的是分佈式的數據庫,那麼只能選擇JTA管理EntityManager事務。

使用JTA管理EntityManager事務時,需要注意以下幾個問題。

— JTA事務只能運行在J2EE的環境中,即EJB容器中和Web容器中;而在J2SE環境中只能使用RESOURCE_LOCAL管理事務。

— 容器託管的EntityManager對象只能採用JTA的事務,而不能採用RESOURCE_LOCAL事務。

在第11.3節中,已經簡單瞭解了一些JTA事務與EntityManager之間的關係,但當Bean的方法中又調用了另一個Bean的方法時,那麼此時事務傳播(Propagation)是如何進行的?下面就深入瞭解事務的傳播與持久化上下文的關係。

有這樣一個記錄日誌的會話Bean,它負責記錄相關的日誌信息等,它有一個記錄日誌的方法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方法中需要新建客戶後,再調用日誌組件來記錄日誌信息,代碼如下所示。

@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對象是容器託管的,並且設置的事務類型爲JPA。下面仔細分析一下,當在一個EJB組件中調用另外一個EJB組件時,事務的傳播與持久化上下文環境的關係。

— 當客戶端調用addCustomer方法時,此時容器自動關聯一個JTA的事務,一個事務開始,這裏將該事務記爲事務A。

— 當調用persist方法持久化客戶時,EntityManager對象發現當前有一個JTA的事務A,則此時將EntityManager對象的事務附加到JTA的事務A中,並且創建了一個新的持久化上下文。

— 調用日誌組件的recordLog方法,容器發現調用了另外一個EJB的方法,所以首先檢查當前是否存在事務,由於當前狀態下存在事務A,所以將recordLog方法的事務附加到事務A中(由於默認情況下,CustomerService的事務類型是REQUIRED)。

— 當進入recordLog方法時,再次調用persist方法持久化日誌時,由於此時EntityManager對象的事務是附加到JTA事務A中的,所以仍與之前調用的persist方法時所在的持久化上下文相同,所以,可以直接調用持久化客戶後的customer.getId(),來獲得持久化客戶的Id值。雖然在一個EJB組件中調用了另外一個EJB組件的方法,但兩次調用的persist方法所在的持久化上下文是相同的。

— recordLog方法結束,又回到addCustomer方法中,此時事務A提交,一個持久化上下文也就隨之結束了。

11.4.3  RESOURCE_LOCAL管理事務

RESOURCE_LOCAL事務數據庫本地的事務。它是數據庫級別的事務,只能針對一種數據庫,不支持分佈式的事務。對於中小型的應用,可以採用RESOURCE_LOCAL管理EntityManager事務。

使用RESOURCE_LOCAL管理EntityManager事務時需要注意以下幾個問題。

— 在J2SE環境中,只能使用RESOURCE_LOCAL管理EntityManager事務,並且EntityManager對象是以應用託管方式獲得的。

— 代碼中使用RESOURCE_LOCAL管理事務時,要通過調用EntityManager的getTransac- tion()方法獲得本地事務對象。

例如,在J2SE環境中,使用RESOURCE_LOCAL管理EntityManager事務的代碼如下所示。

public class CustomerClient {

   public static void main(String[] args) {

       EntityManagerFactory emf = Persistence

               .createEntityManagerFactory("jpaUnit");

       EntityManager entityManager = emf.createEntityManager();

       try {

           /** 事務開始 */

           entityManager.getTransaction().begin();

           CustomerEO customer = new CustomerEO();

           customer.setName("Janet");

           customer.setEmail("[email protected]");

           customer.setAsset(100000.00);

           /** 事務提交 */

           entityManager.getTransaction().commit();

       } finally {

           entityManager.close();

           emf.close();

       }

   }

}

★ 提示 ★

採用RESOURCE_LOCAL管理事務時,要保證數據庫支持事務。例如使用MySQL時,需要設置數據庫的引擎類型爲“InnoDB”,而“MyISAM”類型是不支持事務的。

— 在代碼中,entityManager.getTransaction()方法獲得本地事務EntityTransaction對象,然後通過該對象提供的方法來控制本地的事務。有關EntityTransaction的API將在下一節講述。

— 控制本地事務時,開始一個新事務,使用begin()方法;事務完成後,使用commit()方法提交。控制事務時,並沒有調用rollback()方法回滾,這是因爲在事務開始後,一旦有異常拋出,EntityTransaction對象將自動回滾,所以並不需要顯式地調用rollback()方法回滾。

11.4.4  EntityTransaction API

下面來看本地事務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();

}

下面具體來看各個方法所表示的意義,每個方法都從作用、方法參數、異常信息,以及返回值這幾個方面來講述。

— public void begin()

作用:聲明事務開始。

方法參數:無。

異常信息:如果此時事務處於激活狀態,即isActive()爲true,將拋出IllegalStateException異常。

返回值:無返回值。

— public void commit()

作用:提交事務,事務所涉及的數據的更新將全部同步到數據庫中。

方法參數:無。

異常信息:如果此時事務處於未激活狀態,即isActive()爲false,將拋出IllegalState Exception異常;如果此時提交不成功,則拋出RollbackException異常。

返回值:無返回值。

— public void rollback()

作用:事務回滾。

方法參數:無。

異常信息:如果此時事務處於未激活狀態,即isActive()爲false,將拋出IllegalState Exception異常;如果此時回滾失敗,則拋出PersistenceException異常。

返回值:無返回值。

— public void setRollbackOnly()

作用:設置當前的事務只能是回滾狀態。

方法參數:無。

異常信息:如果此時事務處於未激活狀態,即isActive()爲false,將拋出IllegalState Exception異常。

返回值:無返回值。

— public boolean getRollbackOnly()

作用:獲得當前事務的回滾狀態。

方法參數:無。

異常信息:如果此時事務處於未激活狀態,即isActive()爲false,將拋出IllegalState Exception異常。

返回值:true表示只能回滾狀態。

— public boolean isActive ()

作用:判斷當前事務是否處於激活狀態。

方法參數:無。

異常信息:如果發生了未知的異常,將拋出PersistenceException異常。

返回值:true表示當前事務處於激活狀態,false表示當前事務未處於激活狀態。

11.5  應用託管的EntityManager的持久化上下文

通過表11-2所總結的各種情況,應用託管EntityManager對象在EJB容器中和Web容器中,可選擇的事務類型比較複雜,既可以支持JTA,又可以支持RESOURCE_LOCAL。下面講述在這兩種情況下,如何控制事務。

11.5.1  無狀態的會話Bean與JTA事務(事務範圍)

在會話Bean裏以注入的方式獲得EntityManagerFactory對象,不需要負責它的關閉,所以此時,只需要控制EntityManager的打開和關閉。當客戶端每次調用Bean中的方法時,都首先創建EntityManager對象,然後在方法結束前關閉EntityManager對象。EntityManager對象的事務使用的是容器自動管理的事務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  無狀態的會話Bean與JTA事務(擴展範圍)

與上個會話Bean中的管理方式不同,此時EntityManager對象爲Bean的屬性,當Bean初始化後,也就是標註@PostConstruct方法後,創建EntityManager對象;當Bean銷燬前,也就是標註@PreDestroy方法後,關閉EntityManager對象,所以EntityManager對象是整個的Bean的聲明週期中。當客戶端調用需要關聯事務的方法時,需要使用joinTransaction()方法合併到上一次的事務中。

代碼如下所示。

@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) {

       /**查詢不需要關聯事務*/

       CustomerEO customer = em.find(CustomerEO.class, customerId);

       em.clear();

       return customer;

   }

   public void placeOrder(Integer customerId, OrderEO order) {

       /**

       *EntityManager 對象的作用範圍是這個Bean的生命週期

       *所以,每次使用時要合併到上一次的事務中

       */

       em.joinTransaction();

       CustomerEO customer = em.find(CustomerEO.class, customerId);

       customer.getOrders().add(order);

       em.merge(customer);

       /**

       * 手動脫離當前事務和持久化上下文

       */

       em.flush();

       em.clear();

   }

   @PreDestroy

   public void destroy(){

       em.close();

   }

}

11.5.3  有狀態的會話Bean與JTA事務

同樣是EntityManager對象在整個的Bean的聲明週期中,但由於會話Bean此時是有狀態的Bean,所以當客戶端調用任何方法時,都處在同一個持久化上下文中。所以每次並不需要調用clear()方法來手動地脫離當前的上下文,但每次客戶端的調用仍需要使用joinTransaction()方法合併到上一次的事務中。

代碼如下所示。

@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();

   }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章