EJB之JPA(EntityManager)

 

 

基本概念

EntityManager稱爲實體管理器,它由EntityManagerFactory所建立。EntityManagerFactory,做爲EntityManager的工廠,包含有當前O-R映射的元數據信息,每一個EntityManagerFactory,可稱爲一個持久化單元(PersistenceUnit),每一個持久化單元可認爲是一個數據源的映射(所謂數據源,可理解爲一個數據庫,咱們能夠在應用服務器中配置多個數據源,同時使用不一樣的PersistenceUnit來映射這些數據源,從而可以很方便的實現跨越多個數據庫之間的事務操做!)java

 

       PersistenceContext,稱爲持久化上下文,它通常包含有當前事務範圍內的,被管理的實體對象(Entity)的數據。每一個EntityManager,都會跟一個PersistenceContext相關聯。PersistenceContext中存儲的是實體對象的數據,而關係數據庫中存儲的是記錄,EntityManager正是維護這種OR映射的中間者,它能夠把數據從數據庫中加載到PersistenceContext中,也能夠把數據從PersistenceContext中持久化到數據庫,EntityManager經過Persist、merge、remove、refresh、flush等操做來操縱PersistenceContext與數據庫數據之間的同步!mysql

       EntityManager是應用程序操縱持久化數據的接口。它的做用與hibernate session相似。爲了可以在一個請求週期中使用同一個session對象,在hibernate的解決方案中,提出了currentSession的概念,hibernate中的current session,能夠跟JTA事務綁定,也能夠跟當前線程綁定。在hibernate中,session管理着全部的持久化對象的數據。而在EJB3中,EntityManager管理着PersistenceContext,PersistenceContext正是被管理的持久化對象的集合。spring

       在Java EE環境下,一個JTA事務一般會橫跨多個組件的調用(好比多個EJB組件的方法調用)。這些組件須要可以在單個事務範圍內訪問到一樣的Persistence Context。爲了知足這種狀況的須要,當EntityManager被注入或經過jndi被查詢的時候,它的Persistence Context將會在當前事務範圍內自動傳播,引用到同一個Persistence unit的EntityManager將使用一樣的Persistence Context。這能夠避免在不一樣的組件之間傳遞EntityManager引用。sql

 

經過容器來傳遞PersistenceContext,而不是應用程序本身來傳遞EntityManager。這種方式(由容器管理着PersistenceContext,並負責傳遞到不一樣的EntityManager)稱爲容器管理的實體管理器(Container-Managed EntityManager),它的生命週期由容器負責管理。數據庫

 

有一種不常見的狀況是,應用程序自身須要獨立訪問Persistence Context。即每次建立一個EntityManager都會迫使建立一個新的Persistence Context。這些Persistence Context即便在同一個事務範圍內也不會跟其它EntityManager共享!這個建立過程能夠由EntityManagerFactory的createEntityManager方法來建立。這被稱爲應用管理的實體管理器(application-managed entity manager)。緩存

底層事務控制

EntityManager的底層可使用JTA或RESOURCE_LOCAL類型的事務控制策略。JTA通常在容器環境中使用,而RESOURCE_LOCAL通常在J2SE的環境下使用。安全

好比,在J2SE的環境下,由應用程序自身來建立EntityManagerFactory,並由EntityManagerFactory建立EntityManager,經過EntityManager.getTransaction.begin()方法來開啓事務,commit()方法提交事務等等,這種方式就是RESOURCE_LOCAL的基本使用方法。服務器

最經常使用的就是在容器環境下使用。也就是使用JTA類型的EntityManager,這樣,EntityManager的調用都是在一個外部的JTA事務環境下進行的。session

Container-Managed EntityManager必須是JTA類型的EntityManager,而Application-Managed EntityManager則既能夠是JTA類型的EntityManager,也能夠是RESOURCE_LOCAL類型的EntityManager。oracle

配置示例:

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

Container-Managed Persistence Context

    @PersistenceContext(unitName="test")

    private EntityManager em;

persistence context的生命週期對應用程序來講,老是被自動、透明的管理着的。也就是對應用程序自己來講,它對persitence context的建立、銷燬一無所知,徹底自動和透明。Persistence context隨着JTA事務而傳播。

 

