Java持久性API(The Java Persistence API)-一个简单的实体持久性编程模型

来源:互联网 发布:esports海涛淘宝店 编辑:程序博客网 时间:2024/05/22 16:51

 Java持久性API(The Java Persistence API)-一个简单的实体持久性编程模型

 原文连接:
http://java.sun.com/developer/technicalArticles/J2EE/jpa/
文章索引
Java平台企业版(Java EE)第五版本最重要的主题是易于开发。贯穿整个平台的改变使开发企业Java应用程序变得更简单,需要少的多的代码。值得注意的,这些简化没有降低平台的能力:Java EE 5维持了前一版本,J2EE 1.4的丰富功能。
企业开发者应该注意到Enterprise JavaBeans (EJB)的巨大简化。以前的文章比如Introduction to the Java EE 5 Platform 和 Ease of Development in Enterprise JavaBeans Technology描述了EJB 3.0做的简化。
EJB的一个主要增强是Java 持久性API的增加,它简化了实体持久性模型并加入了EJB2.1中没有的能力。Java持久性实体处理关系型数据映射到Java对象(“持久性实体”)的方法,对象存储到关系型数据库的方法,稍后便可以被访问。并且实体的存在甚至可以延续到使用它的程序结束之后。在简化实体持久性模型之外,Java持久性API将对象-关系的映射标准化。
简单说,EJB 3.0比EJB 2.1更易学,使应用程序开发更迅速。除了Java持久性API之外,EJB 3.0提供给开发者一个实体编程模型(entity programming model),它同样易于使用且更加丰富。
本文补充了早期只聚焦实体相关代码的文章。你将可以分析EJB 2.1实体bean,并将它们同EJB 3.0实体做比较。更明确的,你可以并排观看EJB 2.1实体bean和相同机能的EJB 3.0实体的源代码。注意,在Java持久性API中,实体bean现在被简单的称为实体。你将会看到EJB 3.0代码简单和改进了多少。
本文突出了一些重要的使EJB 3.0简易化的特性。虽然本文聚焦Java持久性API和它在EJB 3.0容器中的使用,API同样可以在容器以外使用——例如,在J2SE中。API同样提供对插件,第三方持久性提供者的支持。例如,一方卖主的持久性实现可以被另一方EJB容器使用,只要容器和持久性实现都符合JSR 200。
本文假设你熟悉EJB 2.1以下的EJB基本概念。否则,请看J2EE 1.4 Tutorial中Enterprise Beans章节。要看更多的关于EJB 3.0和Java持久性概念,请看Java EE 5 Tutorial中"Enterprise Beans章节。
 
内容
应用
实体
类定义
持久字段和属性
实体身份
关联
继承和多态性
操作实体
事务
查询
在EJB容器外测试实体
概要
更多信息
 
 
首先,我们来看一下实际应用。
应用
本文使用EJB2.1的应用程序,CMP客户实例应用程序,来自J2EE 1.4实例包。你可以从J2EE 1.4 Downloads页下载实例包。实例包包含安装和运行EJB2.1版应用程序的说明。
这个EJB 3.0版的应用被称为Java持久性演示,可以从这里#下载。下载包包含安装和运行的说明。
所有版本的应用程序做相同的事情:它们通过关系型数据库互相作用,存储和显示顾客订阅期刊的信息。数据库存储这样的信息例如顾客的姓名和地址,订阅期刊的类型,杂志,期刊或是报纸。你可以提交请求以显示订阅的相关信息。例如,你可以得到一位特定顾客的订阅列表。
图1显示交互的样子。点击可以放大每一张图片。
 
Figure 1: Java Persistence Demo Interactions
Figure 1: Java Persistence Demo Interactions
 
实体
我们来检查一下应用程序中实体相关的代码。首先,我们看一下实体需要哪些类和接口。看图2。比较EJB 2.1与EJB 3.0中的列表。
 
Figure 2: Required Classes and Interfaces
Figure 2: Required Classes and Interfaces
Click here for a larger image
 
