服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

ejb3 persistence api标准化持久性


  持久性对于大多数企业应用程序都非常关键,因为它们需要访问关系数据库(例如oracle database 10g)。如果您正在使用java开发应用程序,您可能需要完成一些常规任务(例如数据库更新和检索),这是通过编写jdbc和sql来完成的。最近几年,几种对象关系(o-r)映射框架(例如oracle toplink、jboss hibernate和bea kodo)开始流行,因为它们简化了持久性问题,将java开发人员从编写jdbc代码的工作中解放出来,从而使他们能够将精力集中于业务逻辑。一些java标准(例如ejb 2.x容器管理持久性(cmp)实体bean)也试图解决持久性挑战,但是不那么成功。

  虽然存在多种构建应用程序持久层的选择,但是还没有一种面向java平台的、在java ee和java se环境下均可使用的持久性标准。好消息是ejb3 java persistence api (jpa)(它是ejb 3.0规范jsr-220的一部分)的出现,它标准化了面向java平台的持久性api。jsr-220为o-r映射供应商(例如toplink、hibernate和kodo)以及其他领先的应用服务器供应商和jdo供应商所广泛接受。ejb3规范提供了一种极有吸引力的选择,用于构建企业java应用程序的持久层。

  在本文中,我将介绍ejb3 java persistence api,我将使用一个简单的域对象模型作为示例。

  域模型

  在构建企业应用程序时,我们首先会设计希望将其保存在数据库中的域对象模型;然后,与数据库设计人员合作,确定数据库模式。域模型表示了持久性对象或实体。实体可以是人、地方或事物,您存储关于它们的数据。它包含数据和行为。富域模型具有所有面向对象的行为特征,例如继承性和多态性。

  我们的简单域模型(图1)具有department与employee实体之间的双向一对多关系。fulltime和contractor实体继承自employee实体。

ejb3 persistence api标准化持久性
图1.示例域对象模型

  o-r框架和ejb3 jpa基础知识

  如果使用过o-r映射框架(例如oracle toplink)构建应用程序持久层,您就会注意到,每种框架都提供三种工具:

  一种声明式地执行o-r映射的方式。这种方法(称为o-r映射元数据)允许将对象映射到一个或多个数据库表。通常,大多数o-r框架使用xml存储o-r映射元数据。

  一个用于操作实体(例如,执行crud操作)的api。此api允许持久化、检索、更新或移除对象。基于api和o-r映射元数据的使用,o-r框架代表开发人员执行数据库操作。此api将开发人员从编写jdbc或sql代码以持久化域对象的工作中解放出来。

  一种用于检索对象的查询语言。这是持久性最重要的方面,因为非法的sql语句可能会降低数据库的速度。此方法也对应用程序屏蔽了混乱地遍布应用程序的的专有sql。查询语言允许检索实体或对象,并将开发人员从编写sql select语句的工作中解放出来。

  ejb3 java persistence api (jpa)提供一种标准o-r映射机制、一个执行crud操作的entitymanager api以及一种扩展ejb-ql以检索实体的方式,从而标准化了面向java平台的持久性的使用。我将在后面讨论这三个方面。

  启用元数据注释

  java se 5.0引入了元数据注释。java ee的所有组件(包括ejb3 jpa)大量使用元数据注释以简化企业java开发。要了解关于元数据注释的更多信息,请参阅kyle downey所著的bridging the gap: j2se 5.0 annotations。在ejb3 jpa中,注释可以用于定义对象、关系、o-r映射和持久性上下文的注入。jpa还提供使用xml描述符来代替的选择。我将主要介绍元数据注释的使用,因为它们大大简化了开发。不过,您可能更倾向于在生产部署环境中使用xml描述符,因为可以使用它们重写注释。

