【转】EJB3.0 EntityManager及相关概念

EntityManager

概念图

 

基本概念

EntityManager 称为实体管理器,它由 EntityManagerFactory 所创建。 EntityManagerFactory ,作为 EntityManager 的工厂,包含有当前 O-R 映射的元数据信息,每个 EntityManagerFactory ,可称为一个持久化单元( PersistenceUnit ),每个持久化单元可认为是一个数据源的映射(所谓数据源,可理解为一个数据库,我们可以在应用服务器中配置多个数据源,同时使用不同的 PersistenceUnit 来映射这些数据源,从而能够很方便的实现跨越多个数据库之间的事务操作!)

 

       PersistenceContext ,称为持久化上下文,它一般包含有当前事务范围内的,被管理?氖堤宥韵? (Entity) 的数据。每个 EntityManager ,都会跟一个 PersistenceContext 相关联。 PersistenceContext 中存储的是实体对象的数据,而关系数据库中存储的是记录, EntityManager 正是维护这种 OR 映射的中间者,它可以把数据从数据库中加载到 PersistenceContext 中,也可以把数据从 PersistenceContext 中持久化到数据库, EntityManager 通过 Persistmergeremoverefreshflush 等操作来操纵 PersistenceContext 与数据库数据之间的同步!

       EntityManager 是应用程序操纵持久化数据的接口。它的作用与 hibernate session 类似。为了能够在一个请求周期中使用同一个 session 对象,在 hibernate 的解决方案中,提出了 currentSession 的概念, hibernate 中的 current session ,可以跟 JTA 事务绑定,也可以跟当前线程绑定。在 hibernate 中, session 管理着所有的持久化对象的数据。而在 EJB3 中, EntityManager 管理着 PersistenceContextPersistenceContext 正是被管理的持久化对象的集合。

       Java EE 环境下,一个 JTA 事务通常会横跨多个组件的调用(比如多个 EJB 组件的方法调用)。这些组件需要能够在单个事务范围内访问到同样的 Persistence Context 。为了满足这种情况的需要,当 EntityManager 被注入或通过 jndi 被查询的时候,它的 Persistence Context 将会在当前事务范围内自动传播,引用到同一个 Persistence unitEntityManager 将使用同样的 Persistence Context 。这可以避免在不同的组件之间传递 EntityManager 引用。

 

通过容器来传递 PersistenceContext ,而不是应用程序自己来传递 EntityManager 。这种方式 ( 由容器管理着 PersistenceContext ,并负责传递到不同的 EntityManager) 称为容器管理的实体管理器( Container-Managed EntityManager ),它的生命周期由容器负责管理。

 

有一种不常见的情况是,应用程序自身需要独立访问 Persistence Context 。即每次创建一个 EntityManager 都会迫使创建一个新的 Persistence Context 。这些 Persistence Context 即使在同一个事务范围内也不会跟其它 EntityManager 共享!这个创建过程可以由 EntityManagerFactorycreateEntityManager 方法来创建。这被称为应用管理的实体管理器( application-managed entity manager )。

底层事务控制

EntityManager 的底层可以使用 JTARESOURCE_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 类型。它可以取值为 TRANSACTIONEXTENDED 。可称为:事务范围的 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 中持久化到数据库中。如果设置 FlushModecommit ,那么在同一个事务中,在查询之前,如果有更新的数据,这些数据是否会影响到查询的结果,这种情况, EJB3 未作说明。

 

Lock – 锁定某个实体对象

实际上就是定义事务的隔离级别。总共有两种形式: READWRITE ,表示:

不管是 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;

    }

}
 

EntityManager 实例操作介绍

EJB3 如何实现继承关系的映射?

继承关系的映射,总共有三种策略,与 Hibernate 类似

 

1SINGLE_TABLE 策略:整个类继承树对应的对象都存放在一张表

 

父类定义:

@Entity

@Inheritance(strategy=InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name="animalType")

@DiscriminatorValue("A")

public class Animal {

 

子类定义:

 

@Entity

@DiscriminatorValue(value="B")

public class Bird extends Animal{

 

2JOINED 策略:父类和子类都对应不同的表,子类中只存在其扩展的特殊的属性(不包含

父类的属性)

 

父类定义:

@Entity

@Inheritance(strategy=InheritanceType.JOINED)

public class Animal {

 

子类定义:

@Entity

public class Bird extends Animal{

 

3TABLE_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 的事务中同时操纵 mysqlOracle 的数据库,一样可以实现事务的管理。

 

整体过程与上面所述的过程是一样的,只是具体的实现不同而已:

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

 

 

 

看着文章好才转的,还没全部仔细研究过,其中的中文乱码还有待修正。我看的也是别人转的,找不到原文地址了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值