你需要编码更少的类和接口
EJB 3.0的实体,你不再需要编码例如LocalAddressHome 和 LocalAddress等接口--甚至部署描述。所有你需要提供的是一个实体类。所以在EJB3.0版的应用程序中,一个实体所需要的从3个类--给本地接口和业务类--降低为一个实体类。简化不局限于实体。EJB 3.0技术除去了企业bean中任何类型的本地接口(home interface)。另外,你不需要实现EJBObject 和 EJBLocalObject接口。例如,会话bean现在只需要一个bean类和一个业务接口,那是一个简单的Java接口。
 
类定义
让我们看一下实体的代码。我们从比较EJB 2.1AddressBean类和EJB 3.0 Address类代码中的关键部分开始。首先,我们看类的定义。你可以通过点击图3中它们的名字,察看整个实体bean类和实体类。
 
Figure 3: Comparing Class Definitions
Figure 3: Comparing Class Definitions
Click here for a larger image
 
实体是一个无格式Java旧对象,所以不需要样板文件
EJB 2.1版的实体bean类实现javax.ejb.EntityBean接口。实际上,EJB 2.1实体bean类必须实现这个接口同EJB 2.1容器交互。因此,bean类必须实现接口的所有方法:ejbRemove, ejbActivate, ejbPassivate, ejbLoad, ejbStore, setEntityContext, unsetEntityContext.类必须实现这些回调方法即使不使用它们,如图3中EJB 2.1中的这些方法。
通过比较,EJB 3.0是一个简单,不抽象,有形的类--一个POJO,通过new符,你可以像其他简单Java类一样将它实例化。它不实现javax.ejb.EntityBean或任何其他强制容器类。由于实体类不再实现javax.ejb.EntityBean,你也不再需要实现任何回调函数。尽管如此,你可以仍旧实现实体类中的回调函数,如果你需要它们处理实体的生命周期事件。同时注意无参数的公共构造函数。在EJB 3.0中,实体类必须拥有一个无参数的公共或保护型构造函数。
注释使需要指定的东西最小化
@Entity元数据注释标志EJB 3.0类为一个实体。EJB 3.0和Java持久性API非常依赖与元数据注释,这是从J2SE 5.0引入的一个特性。注释由@号,注释的类型,有时跟着一个附加的”元素-值”对列表。EJB 3.0规范定义了多种注释的类型。一些指定bean类型,例如@Stateless;一些指定是否bean是一个远程的或本地的,例如@Remote 和 @Local;事务属性,例如@TransactionAttribute;安全性和方法许可,例如@MethodPermissions, @Unchecked, 和 @SecurityRoles.Java持久性API增加了注释,例如@Entity,指定为实体。EJB 3.0和Java持久性API对注释的信赖,显示了重大的设计变化。
 
默认项使事情更简单
在很多情况下,程序可以使用默认项替代明确的元数据注释元素。在这些情况下,你不需要完全指定元数据注释。你可以得到相同的结果就好像你已经完全的指定了注释。例如,@Entity注释有一个name元素,用来指定名称,用来查找相关的实体。name元素默认为实体类中没限制的名字。所以Address的代码中,@Entity的注释是空的。在注释中不需要指定名字。这些默认选项使注释的实体非常简单。在很多情况下,当注释没有指定,默认选项是假定的。在这些情况下,默认选项描述了最通常的规格。例如,容器管理的事务划分--容器,与bean相反,管理任务单元向数据库的提交或回滚--被作为企业bean的假定如果没有注释指定。这些默认项举例说明了coding-by-exception途径,指导EJB 3.0。处理过程对开发者更加简单。你仅需要当默认项不充分的时候编码。
 
持久字段和属性
让我们来比较怎样声明字段和属性的持久性。
 
Figure 4: Comparing Persistent Field and Property Declarations
Figure 4: Comparing Persistent Field and Property Declarations
Click here for a larger image
 