标准化jpa中的o-r映射

  定义持久对象:实体

  实体是轻量级的域对象――您希望将其保存在关系数据库中的plain old java object (pojo)。像任何pojo一样,实体可以是抽象或具体类,它能够扩展另一个pojo。可以使用javax.persistence.entity注释将pojo标记为实体。

  以下代码将使域模型中的department对象成为实体:

package onjava;
import java.io.serializable;
import java.util.collection;
import javax.persistence.*;
@entity
@namedquery(name="findalldepartment", query="select o from department o")
@table(name="dept")
public class department implements serializable {
@id
@column(nullable=false)
protected long deptno;
@column(name="dname")
protected string name;
@column(name="loc")
protected string location;
@onetomany(mappedby="department")
protected collection employees;
public department() {
}
...
public collection getemployees() {
return employees;
}
public void setemployees(collection employees) {
this.employees = employees;
}
public employee addemployee(employee employee) {
getemployees().add(employee);
employee.setdepartment(this);
return employee;
}
public employee removeemployee(employee employee) {
getemployees().remove(employee);
employee.setdepartment(null);
return employee;
}
}

  每个实体都有一个主键;可以在持久字段或属性上使用id注释将其标记为主键。实体通过使用字段或属性(通过setter和getter方法)来保存其状态。这取决于在哪里使用o-r映射注释。以上示例使用基于字段的访问;我们已经使用了具有deptno字段的id注释。要使用基于属性的访问,就要使用属性标记注释(例如id),如下所示:

@id
public long getdeptno() {
return deptno;
}
public void setdeptno(long deptno) {
this.deptno = deptno;
}

  请记住,对一个实体层次结构中的所有实体,必须应用相同的访问类型(字段或属性)。

  默认情况下,定义在实体中的每个字段天然就是持久的;如果不希望保存字段/属性的状态,则必须将字段/属性定义为瞬态的,方法是使用@transient注释或transient修饰符标记它。

  可嵌入对象

  可嵌入对象是不具有自己标识的持久对象;它是另一个实体的一部分。例如,我们可以假定address没有自己的标识,且作为employee实体的一部分存储。因此,address是可嵌入对象的候选。

  可以如下所示创建可嵌入对象:

@embeddable
public class address {
protected string streetaddr1;
protected string streetaddr2;
protected string city;
protected string state;
..
}

  以下是将对象定义为目标实体中的可嵌入对象的方法:

@entity
public class employee {
@id
@generatedvalue(strategy=generationtype.auto)
protected long id;
...
@embedded
protected address address;
...
}

关系

  在一个典型的域模型中,实体是彼此关联的,或者它们相互之间存在着关系。两个实体之间的关系可以是一对一、一对多、多对一和多对多的。这些关系可以分别使用onetoone、onetomany、manytoone或manytomany注释表示。在我们的示例中,department和employee实体之间具有双向onetomany关系。

  既然我们在实体中使用了基于字段的访问,我们就在department实体的关系字段上指定注释,如下所示:

@onetomany(mappedby="department")
protected collection<employee> employees ;

  对于双向关系,必须在关系的另一方指定mappedby元素(如上),方法是指向拥有此关系的字段或属性的名称。

  标准化o-r映射

  可以使用java元数据注释或xml实现实体的o-r映射。ejb3 jpa定义了多种用于o-r映射的注释,例如table、secondarytable、column、joincolumn和primarykeyjoincolumn。请参阅ejb3 jpa规范,以获得关于所有注释的信息。

  在我们的示例中,可以使用table注释定义实体被映射到的表,如下所示:

@table(name="dept")
public class department implements serializable {

  ejb3 jpa严重依赖于默认设置,因此如果未定义表映射,则持久性提供程序会假定此实体被映射到与实体类同名的表(在我们的示例中是department)。如果实体被映射到多个表,则可以使用secondarytable注释。

  可以使用column注释将持久字段或属性映射到数据库列,如下所示:

@column(name="dname")
protected string name;