一個容器管理的persistence context (即container-managed persistence context)能夠被限定爲單個事務範圍,或,擴展其生存期跨越多個事務!這取決於當它的entity manager被建立的時候所定義的PersistenceContextType類型。它能夠取值爲TRANSACTION和EXTENDED。可稱爲:事務範圍的persistence context或擴展的persistence context。

 

Persistence context老是會關聯到一個entity manager factory。

 

-          Transaction-scope persistence context

當entity manager的方法被調用的時候,若是在當前JTA事務中尚未persistence context,那麼將啓動一個新的persistence context ,並將它跟當前的JTA事務關聯。

-          Extended persistence context

擴展的persistence context 老是跟stateful session bean綁定在一塊兒。當在stateful session bean中注入entity manager,並定義爲extended persistence context時,從咱們開始調用stateful session bean開始,直到stateful session bean被銷燬(移除)!一般,這能夠經過調用一個在stateful session bean中被註解定義爲@Remove的方法來結束一個stateful session bean的生命週期。

 

Application-Managed Persistence Context

即當咱們本身建立EntityManager的時候,咱們經過entityManager.close() / isOpen()方法來管理entityManager及其對應的persistence context.

 

實體對象的生命週期

幾種類型:New,managed,detached,removed

 

New – 即未有id值,還沒有跟persistence context創建關聯的對象

Managed – 有id值,已跟persistence context創建了關聯

Detached – 有id值,但沒有(或再也不)跟persistence context創建關聯

Removed – 有id值,並且跟persistence context尚有關聯,但已準備好要從數據庫中把它刪除。

 

EntityManager的接口方法

 

添加:調用persist方法

* 將把一個對象持久化,若是對象的ID非空,則在調用persist方法時將拋出異常,沒法持久化

 

刪除:remove方法

不能直接new一個對象,而後給它的id賦值,而後刪除。要刪除一個對象,這個對象必須是處於持久化狀態。

 

更新:merge方法

 

Find – 查找某個對象,若是查不到該對象,將返回null,至關於get

 

getReference – 查找某個對象,若是查找不到該對象,將拋出異常,至關於load

 

flush – 將實體對象由persistence context同步到底層的數據庫

l  FlushMode

n  Auto – 即在同一個事務中,在查詢發生前,將自動把數據從PersistenceContext持久化到數據庫中。

n  Commit – 只在提交的時候,把數據從PersistenceContext中持久化到數據庫中。若是設置FlushMode爲commit,那麼在同一個事務中,在查詢以前,若是有更新的數據,這些數據是否會影響到查詢的結果,這種狀況,EJB3未做說明。

 

Lock – 鎖定某個實體對象

實際上就是定義事務的隔離級別。總共有兩種形式:READ和WRITE,表示:

不論是READ仍是WRITE,都應該可以避免髒讀(讀到另一個事務未提交的數據)和不可重複讀(同一查詢在同一事務中屢次進行,因爲其餘提交事務所作的修改或刪除,每次返回不一樣的結果集,此時發生非重複讀。)

而對於WRITE來講,還應該可以強迫版本號的增長(對那些標識了版本的對象而言)。所以,其它事務沒法對其作任何更改操做!

 

Refresh – 將數據從數據庫中加載到Persistenc Context。

 

Clear – 清除Persistence Context中緩存的實體對象數據

 

Contains – 測試當前Persistence Context中是否包含某實體對象

 

實體對象的監聽器和回調方法

@PrePersist(在保存以前)等註解,能夠被定義到Entity Bean的某些方法上面,定義其回調方法。(請參考persistenc規範3.5.1)

 

 

 

這些方法既能夠被定義到Entity Bean上面,也能夠被定義到Entity Listener類的方法上。

 

 

攔截器