持久性声明更简单
EJB 2.1中,为指定类中哪个些字段在数据库中持久化,你需要为那些字段定义公共抽象的getter 和 setter方法,并在部署描述器中制做规格说明--这种方法使很多程序员感觉很笨拙。EJB 3.0不需要这些规格说明。本质上,持久性成为实体的一部分。实体的持久性状态可以通过它的持久字段或持久属性来描述。
 
 
请回忆,实体是一个POJO。同POJO一样,它可以拥有不抽象,私有的实例变量,类似Address类里的AddressID变量。在Java持久性API中,实体可以拥有基于字段或基于属性的访问,持久性提供者直接通过它的实例变量来访问实体的状态。在基于属性的访问中,持久性提供者使用JavaBeans格式的get/set存取方法来访问实体的持久属性。实体中所有的字段和属性都是持久的。尽管如此,你可以通过标记@Transient注释或Java关键字transient,取消字段或属性的持久性。
无需XML描述器声明持久字段
EJB 2.1,实体bean字段在bean部署描述器中是作为持久性字段确定的。部署描述器,ejb-jar.xml,经常是一个很大,很复杂的XML文件。在实体bean类中编写公共的存取方法之外,你还需要在部署描述器中为每个持久性字段指定一个cmp-field元素。在Java持久性API中,你不再需要提供部署描述器指定实体的持久性字段。
在可能的地方使用默认的映射
在EJB 2.1中,你需要在部署描述器,如sun-ejb-jar.xml中定义实体bean字段与它们的对应数据库列的映射。相对照,Java持久性API不需要XML描述器。作为替换,指定映射,你要通过使用@Column注释来标记适当的持久字段和属性存取方法。尽管如此,你可以使用默认注释的优势。如果你不为持久字段和属性指定@Column注释,将假定到同名字段和属性的数据库列的默认映射。虽然你不需要指定XML描述器,你可以通过选择注释来使用它们。使用XML描述器可能在具体化对象关系映射信息中是有用的。同样,多样的XML描述器,可能在剪裁到数据库的对象关系映射信息中是有用的。
Java持久性API标准化对象关系的映射,使非常轻便的应用成为可能。
实体身份
同样拥有简化为实体指定主键和复合键的方法。
Figure 5: Comparing Settings for Primary Keys
Figure 5: Comparing Settings for Primary Keys
Click here for a larger image
不需要XML描述器指定主键
在EJB 2.1中,实体的主键--那是它的唯一性标志--不是在实体bean类中指定而是在它的部署描述器中指定。在Java持久性API中,你不需要提供XML描述器指定实体主键。作为替代,你通过用@Id注释标记适当的持久字段或持久属性的方式,来指定实体类中的主键。你可以通过两种方式指定符合主键,使用@IdClass注释或@EmbeddedId注释。
关系
目前为止,焦点一直在编码简化上,涉及到基本的持久性,即实体的持久性和它们字段或属性的持久性。现在,让我们来看涉及实体关系的编码简化。由此,让我们聚焦CustomerBean类并将它同CustomerBean实体比较。CustomerBean类有同AddressBean一对多的单向性关系和同SubscriptionBean多对多的关系。让我们来看在EJB3.0指定相等的关系设置有多简单。
 
Figure 6: Comparing Relationship Declarations
Figure 6: Comparing Relationship Declarations
Click here for a larger image
 
关系声明更简单
EJB2.1中指定容器管理的关系相当的复杂。如果实体bean A同实体bean B有关系,你必须为相关联的实体beanB在实体bean A中指定抽象的getter 和 setter方法。另外,你必须在ejb-jar.xml部署描述器中为关系提供一个相当冗长的条目。在Java持久性API中,你就像任何POJO一样指定关系。另外,你要指定注释,描述关系和所有数据库相关表元数据的语义。你不需要XML描述器来指定实体关系。
 
 
注意,尽管如此,使用Java持久性API的程序,不像EJB 2.1容器管理的持久性,依赖于管理的关系。例如,不像EJB 2.1,Java持久性API在双向关系中需要设置backpointer参考。假设实体A拥有对实体B的双向关系。在EJB 2.1中,你需要做的是设置A到B的关系--潜在的持久性实现依赖于设置B到A的backpointer参考。Java持久性API要求设置双边关系的参考。这意味着你需要明确的调用b.setA(a) 和 a.setB(b).
 
