天天看點

JTA和RESOURCE_LOCAL的差別

事務管理

事務管理是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