  这里,dname是持久字段名称所映射到的列的名称。如果未使用column注释定义o-r映射,则持久性引擎会尝试将其状态保存在列中(使用与字段或属性相同的名称)。

  实体继承性

  ejb3 jpa支持多种实体继承性方法。它需要两种类型的继承性表映射策略:single-table-per-entity层次结构策略和joined-subclass策略。最好避免使用可选的table-per-class层次结构。

  single-table-per-entity (single_table)层次结构策略允许将层次结构中的所有实体映射到一个表。在我们的示例中,fulltime和contractor扩展了employee,所有这些都可以映射到一个名为emp的表。换句话说,所有与employee、fulltime和contractor有关的数据都被存储于相同的表内。

  如果使用joined subclass策略,则可以将公共持久数据存储在超类所映射到的表中(例如employee),并且可以为层次结构中每个子类创建表,以便存储特定于子类的持久字段。

  必须在超类中使用inheritance注释,以指定继承类型,如以下代码所示。此示例展示了使用single-table-per-entity层次结构策略的实体层次结构。

@entity
@table(name="emp")
@inheritance(strategy=inheritancetype.single_table)
@discriminatorcolumn(name="employee_type",
discriminatortype=discriminatortype.string, length=1)
public abstract class employee implements serializable {
...
}

  每个子类必须指定用于该实体类型的鉴别器值,如下所示:

@entity
@discriminatorvalue(value="f")
public class fulltime extends employee {
@column(name="sal")
protected double salary;
@column(name="comm")
protected double commission;
@column(name="desig")
protected string designation;
...
}

  entity manager api:用于实体操作的标准api

  javax.persistence.entitymanager管理实体生命周期,并公开了多个在实体上执行crud操作的方法。

  entitymanager api在事务上下文中调用。可以在ejb容器外部(例如,从一个web应用程序)调用它,而无需会话bean外观。

  在执行任何实体操作之前,必须获取entitymanager实例。可以使用容器管理或应用程序管理的实体管理器,可以使用jndi查找或依赖注入来获取entitymanager实例。正如其名称所暗示的,java ee容器管理着容器管理实体管理器的生命周期。它可能主要在企业java应用程序中使用。

  可以使用persistencecontext注入获取容器管理实体管理器实例,如下所示:

@persistencecontext(unitname="onjava")
private entitymanager em;

  如果使用应用程序管理的实体管理器,则必须管理其生命周期。可以创建一个应用程序管理实体管理器实例,如下所示:

@persistenceunit(unitname="onjava")
private entitymanagerfactory emf;
private entitymanager em = emf.createentitymanager();

  然后可以使用entitymanager实例在实体上执行crud操作。要关闭应用程序管理实体管理器实例,请在完成工作后调用em.close()方法。

  如前所述,必须在事务上下文中执行涉及任何数据库更改的实体管理器操作。

  下表列出了entitymanager接口的一些用于执行实体操作的关键方法。

方法用途
public void persist(object entity);持久化实体实例。
public <t> t merge(t entity); 合并分离的实体实例。
public void remove(object entity);移除实体实例。
public <t> t find(class<t> entityclass, object primarykey); 通过主键检索实体实例。
public void flush();使实体状态与数据库同步。

  可以使用persist()方法持久化实体实例。例如,如果想持久化contractor实例,请使用以下代码:

@persistencecontext(unitname="onjava")
private entitymanager em;
...
contractor pte = new contractor();
pte.setname("nistha")
pte.sethourlyrate(new double(100.0));
em.persist(pte);

  在持久化实体时,如果此关系的cascadetype被设置为persist或all,则任何对关联实体的状态更改也将被持久化。除非正在使用扩展的持久上下文,否则实体将在事务终止后分离。合并操作允许将分离的实体实例与持久上下文合并;分离实体的状态将与数据库同步。这将有助于摆脱ejb 2.x中常见的数据传输对象(data transfer object,dto)反模式,因为作为pojo的实体可以在层与层之间传输。惟一的要求是实体类必须实现java.io.serializable接口。 查询api