注释指定多样性和有关信息
你在注释里声明关系,它基于相关的实体。持久字段和属性上的注释指定关系的多样性,例如一对多或多对多,和其他信息,比如相联结的和急于获取的。例如,在Customer实体的getAddresses存取方法上的@OneToMany注释,指定了同Address实体的一对多关系。
 
Customer实体是关系的自有边。在关系的另一个边没有相关的注释。所以这是一个同Address实体一对多单向性的关系。注释中为cascade元素设定的值,指定了Customer实体的生命周期运作必须联结到关系目标Address实体。还句话说,当一个Customer实例被创建为持久的,任何关联到Customer的Address实例也要是持久的。这意味着如果Customer实例被删除,相关的Address实例也同样被删除。FetchType值指定”急于获取”--它通知容器预取相关联的实体。
 
注意Customer 和 Subscription实体类间多对多的关系,它使用在两个类中的@ManyToMany注释和Customer类中的@JoinTable注释指定。Customer 或Subscription类都可以被指定为自由类。此例中,Customer是自由类,所以@JoinTable注释在类中被指定。Subscription类中的@ManyToMany注释涉及Customer类,通过mappedBy元素映射信息。由于此例中为关系使用了一个加入的表,@JoinTable注释指定加入表的外键列,那些表映射到相关实体的主键列。
关系的默认映射在可能的地方被使用
你可以更多的利用默认映射的优势,以为关系映射简化编码。如果你看一下本演示setup/sql目录下的create.sql文件,你将发现一个名为CUSTOMER_ADDRESS的加入表。注意,在Customer实体中指定Customer 和 Address实体的到表CUSTOMER_ADDRESS中列的映射不需要任何注释。这是因为表名和加入列的名字是默认的。
 
继承和多态性
Java持久性API的一项重要能力是它对继承和多态性的支持,这在EJB2.1是无法做到的。你可以将一个实体层次,即一个实体是另一个的子类,映射到一个关系数据库结构,并提交对基类的查询。查询被整个层次多态性的进行处理。
下面EJB3.0列中的代码显示了一个支持继承和多态性的例子。这个例子不是取自那个程序的代码,因为java持久性演示不使用这个特性。这里,ValuedCustomer是一个实体,它扩展了Customer实体。层次被映射到CUST表:
 
Figure 7: Support for Inheritance and Polymorphism
Figure 7: Support for Inheritance and Polymorphism
Click here for a larger image
 
通过注释指定继承
@Inheritance注释为实体类层次确定了映射策略。在这个例子中,由strategy元素值指定的策略是SINGLE_TABLE.这意味着基类Customer 和它的子类 ValuedCustomer被映射到一个单独的表。通过这种方式,你可以指定加入基类和子类的策略,或者将它们映射到不同表的策略。单表策略需要表中的一个辨别列,以辨别表中基类的行和子类的行。@DiscriminatorColumn注释确定了辨别列,本例中是DISC. discriminatorType元素指定了辨别列包含字符串。@DiscriminatorValue注释用来为相关联的实体指定辨别列的值。这里,ValuedCustomer实例与Customer实例相区别,是通过在辨别列里有VCUSTOMER的值。
默认项可以被多种继承规格使用
在EJB3.0和Java持久性API其他的许多部分,你可以依赖默认项更多的简化编码。例如,SINGLE_TABLE是继承策略的默认项,所以代码实例不需要指定它。实际上,如果你正在使用单表(single-table)继承,你不需要指定@Inheritance注释。@DiscriminatorColumn注释中discriminatorType元素的默认项是STRING,所以代码示例中的规格也不需要。同样,你不需要为字符串辨别列指定辨别值,默认项是实体的名字。
 
 
实体可以从其他实体和非实体继承
EJB3.0示例阐明了从实体的继承。但是继承自非实体的POJOs-行为和映射属性-都是被允许的。基础实体或非实体可以为抽象的或具体的。
实体上的操作
Java持久性API的另一个显著简化是执行实体操作的方法。例如,看一下删除一个订阅的方法,它在editCustomer.jsp文件里,比较EJB 2.1的 CustomerBean和EJB 3.0 的session bean-CustomerSession.
 
