事務管理
事務管理是JPA中另一項重要的内容,了解了JPA中的事務管理,能夠進一步掌握JPA的使用。
事務管理是對一系列操作的管理,它最終隻有兩個結果,要麼成功,要麼失敗。一旦失敗,所有的操作将復原到初始狀态。一旦成功,才最終送出,最終持久化。事務管理對銀行系統最為典型。例如一個人去銀行取款,他取款的錢此時大于銀行賬戶中的錢,此時交易失敗,是以取款不成功,事務復原到操作前的狀态。
在JPA中,對于實體的“CRUD”基本操作,其中涉及事務的是“C”、“U”和“D”,即“建立”、“更新”和“删除”,因為這些操作都會影響資料庫中的資料變化,是以必須使用事務保證其一緻性;對于“R”查詢,隻是查詢資料,沒有對資料産生變化,是以并不需要控制事務。
是以,一說到事務,讀者首先應确定所使用的操作是否需要關聯事務,先要界定事務所有效使用的範圍。
事務與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對象,在代碼中控制事務也是不同的。
對于不同的EntityManager類型與所運作的環境,所支援的事務類型是不一樣的。
其中兩種情況下最為簡單,一種是容器托管的EntityManager隻能運作在EJB容器中,隻能采用JTA的方式管理事務;另一種是J2SE環境下,隻能使用應用托管的EntityManager并且隻能采用RESOURCE_LOCAL的方式管理事務。本節的事務隻針對這兩種情況講述,而對于應用托管的EntityManager在EJB容器和Web容器中由于都可以選擇不同的事務管理方式。
JTA管理事務
JTA事務(Java Transaction API)是J2EE規範中有關事務的标準。它是容器級别的事務,隻能運作在J2EE伺服器中。它的最大優勢是可以支援分布式的事務,如果系統采用的是分布式的資料庫,那麼隻能選擇JTA管理EntityManager事務。
使用JTA管理EntityManager事務時,需要注意以下幾個問題。
— JTA事務隻能運作在J2EE的環境中,即EJB容器中和Web容器中;而在J2SE環境中隻能使用RESOURCE_LOCAL管理事務。
— 容器托管的EntityManager對象隻能采用JTA的事務,而不能采用RESOURCE_LOCAL事務。
有這樣一個記錄日志的會話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送出,一個持久化上下文也就随之結束了。
RESOURCE_LOCAL管理事務
RESOURCE_LOCAL事務資料庫本地的事務。它是資料庫級别的事務,隻能針對一種資料庫,不支援分布式的事務。對于中小型的應用,可以采用RESOURCE_LOCAL管理EntityManager事務。
使用RESOURCE_LOCAL管理EntityManager事務時需要注意以下幾個問題。
— 在J2SE環境中,隻能使用RESOURCE_LOCAL管理EntityManager事務,并且EntityManager對象是以應用托管方式獲得的。
— 代碼中使用RESOURCE_LOCAL管理事務時,要通過調用EntityManager的getTransaction()方法獲得本地事務對象。
例如,在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("");
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()方法復原。
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表示目前事務未處于激活狀态。
應用托管的EntityManager的持久化上下文
應用托管EntityManager對象在EJB容器中和Web容器中,可選擇的事務類型比較複雜,既可以支援JTA,又可以支援RESOURCE_LOCAL。下面講述在這兩種情況下,如何控制事務。
無狀态的會話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();
}
}
無狀态的會話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) {
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();
}
}
有狀态的會話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();
}
}
http://brushupo.blog.sohu.com/95340446.html