  对实体的检索是持久性的一个重要方面。使用ejb3 jpa时,使用java持久化查询语言(java persistence query language,jpql)表示查询。jpql是ejbql的扩展,它是作为ejb 2.0规范的一部分而引入的。然而,ejb3 jpa解决了ejbql的所有局限性,并添加了许多新特性,从而成为一种功能强大的查询语言。

  jpql较之ejbql 2.x的改进

  以下是ejb3 jpa中的jpql的新特性:

  ?简化了的查询语法

  ?join操作

  ?group by和having clause

  ?子查询

  ?动态查询

  ?指定参数(named parameter)

  ?批量更新和删除

  此外,如果希望从特定于数据库的查询扩展中获益,则必须对查询实体使用原生(native ) sql。

  动态查询与指定查询

  可以使用动态查询或指定查询(named query)。指定查询随实体存储,可从应用程序重用。

  要创建动态查询,请使用实体管理器接口的createquery方法,如下所示:

query query = em.createquery(
"select e from employee e where e.empno > 1");
query.setparameter(1,100);
return query.getresultlist();

  如果希望将此查询用作指定查询,请在实体中使用namedquery注释,如下所示:

@entity
@namedquery(name="findallemployee",
query="select e from employee e where e.empno > 1")
public abstract class employee implements serializable {
}

  要执行指定查询,首先使用entitymanager接口上的createnamedquery方法创建一个query实例,如下所示:

query = em.createnamedquery(" findallemployee");
query.setparameter(1,100);
return query.getresultlist();

  指定参数

  可以在ejbql查询中使用指定参数(named parameter)代替位置参数(positional parameter)。例如,可以将以上查询重写如下:

  "select e from employee e where e.empno > :empno "

  如果在查询中使用指定参数,则必须设置此参数如下:

query = em.createnamedquery("findallemployee");
query.setparameter("empno",100);
return query.getresultlist();

  打包

  ejb3 jpa标准化了pojo持久性。因此,实体并不局限于ejb模块;它们能够打包到web模块、ejb-jar模块、ear级中的库模块或标准jar文件中。也可以在java se中使用实体。必须在包含实体的档案文件中打包描述符(persistence.xml),如下所示:

<persistence>
<persistence-unit name="onjava">
<provider>oracle.toplink.essentials.persistenceprovider</provider>
<jta-data-source>jdbc/oracleds</jta-data-source>
...
</persistence-unit>
</persistence>

  此描述符标识持久性提供程序、持久单元和持久单元所使用的数据源。正如其名称所暗示的,持久单元是集中管理的实体的集合。如果有一个定义在特定模块中的持久单元,就不需要在persistence.xml中标识实体类;它将由持久性提供程序动态发现。

  参考实现

  bea kodo以及oracle toplink的toplink essentials都提供了ejb3 jpa的参考实现。它可分别从open jpa和glassfish开源项目中得到。

  您可以在参考实现服务器或其他任何服从ejb3 jpa的应用服务器上使用本文中的代码。

  ejb3 jpa工具

  开发工具确实能够帮助构建更好的应用程序,而如果使用xml实现o-r映射,情况可能就不太妙了。eclipse dali o-r映射项目,oracle jdeveloper 10.1.3和bea workshop studio之类的工具都支持ejb3 jpa。

  结束语

  ejb3 java persistence api标准化了面向java平台的持久性api。它通过使用元数据注释和异常配置方法,简化了透明持久性的使用。多种应用服务器已支持ejb3规范(编者注:bea已经发布weblogic server ejb 3.0 tech preview )。随着java ee 5.0和ejb 3.0规范的完成,您将很快看到许多一流的应用服务器和持久性提供程序会实现ejb3 java persistence api。您可以使用来自glassfish项目的 参考实现 来启用ejb3 persistence。

扫描关注微信公众号