Figure 8: Comparing Operations on Entities
Figure 8: Comparing Operations on Entities
Click here for a larger image
 
实体操作直接在实体上执行
在EJB2.1中,客户端必须使用Java命名和目录接口(JNDI)查找bean。由此,JNDI获得一个表示bean本地接口的对象。作为响应,EJB容器创建一个bean的实例并初始化它的状态。然后客户端可以调用JNDI获得的对象的方法,以执行bean上的操作。在上面的EJB2.1代码实例中,JNDI被用来获得表示LocalSubscriptionHome接口的对象。在CustomerBean 的removeSubscription()业务方法中,findByPrimaryKey()方法查找有关的SubscriptionBean实例,并且删除对它的涉及。当CustomerBean的业务方法参与的这个事务提交时,关系的更新同步到数据库。许多开发者将此过程视为不必要的间接和复杂。
通过比较,Java持久性API不需要基于JNDI的查找。EntityManager实例被用来查找有关的Customer 和 Subscription实体。然后对subscription实例的涉及被从Customer的subscription列表中删除。同样,对Customer实例的涉及也被从Subscription的customer列表中删除--就如同POJOs。
实体管理器管理实体
EntityManager实例被用来管理一个持久性上下文中的实体的状态和生命周期。实体管理器负责创建和删除持久实体,通过实体主键查找实体。它同样允许查找在实体上运行。
依赖可以被注入
如图8的EJB 3.0代码示例所示,JNDI不再需要用来获取资源和企业bean上下文的其他对象的参考。作为替代,你可以在bean类中使用资源和环境参考注释。这些注释注入了企业bean所依赖的资源。在EJB 3.0代码示例中,PersistenceContext注释将会话bean所依赖的事务持久性上下文注入到实体管理器。容器然后处理获取资源的参考并将它提供给bean。依赖注入可以显著的简化对获取资源和环境参考的编码。
事务
有关事务的规格说明同样被简化
 
Figure 9: Comparing Transaction-Related Specifications
Figure 9: Comparing Transaction-Related Specifications
Click here for a larger image
 
不需要XML描述器来指定事务属性
在EJB 2.1中,你在通常很长和复杂的部署描述器中为容器管理的事务指定事务属性。在EJB 3.0中,不需要用XML描述器来指定事务属性。作为替代,你可以使用@TransactionManagement注释指定容器管理的事务,也可以指定bean管理的事务。@TransactionAttribute注释用来指定事务属性。在EJB 3.0代码示例中,@TransactionManagement注释为会话bean指定容器管理的事务。因为容器管理的事务是事务划分的默认类型,所以这里的注释并不需要。remove()方法上的@TransactionAttribute注释指定了REQUIRED的事务属性,这是事务的默认类型。所以注释也不必需。在指定其他事务类型时,你将需要注释,例如"Mandatory" 或 "Supports".
 
容器管理的实体的管理器是Java事务API实体管理器
在Java持久性API中,包括实体管理器操作的事务可以通过两种方式被控制,通过JTA或者通过实体事务API(EntityTransaction API)。事务通过JTA来控制的实体管理器被成为JTA实体管理器。事务通过EntityTransaction API来控制的实体管理器被称为本地资源实体管理器。容器管理的实体的管理器必须是一个JTA实体管理器。JTA实体管理器的事务在实体管理器之外启动和结束,实体管理器方法与调用它们的方法共享会话bean的事务上下文。
查询
Java持久性API中对查询的支持显著的增强。一些这方面的增进在图10中显示。
 
Figure 10: Comparing Query Specifications
Figure 10: Comparing Query Specifications
Click here for a larger image
 
