基本概念
EntityManager稱為實體管理器,它由EntityManagerFactory所創建。EntityManagerFactory,作為EntityManager的工廠,包含有當前O-R映射的元數據信息,每個EntityManagerFactory,可稱為一個持久化單元(PersistenceUnit),每個持久化單元可認為是一個數據源的映射(所謂數據源,可理解為一個數據庫,我們可以在應用服務器中配置多個數據源,同時使用不同的PersistenceUnit來映射這些數據源,從而能夠很方便的實現跨越多個數據庫之間的事務操作!)
PersistenceContext,稱為持久化上下文,它一般包含有當前事務范圍內的,被管理的實體對象(Entity)的數據。每個EntityManager,都會跟一個PersistenceContext相關聯。PersistenceContext中存儲的是實體對象的數據,而關系數據庫中存儲的是記錄,EntityManager正是維護這種OR映射的中間者,它可以把數據從數據庫中加載到PersistenceContext中,也可以把數據從PersistenceContext中持久化到數據庫,EntityManager通過Persist、merge、remove、refresh、flush等操作來操縱PersistenceContext與數據庫數據之間的同步!
EntityManager是應用程序操縱持久化數據的接口。它的作用與hibernate session類似。為了能夠在一個請求周期中使用同一個session對象,在hibernate的解決方案中,提出了currentSession的概念,hibernate中的current session,可以跟JTA事務綁定,也可以跟當前線程綁定。在hibernate中,session管理着所有的持久化對象的數據。而在EJB3中,EntityManager管理着PersistenceContext,PersistenceContext正是被管理的持久化對象的集合。
在Java EE環境下,一個JTA事務通常會橫跨多個組件的調用(比如多個EJB組件的方法調用)。這些組件需要能夠在單個事務范圍內訪問到同樣的Persistence Context。為了滿足這種情況的需要,當EntityManager被注入或通過jndi被查詢的時候,它的Persistence Context將會在當前事務范圍內自動傳播,引用到同一個Persistence unit的EntityManager將使用同樣的Persistence Context。這可以避免在不同的組件之間傳遞EntityManager引用。
通過容器來傳遞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事務環境下進行的。
Container-Managed EntityManager必須是JTA類型的EntityManager,而Application-Managed EntityManager則既可以是JTA類型的EntityManager,也可以是RESOURCE_LOCAL類型的EntityManager。
配置示例:
| <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類似
1、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{
2、JOINED策略:父類和子類都對應不同的表,子類中只存在其擴展的特殊的屬性(不包含 父類的屬性)
父類定義: @Entity @Inheritance(strategy=InheritanceType.JOINED) public class Animal {
子類定義: @Entity public class Bird extends Animal{
3、TABLE_PER_CLASS策略:父類和子類都對應不同的表,子類中存在所有的屬性(包含從 父類繼承下來的所有屬性) @Entity @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) public class Animal {
子類定義: @Entity public class Bird extends Animal{
|
如何注入EntityManager對象?
1、首先要確保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>
|
2、在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對象。但某些特殊情況下,我們可能想要由應用程序本身來管理它,那么,可以采取如下方法來辦到這一點。
1、注入EntityManagerFactory對象
|
/** * 也可以注入EntityManagerFactory,手工管理PersistenceContext */ @PersistenceUnit(unitName="test") private EntityManagerFactory factory;
|
2、在程序中,用factory來創建和管理EntityManager對象
|
EntityManager em = factory.createEntityManager(); Student student = new Student(); student.setName("張三"); em.persist(student); em.close();
|
如何利用JTA實現跨越多個數據庫的事務控制(一)?
JTA 本身就支持跨越多個數據庫的事務控制。要實現這一點,我們需要有如下步驟:
1、 首先是數據源的配置,為了支持兩個以上的數據庫,我們必須針對每個數據庫單獨配置它的數據源,下面是配置了兩個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>
|
2、其次,在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>
|
3、編寫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的數據庫,一樣可以實現事務的管理。
整體過程與上面所述的過程是一樣的,只是具體的實現不同而已:
1、 配置mysql的數據源(請參考上述配置)
2、 配置oracle的數據源(請參考JBOSS中相應的數據源配置模板,注意,需要拷貝oracle的數據庫驅動到jboss/server/default/lib下面)
3、 配置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>
|
4、 編寫測試的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中,回滾的策略是:
1、 默認情況下,拋出RuntimeException及其子類將導致事務回滾,其它異常不會回滾。
2、 我們可以定義一個自己的異常類,然后定義這個異常類用注解定義為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注解來定義
如何監聽實體對象的調用?
需求是:在存儲一個實體對象之后,需要主動觸發調用一段代碼
實現過程是:
1、假設我們要監控Student對象的調用,那么監聽器可以如下編寫:
public class MyMonitor { /** * 必需帶一個參數!!!!否則部署不會成功! * 添加了Listener之后,JBoss需要重新啟動!! * @param student */ @PostLoad public void dosomething(Student student){ System.out.println("實體對象已經被成功加載!!!!!!name = "+ student.getName()); } }
|
2、只需要在Student實體類中指定我們要采用哪個類作為監聽器即可
@Entity @EntityListeners(MyMonitor.class) public class Student implements Serializable{
|
如何攔截session bean的方法調用?
需求是:我們想要攔截某個session bean的方法調用,可能要在攔截器中增加日志記錄或安全控制之類的代碼(在spring中,我們可以通過AOP來做到這一點)
實現過程是:
1、首先實現一個攔截器類
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; } }
|
2、在session bean 上如下定義即可:
@Stateless(name="Student1Manager") @Remote @Interceptors(InterceptorForStudent1Manager.class) public class Student1ManagerImpl implements Student1Manager {
|
