操作持久化Entity
对Entity进行操作的API都设计在javax.persistence.EntityManager接口上。EntityManager,顾名思义是管理所有EJB 3运行环境中的所有Entity。 EntityManager根据运行的环境不同分为容器管理的EntityManager和应用管理的EntityManager。Liberator EJB3是一个可以运行在J2SE环境中的 嵌入式的EJB3 Persistence实现,因此在Liberator EJB3中的 EntityManager都是指应用管理的EntityManager。
配置和获得EntityManager
在J2SE环境中,EJB3定义了一个javax.persistence.Persistence类用于启动EJB3运行环境。要获得EntityManager,首先需要通过javax.persistence.Persistence获得EntityManagerFactory,然后调用EntityManagerFactory.createEntityManager()方法获得。
// 获得默认当前的EntityManagerFactory
final EntityManagerFactory emf = Persistence.createEntityManagerFactory();
final EntityManager entityManager = emf.createEntityManager();
当调用Persistence.createEntityManagerFactory()的时候,Persistence会做以下的步骤:
- 搜索当前jar包的META-INFO/persistence.xml配置文件
- 如果没有在META-INFO下找到persistence.xml,搜索当前线程的ContextClassLoader中的persistence.xml
- 根据获得的persistence.xml初始化EntityManagerFactory
每个EntityManagerFactory都必须有一个persistence.xml配置文件。下面是一个例子:
<entity-manager>
<name>myEntityManager</name>
<provider>com.redsoft.ejb3.PersistenceProviderImpl</provider>
<class>com.redsoft.samples.HelloEntityBean</class>
<properties>
<property name="ConnectionDriverName" value="com.mysql.jdbc.Driver"/>
<property name="ConnectionURL" value="jdbc:mysql://localhost/EJB3Test"/>
<property name="ConnectionUserName" value="ejb3"/>
<property name="ConnectionPassword" value="ejb3"/>
<property name="RetainValues" value="true"/>
<property name="RestoreValues" value="false"/>
<property name="IgnoreCache" value="false"/>
<property name="NontransactionalRead" value="true"/>
<property name="NontransactionalWrite" value="false"/>
<property name="Multithreaded" value="false"/>
<property name="Liberator.option.action" value="create"/>
<property name="Liberator.option.cache" value="com.redsoft.jdo.enterprise.OscacheAdapter"/>
<property name="Liberator.option.JMXEnabled" value="false"/>
<property name="Liberator.option.NamingPort" value="3012"/>
<property name="Liberator.option.JMXPort" value="3011"/>
<property name="Liberator.option.JDBC.scrollable" value="false"/>
<property name="Liberator.option.JDBC.fetchSize" value="500"/>
<property name="cache.algorithm" value="LRUCache"/>
<property name="cache.capacity" value="5000"/>
<property name="log4j.logger.com.redsoft" value="FATAL"/>
</properties>
</entity-manager>
<name></name> 定义一个EntityManagerFactory的名字,这样当应用中需要多个EntityManagerFactory的时候可以区别。通常情况下,一个EntityManagerFactory对应一个数据源。
<provider></provider> 定义采用的什么EJB3 Persistence运行环境(也就是EJB3 Persistence产品),例子中采用的的是Liberator EJB3的运行环境。正如前面说的,在EJB3规范中, EJB3 Persistence运行环境( Runtime )是作为一个独立的嵌入式模块,你可以随意的更换不同的EJB3 Persistence运行环境而不会影响你的应用程序。甚至在J2EE服务器内运行你也可以不用服务器本身提供的EJB3 Persistence运行环境,而采用第三方的EJB3 Persistence运行环境。
<class></class> 定义哪些类是Entity持久化类。如果是测试环境或者你不采用ear/war的方式发布你的应用,而只是把编译后的类直接拷贝到web容器中,在添加一个新的Entity类时,需要在这里添加一行。要求这个步骤是因为在J2EE 5架构中EJB3运行环境的嵌入式设计。采用这种松耦合设计使EJB3 Persistence运行环境不能控制持久化类的加载(由J2EE服务器负责加载), 这样EJB3 Persistence运行环境就无法分辨jvm中哪个类是持久化Entity类,哪个不是。因此需要一个额外的信息来让EJB3 Persistence知道那些是持久化Entity。
<property></property> 定义了了EJB3 Persistence运行环境需要的其他参数,这些参数不是标准的,而是由EJB3 Persistence运行环境的厂商自行定义。
详细的配置方法参看persistence.xml的语法。
Entity的生命周期和状态
在EJB3中定义了四种Entity的状态:
- 新实体(new)。Entity由应用产生,和EJB3 Persistence运行环境没有联系,也没有唯一的标示符(Identity)。
- 持久化实体(managed)。新实体和EJB3 Persistence运行环境产生关联(通过persist(), merge()等方法),在EJB3 Persistence运行环境中存在和被管理,标志是在EJB3 Persistence运行环境中有一个唯一的标示(Identity)。
- 分离的实体(detached)。Entity有唯一标示符,但它的标示符不被EJB3 Persistence运行环境管理, 同样的该Entity也不被EJB3 Persistence运行环境管理。
- 删除的实体(removed)。Entity被remove()方法删除,对应的纪录将会在当前事务提交的时候从数据库中删除。
持久化Entity(Persist)
final EntityManagerFactory emf = Persistence.createEntityManagerFactory();
final EntityManager entityManager = emf.createEntityManager();
final HelloEntityBean hello = new HelloEntityBean( 1, "foo" );
EntityTransaction trans = entityManager.getTransaction();
trans.begin();
// 持久化hello,在此操作之前hello的状态为new
entityManager.persist( hello );
// 这时hello的状态变为managed
trans.commit();
entityManager.close();
// 这时hellow的状态变为detached.
Liberator EJB3支持Persistence by reachability。当保存一个Entity时,以该对象为根对象的整个对象图都会自动的被保存。但在EJB3中,我们仍然可以通过关系元数据(比如OneToOne,OneToMany)的cascade属性来精确定义保存的级联行为。 下面我们来看看不同的cascade属性的区别。
不配置cascade的情况下,EJB3 Persistence运行环境默认不会采用Persistence by reachability。
public class Father{
@Id
int id
String name;
// OneToOne没有配置cascade属性,因此默认不会使用Persistence by reachablity
@OneToOne
Son mySon
public Father( int id, String name, Son mySon ){
this.id = id;
this.name = name;
this.mySon = mySon;
}
}
现在来保存一个Father和Son。
final EntityManager manager = emf.createEntityManager();
manager.getTransaction().begin;
Son mySon = new Son();
Father = new Father( 1, "father" mySon );
// 保存Father
manager.persist( father );
// 由于OneToOne关系中没有配置casacade属性,father 关联的mySon不会被自动保存,需要分别保存
manager.persist( mySon );
manager.getTransaction().commit();
manager.close();
现在我们配置casacde=CascadeType.ALL
public class Father{
@Id
int id
String name;
// OneToOne配置cascade=CascadeType.ALL,配置cascade=CascadeType.PERSIT也对persist操作也可以获得同样的效果。
// CascadeType.ALL包含CascadeType.PERSIST。
@OneToOne(cascade=CascadeType.ALL)
Son mySon
public Father( int id, String name, Son mySon ){
this.id = id;
this.mySon = mySon;
this.name = name;
}
}
在代码中同样持久化Father和mySon。
final EntityManager manager = emf.createEntityManager();
manager.getTransaction().begin;
Son mySon = new Son();
Father = new Father( 1, mySon );
// 保存Father。由于OneToOne关系中配置casacade=CascadeType.ALL属性,关联的mySon会自动地被持久化
manager.persist( father );
manager.getTransaction().commit();
manager.close();
由于Liberator EJB3运行环境采用了弱引用POJO管理技术,能很好的支持Persistence by reachablity而不会有其他产品有的潜在内存回收的问题, 因此建议在应用中
尽可能使用cascade=CascadeType.ALL来减少持久化操作的复杂性和代码量,特别是在有复杂对象关系图的时候。
获取Entity
如果知道Entity的唯一标示符,我们可以用find()方法来获得Entity。
Father father = manager.find( Father.class, new Integer( 1 ) );
// 由于JDK1.5支持自动转型,也可以如下使用
Father father = manager.find( Father.class, 1 );
/*
* 或者,可以用Entity名字作为查找。但无法利用JDK 1.5的自动转型功能,
* 需要使用对象作为查找主键,并需要对获得Entity进行转型
*/
Father father = (Father)manager.find( "com.redsoft.samples.Father", new Integer( 1 ) );
更新Entity
对Entity的更新必须在事物内完成。和persist中一样,关系元数据的cascade属性对是否集联删除有影响。
transaction.begin();
Father father = manager.find( Father.class, 1 );
// 更新原始数据类型
father.setName( "newName" );
// 更新对象引用
Son newSon = new Son();
father.setSon( newSon );
// 提交事务,刚才的更新同步到数据库
transaction.commit();
删除Entity
对Entity的删除必须在事物内完成。
transaction.begin();
Father father = manager.find( Father.class, 1 );
// 如果father/son的@OneToOne的cascade=CascadeType.ALL,在删除father时候,也会把son删除。
// 把cascade属性设为cascade=CascadeType.REMOVE有同样的效果。
manager.remove( father );
// 提交事务,刚才的更新同步到数据库
transaction.commit();
脱离/附合(Detach/Merge)
在三层或者分布式应用中,我们很多时候需要Entity能脱离EntityManager,避免长时间保持EntityManager打开占用资源和可以在不同的JVM之间传递Entity。
在脱离EJB3 Persistence Runtime(EntityManager)的管理后,我们仍然可以读取或者修改Entity中的内容。而在稍后的时间,我们又可以将Entity重新和原有或者新的EntityManager附合,如果附合前Entity被改动过,更改的数据可以自动的被发现并和数据库同步。
EntityManager entityManager = emf.createEntityManager();
// 这时Father还是被EntityManager管理的
Father father = manager.find( Father.class, 1 );
// 当entityManger关闭的时候,当前被entityManager管理的Entity都会自动的脱离EntityManager,状态转变为detached
entityManager.close();
// 脱离EntityManager后,我们仍然可以修改Father的属性
father.setName( "newName" );
// 在稍后的,我们可以将father重新附和到一个新的或者原来的EntityManager中
EntityManager newEntityManager = emf.createEntityManager();
// 附合( merge )需要在事务中进行
newEntityManager.getTransaction().begin();
newEntityManager.merge( father );
// commit后father中的被修改的内容会同步到数据库。
newEntityManager.getTransaction().commit();
Liberator EJB3 Persistnece运行环境由于支持Persistence-by-reachablity。我们建议采用cascade=CascadeType.ALL,这样当需要附合的是一个对象图的时候,我们只需要merge根对象即可,整个对象图,对象图中被修改过的对象都会被自动识别和同步。
需要注意的是在脱离EJB3 Persistence Runtime的管理后,如果对象中有定义为lazy-load的属性将无法访问。