无需XML描述器指定查询
在EJB 2.1中,你通过企业JavaBean查询语言(EJB QL)来为一个实体bean定义查询。你在部署描述器中为bean指定查询,并将它与查询者或bean的select方法相联结。在Java持久性API中,你可以在bean类本身里定义一个有名的或静态的查询。你也有创建动态查询的选择。为创建一个有名的查询,你首先要使用@NamedQuery注释定义有名的查询。然后你使用EntityManager的createNamedQuery方法创建先前定义的查询。在EJB 3.0示例中,两个有名的查询在Customer实体类中定义:findCustomerByFirstName 和 findCustomerByLastName. 有名的查询被创建在会话bean,CustomerSession中,它为实体提供客户端代码。为创建一个动态的查询,你要使用EntityManager的createQuery方法。Java持久性API提供一个扩展了EJB QL的Java持久性查询语言。你可以在有名的或动态的查询中使用Java持久性查询语言或本地SQL。
增加对动态查询和有名查询的支持
Java持久性API提供一个查询API以创建动态的和有名的查询。例如,CustomerSession类使用实体管理器上的查询API以创建有名的查询findCustomerByFirstName 和 findCustomerByLastName.查询方法的setParameter绑定参数到一个有名的参量。(顺便说一下,对有名变量的支持同样是Java持久性API的一个新特性:所以有名的和动态的查询可以使用有名的变量,和位置变量,虽然一个单独的查询不能混合两种变量。)例如,findCustomerByFirstName的setParameter方法在有名查询定义里绑定firstname参数到有名变量:firstName。getResultList方法返回查询结果。
所有使用Java持久性API的查询是多态的
这意味着当一个类被查询,所有符合查询标准的子类将被返回。
Java持久性查询语言是一种增强的查询语言
EJB QL是一个很流行的EJB平面。尽管如此,EJB QL缺乏全结构查询语言的一些特性。例如非常重要的更新和删除操作,外联操作,投影和子查询。Java持久性查询语言增加了这些特性。它也增加了对基于外联的预取的支持。Java持久性查询语言的全排列可以在静态或动态查询中使用。尽管如此,Java持久性演示没有使用Java持久性查询语言的增强功能。
在容器外测试实体
虽然这不可能在边对边的代码比较中示范,在EJB容器外测试实体现在简单的多。先前,实体bean组件模型--需要本地和组件接口,抽象化了实体bean类,和虚的持久字段--这导致很难在容器外测试实体bean。Java持久性API去掉了对这些接口的需要。实体bean类唯一需要的东西是一个具体的实体bean类--POJO--拥有持久字段或持久属性。另外,实体的生命周期通过实体管理器被控制,而不是通过一个本地接口,这个本地接口的生命周期方法由EJB容器实现。因为所有这些使实体更少的依赖于EJB容器的干涉,实体可以更简单的在容器外测试。
概要
新的Java持久性API的目标是简化持久实体的开发。它达成此目标是通过一个简单的基于POJO的持久性模型,这个模型降低了必需类和接口的数量。你使用POJO将你的数据模型化,然后通过注释来通知容器关于实体的特征和所需资源。你也为对象-关系映射和实体关系使用注释,和部署时的说明。这种基于注释的方法取消了对冗长复杂的基于XML的描述器的需要。在许多情况下,注释的默认项已经足够了。你只需在默认项不充分的时候编码指定注释的属性。
除了这些简化,Java持久性API增加了EJB 2.1中没有的能力,在开发和使用持久实体的过程中给你更多的功能和灵活性。你可以利用查询语言增强功能和新特性的优势,例如继承和多态性,以执行更强大的查询。你可以练习对查询更多的控制,并根据需要执行查询优化的细节。简而言之,Java持久性API比它的前任更简单和直觉,它提供一个强劲的API创建,管理和使用持久实体。
本文只能集粹一些Java持久性API的简化和改进。你可以通过Enterprise JavaBeans 3.0 Specification以得到更多这方面的信息。现在是尝试EJB 3.0和Java持久性API的绝好时机。 
 更多信息
  • EJB 3.0 Specification (JSR-220)
  • Enterprise JavaBeans Technology
  • Introduction to the Java EE 5 Platform
  • Ease of Development in Enterprise JavaBeans Technology
  • The Java EE 5 Tutorial
  • Annotations
  • Java EE 5 SDK Preview
  • Preview: NetBeans IDE 5.5 with NetBeans Enterprise Pack 5.5
  • Project GlassFish
  •  
    原创粉丝点击