@Interceptors(InterceptorForStudent1Manager.class) public class StudentManagerImpl implements StudentManager { public class InterceptorForStudent1Manager { @AroundInvoke public Object doit(InvocationContext context) throws Exception{ System.out.println("將要開始執行方法:"+context.getMethod().getName()); Object obj = context.proceed(); System.out.println("方法"+context.getMethod().getName()+"已被成功執行"); return obj; } }

 

 

將Interceptors定義到類或特定方法上面能夠對類的全部方法或特定方法的調用進行攔截!

 

EntityManager實例操做介紹

EJB3如何實現繼承關係的映射?

繼承關係的映射,總共有三種策略,與Hibernate相似

 

一、SINGLE_TABLE策略:整個類繼承樹對應的對象都存放在一張表

 

父類定義:

@Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="animalType") @DiscriminatorValue("A") public class Animal { 

 

子類定義:

 

@Entity @DiscriminatorValue(value="B") public class Bird extends Animal{

 

 

二、JOINED策略:父類和子類都對應不一樣的表,子類中只存在其擴展的特殊的屬性(不包含

父類的屬性)

 

父類定義:

@Entity @Inheritance(strategy=InheritanceType.JOINED) public class Animal {

 

 

子類定義:

@Entity public class Bird extends Animal{ 

 

三、TABLE_PER_CLASS策略:父類和子類都對應不一樣的表,子類中存在全部的屬性(包含從

父類繼承下來的全部屬性)

@Entity @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) public class Animal { 

 

子類定義:

@Entity public class Bird extends Animal{

 

 

如何注入EntityManager對象?

一、首先要確保persistence.xml中已定義了相應的persistence-unit,好比:

<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

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

        <jta-data-source>java:/MySqlDS</jta-data-source>

        <properties>

           <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>

          <property name="hibernate.hbm2ddl.auto" value="create"/>

          <property name="hibernate.show_sql" value="true"/>

        </properties>

    </persistence-unit>

</persistence>

 

 

二、在session bean中,直接定義EntityManager,並使用@PersistenceContext註解注入便可:

@Stateless(name="userManager") public class UserManagerImpl implements UserManager { /** * 若是隻定義了一個Persistence Unit,則無需指定unitName,但一旦定義了 * 多個,則必須指定unitName */ @PersistenceContext(unitName="test") private EntityManager em;

 

 

如何知道有哪些實體類?或哪些實體類將要被映射到數據庫表?

咱們學習hibernate的時候知道,hibernate有一個hibernate.cfg.xml,能夠在這個文件中指定哪些實體類將要被映射到數據庫表。那麼EJB3中,是如何指定的呢?默認狀況下,會將EJB JAR包中的全部被定義了@Entity註解的類映射到數據庫表。咱們也能夠經過下列方法來指定要映射的類:

    

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

        <jta-data-source>java:/MySqlDS</jta-data-source>

        <class>com.bjsxt.jpa.User</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>

           <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>

          <property name="hibernate.hbm2ddl.auto" value="create"/>

          <property name="hibernate.show_sql" value="true"/>

        </properties>

    </persistence-unit>

 

正如這裏所表示的同樣,使用<class>標籤和<exclude-unlisted-classes>標籤的聯合使用,能夠指定咱們要映射的實體類。

 

如何由應用程序來管理EntityManager?

咱們通常狀況下,都不須要由應用程序自身來管理EntityManager,最好就是使用容器管理的EntityManager對象。但某些特殊狀況下,咱們可能想要由應用程序自己來管理它,那麼,能夠採起以下方法來辦到這一點。

一、注入EntityManagerFactory對象

  

/** * 也能夠注入EntityManagerFactory,手工管理PersistenceContext */ @PersistenceUnit(unitName="test") private EntityManagerFactory factory;

 

 

二、在程序中,用factory來建立和管理EntityManager對象

     

EntityManager em = factory.createEntityManager(); Student student = new Student(); student.setName("張三"); em.persist(student); em.close();

 

 

如何利用JTA實現跨越多個數據庫的事務控制(一)?

JTA 自己就支持跨越多個數據庫的事務控制。要實現這一點,咱們須要有以下步驟:

一、  首先是數據源的配置,爲了支持兩個以上的數據庫,咱們必須針對每一個數據庫單獨配置它的數據源,下面是配置了兩個MySQL數據庫的示例:

<datasources>

  <local-tx-datasource>

    <jndi-name>MySqlDS</jndi-name>

    <connection-url>jdbc:mysql://localhost:3306/ejb3</connection-url>

    <driver-class>com.mysql.jdbc.Driver</driver-class>

    <user-name>root</user-name>

    <password>mysql</password>

    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>

   

    <metadata>

       <type-mapping>mySQL</type-mapping>

    </metadata>

</local-tx-datasource>

 

  <local-tx-datasource>

    <jndi-name>MySqlDS2</jndi-name>

    <connection-url>jdbc:mysql://localhost:3306/ejb4</connection-url>

    <driver-class>com.mysql.jdbc.Driver</driver-class>

    <user-name>root</user-name>

    <password>mysql</password>

    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>

    <metadata>

       <type-mapping>mySQL</type-mapping>

    </metadata>

  </local-tx-datasource>

</datasources>

 

二、其次,在persistence.xml中,能夠定義兩個以上的persistence-unit配置:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <!-- transaction-type 也能夠取值爲RESOURCE_LOCAL -->

    <!-- 若是在一個EJB JAR包內定義了兩個以上的persistence-unit,則在這個EJB JAR包內的全部實體類,將 同時映射到這兩個persistence-unit所表明的數據庫中! -->

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

        <jta-data-source>java:/MySqlDS</jta-data-source>

        <class>com.bjsxt.jpa.User</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>

           <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>

          <property name="hibernate.hbm2ddl.auto" value="create"/>

          <property name="hibernate.show_sql" value="true"/>

        </properties>

    </persistence-unit>

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

        <jta-data-source>java:/MySqlDS2</jta-data-source>

        <class>com.bjsxt.jpa.Person</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>

           <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>

          <property name="hibernate.hbm2ddl.auto" value="create"/>

          <property name="hibernate.show_sql" value="true"/>

        </properties>

    </persistence-unit>

</persistence>

 

 

三、編寫SESSION BEAN

 

@Stateless(name="userManager") public class UserManagerImpl implements UserManager { /** * 若是隻定義了一個Persistence Unit,則無需指定unitName,但一旦定義了 * 多個,則必須指定unitName */ @PersistenceContext(unitName="test") private EntityManager em; @PersistenceContext(unitName="test2") private EntityManager em2; public void addUser() { User user = new User(); user.setName("張三"); em.persist(user); Person person = new Person(); person.setName("Test Person"); em2.persist(person); //若是拋出異常,將會致使整個事務回滾!這就是跨越數據庫的事務管理

       throw new RuntimeException("隨便一個異常"); } }

 

 

 

如何利用JTA實現跨越多個數據庫的事務控制(二)?

下面,咱們的例子是,利用JTA的能力,在同一個session bean的事務中同時操縱mysql和Oracle的數據庫,同樣能夠實現事務的管理。

 

總體過程與上面所述的過程是同樣的,只是具體的實現不一樣而已:

一、  配置mysql的數據源(請參考上述配置)

二、  配置oracle的數據源(請參考JBOSS中相應的數據源配置模板,注意,須要拷貝oracle的數據庫驅動到jboss/server/default/lib下面)

三、  配置persistence.xml,示例以下:

<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <!-- transaction-type 也能夠取值爲RESOURCE_LOCAL -->

    <!-- 若是在一個EJB JAR包內定義了兩個以上的persistence-unit,則在這個EJB JAR包內的全部實體類,將 同時映射到這兩個persistence-unit所表明的數據庫中! -->

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

        <jta-data-source>java:/MySqlDS</jta-data-source>

        <class>com.bjsxt.jpa.User</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>

           <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>

          <property name="hibernate.hbm2ddl.auto" value="create"/>

          <property name="hibernate.show_sql" value="true"/>

        </properties>

    </persistence-unit>

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

        <jta-data-source>java:/OracleDS</jta-data-source>

        <class>com.bjsxt.jpa.Person</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>

           <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect"/>

          <property name="hibernate.hbm2ddl.auto" value="create"/>

          <property name="hibernate.show_sql" value="true"/>

        </properties>

    </persistence-unit>

</persistence>

 

 

 

四、  編寫測試的session bean

 

@Stateless(name="userManager") public class UserManagerImpl implements UserManager { @PersistenceContext(unitName="mysql") private EntityManager mysql; @PersistenceContext(unitName="oracle") private EntityManager oracle; public void addUser() { for(int i=0; i<10; i++){ User user = new User(); user.setName("張三"+i); mysql.persist(user); } for(int i=0; i<10; i++){ Person person = new Person(); person.setName("Person"+i); oracle.persist(person); } throw new RuntimeException("隨便一個異常!"); } } 

 

 

在聲明式事務管理中,如何控制事務的回滾?

在spring中,咱們知道,默認狀況下,拋出RuntimeException及其子類,將會致使事務的回滾,固然也能夠定義rollback-for屬性或not-rollback-for屬性來指定相應的異常類是回滾或不回滾。

在EJB3中,回滾的策略是:

一、  默認狀況下,拋出RuntimeException及其子類將致使事務回滾,其它異常不會回滾。

二、  咱們能夠定義一個本身的異常類,而後定義這個異常類用註解定義爲ApplicationException,指定它的回滾特性便可。

 

先看一段代碼:

public class MyException extends Exception { public class MyException1 extends RuntimeException { public void addMultiPerson() throws Exception { for(int i=0; i< 10; i++){ Person4 person = new Person4(); person.setName("P"+i); em.persist(person); if(i == 5){ //默認狀況下,拋出Exception及其子類不會進行回滾 //throw new MyException("拋出MyException"); //默認狀況下,拋出RuntimeException及其子類會進行回滾

              throw new MyException1("拋出MyException1"); } } }

 

在上面這段代碼中,已經說得很是清楚了。這是EJB3默認的回滾特性演示。

那麼,如何改變這種默認策略呢?請看以下代碼:

@ApplicationException(rollback=true) public class MyException extends Exception { @ApplicationException(rollback=false) public class MyException1 extends RuntimeException { public void addMultiPerson() throws Exception { for(int i=0; i< 10; i++){ Person4 person = new Person4(); person.setName("P"+i); em.persist(person); if(i == 5){ //在MyException上定義ApplicationException,並設置rollback=true,會致使拋出異常的時候,事務的回滾 //throw new MyException("拋出MyException"); //在MyException1上定義ApplicationException,並設置rollback=false,會致使拋出異常的時候,事務不回滾

              throw new MyException1("拋出MyException1"); } } }

 

 

 

 

如何定義事務的傳播特性?

在容器管理的事務中,咱們能夠定義事務的傳播特性。

事務傳播特性是指,當事務跨越多個組件的方法調用時,如何將事務由上級調用傳送到下級中去:

Not Supported – 不支持,若是當前有事務上下文,將掛起當前的事務

Supports - 支持,若是有事務,將使用事務,若是沒有事務,將不使用事務

Required - 須要,若是當前有事務上下文,將使用當前的上下文事務,若是沒有,將建立新的事務

Required New - 須要新的事務,若是當前有事務上下文,將掛起當前的事務,並建立新的事務去執行任務,執行完成以後,再恢復原來的事務

Mandatory - 當前必需要有事務上下文,若是當前沒有事務,將拋出異常

Never - 當前必須不能有事務上下文,若是有事務,將拋出異常

 

可經過@TransactionAttribute註解來定義

 

如何監聽實體對象的調用?

需求是:在存儲一個實體對象以後,須要主動觸發調用一段代碼

實現過程是:

一、假設咱們要監控Student對象的調用,那麼監聽器能夠以下編寫:

public class MyMonitor { /** * 必需帶一個參數!!!!不然部署不會成功! * 添加了Listener以後,JBoss須要從新啓動!! * @param student */ @PostLoad public void dosomething(Student student){ System.out.println("實體對象已經被成功加載!!!!!!name = "+ student.getName()); } }

 

二、只須要在Student實體類中指定咱們要採用哪一個類做爲監聽器便可

@Entity @EntityListeners(MyMonitor.class) public class Student implements Serializable{

 

 

如何攔截session bean的方法調用?

需求是:咱們想要攔截某個session bean的方法調用,可能要在攔截器中增長日誌記錄或安全控制之類的代碼(在spring中,咱們能夠經過AOP來作到這一點)

實現過程是:

一、首先實現一個攔截器類

public class InterceptorForStudent1Manager { @AroundInvoke public Object doit(InvocationContext context) throws Exception{ System.out.println("將要開始執行方法:"+context.getMethod().getName()); Object obj = context.proceed(); System.out.println("方法"+context.getMethod().getName()+"已被成功執行"); return obj; } }

 

二、在session bean 上以下定義便可:

@Stateless(name="Student1Manager") @Remote @Interceptors(InterceptorForStudent1Manager.class) public class Student1ManagerImpl implements Student1Manager {
相關文章
相關標籤/搜索