下篇:可管理的POJO持久性
在Java虚拟机(JVM)里面,所有数据都被建模,并且被封装在树结构的类和对象中。然而,在后端关系数据库中,数据被建模成关系表,它们通过共享的键字段相互关联起来。同一数据却有两个不同的视图,这给企业Java的开发人员带来了挑战:如果你要把数据保存到持久性数据存储区,或者从持久性数据存储区获取数据,就必须在对象和关系表示之间来回转换数据,这个过程就叫作对象-关系映射(ORM)。在Java EE(Java企业版,以前叫J2EE)中,可以通过两个方法来完成对象-关系映射。
● 人工方法:使用Java数据库连接性(JDBC)直接处理持久性——这个简单的解决方法适用于简单的应用程序。JDBC API的类紧密地按照关系数据库里面的表、行和列进行建模。但必须在应用程序的内部对象模型和JDBC对象模型之间进行人工转换,如果应用程序的内部模型已经类似二维关系表,采用JDBC是最佳方法。
● 自动方法:可以把ORM任务交给框架去处理。框架通常提供了可以处理任何数据对象的API。通过这个API,可以保存、获取及查找数据库。框架在后台完成对象-关系的转换。因为针对特定关系的SQL查询不适合对象接口,ORM框架通常定义了自己的查询语言,可以为当前的关系数据库自动生成正确的SQL语句。对数据模型复杂的应用程序而言,基于框架的方法可以节省许多时间,并且减少出错。
ORM 框架
EJB 实体bean是Java EE中的“官方”ORM解决方案。不过在EJB1.x和2.x中,实体bean使用起来非常困难,这有两个原因:
● EJB 1.x和2.x实体bean必须符合严格的组件模型。每个bean类必须实现本地接口和业务接口。它们必须从某些抽象类继承而来,还要实现所有方法,即便许多方法是空的。有了这样一种严格的组件模型,就不可能利用EJB 1.x和2.x实体bean来构建面向对象的数据模型。
● EJB 1.x和2.x容器需要极其冗长的XML配置文件把实体bean映射到关系数据库里面的表。那些文件非常冗长,还容易出错。
简而言之,EJB 1.x和2.x实体bean是一种设计拙劣的ORM框架,既满足不了Java数据对象模型的需求,也满足不了关系表数据模型的需求。出于对EJB 1.x和2.x实体bean的不满,开发人员寻求ORM的其他方案。在实际环境中,采用开放源代码的Hibernate(由JBoss公司开发)和Oracle公司的TopLink是两个最成功的Java ORM框架。Hibernate和TopLink都基于POJO:它们不依赖任何预定义的组件模型。相反,它们获得POJO数据对象(采用简单的JavaBean格式)后,会自动解释如何把这些数据对象以及它们之间的关系映射到关系数据库。通常,一个JavaBean类映射到一张数据库表,类之间的关系通过表里面的外来键字段进行映射。可以在简单、直观的XML配置文件里面指定ORM元数据,譬如与JavaBean类相对应的表名以及与属性相对应的列名。可以通过框架中的工具类(如Hibernate中的Session类)来操作这些POJO(譬如保存、获取及查找)。
EJB 3.0建立在 Hibernate和TopLink的思想和成功这一基础上。它为Java EE提供了标准的POJO ORM框架。另外,较之现有的POJO持久性解决方案,EJB 3.0有两项重要创新:
● EJB 3.0让开发人员可以直接在POJO代码中注释映射信息,而不是使用XML文件来指定ORM元数据。譬如说,你可以用注释来指定与每个JavaBean属性相对应的关系列名。读者会在本文后面看到更多的示例。注释使得映射更直观,也更容易维护。
● EJB 3.0为实体bean定义了新的存档格式。每个存档定义了持久性上下文,后端数据库和ORM行为各使用独立的一组配置。本文会在后面讨论持久性上下文。
现在,我们不妨通过几个简单的示例来看一下EJB 3.0是如何实现POJO ORM的。
映射简单对象
在EJB 3.0中,每个实体bean都是JavaBean样式的简单类。为了告诉EJB 3.0容器这个类应当进行映象以实现持久性,应当用@Entity来注释这个类。
每个实体bean类映射到关系数据库表。默认情况下,表名与类名相对应。可以使用@Table注释,为该类指定另一个表名。bean类的每个JavaBean属性映射到表中的列。默认情况下,列名就是属性名。可以通过为属性的设置方法添加@Column注释,来改变这种默认关系。下面是EJB 3.0实体bean类的简单示例:
|
容器把Person类映射到Person SQL数据库表后,每个Person实例就是表中的一行数据。
映射简单的JavaBean类很容易。但需要映射相互关联的对象时,自动ORM框架的优点才会真正体现出来。本文会在后面介绍EJB 3.0是如何处理对象关系的。
关系
在数据模型中,类与类之间通常有着关系。譬如,Person对象可以与Resume对象联系起来,反之亦然(一对一关系);Person对象可以与多个CreditCard对象联系起来,而CreditCard对象只与一个Person对象相对应(一对多关系)。多个Person对象可以与一个Address对象联系起来,而一个Address对象只对应于一个Person对象(多对一关系)。
在对象模型中,对象引用负责处理这些关系。譬如说,Person对象可以有一个属性(即字段)来引用Resume对象,还有另一个属性是CreditCard对象的集合体。为了把对象之间的关系告诉EJB 3.0容器,只要在POJO中注释这些JavaBean属性。
|
在关系数据库中,这些关系由EJB 3.0容器使用外来键字段自动重新构建。譬如说,Person表有一个外来键字段,里面包含了Resume表中相应行的主键。运行时,EJB 3.0容器执行一对一的关系:它保证了Resume键值对Person表中的每一行来说是惟一的。为了实现Resume表到Person表的双向查询,也可以在Resume表中定义Person属性,并为其添加@OneToOne注释。
Person表中还有一个外来键字段,里面包含了Address表中相应行的主键。这种情况下,同一个Address主键可出现在多个Person行中,因为这是多对一的关系。至于一对多的关系,映射起来要复杂一点,因为外来键的列是在多对一表中的数据源里面定义的。所以在CreditCard类中,必须用@ManyToOne注释来定义Person属性。
上面讨论的相互关系只是实体bean关系的一种类型,实体bean类之间的另一种重要关系是继承。
继承
面向对象设计的一个重要概念就是继承。使用继承,你可以为对象创建复杂的树结构,而不需要重复代码。譬如说,顾问(Consultant)是提供有偿咨询服务的人。因而在我们的数据模型中,Consultant类从具有另外收费属性的Person类继承而来。遗憾的是,关系数据库里面没有继承这个概念。ORM框架主要依靠这两种方法来模仿这种行为:
● 框架可以为每个类生成单独的表。子类的表从超类的表当中复制了所有的列。子类和超类的实例被保存在相应的表中。
● 框架可以使用包含所有子类属性列的一张表。两种类的实例保存在同一张表中——超类对象的行把该类没有的列里面的值设为空值。为了让继承映射更健壮,表还有“区别”列,它里面存放的标记表明每行映射到哪个类。
EJB 3.0实体bean支持上述两种映射策略,默认情况下采用一张表映射策略。只要注释指明超类,即可指定继承策略和区别列的名字。下面是Consultant类的示例,它从Person类继承而来:
|
在上面的例子中,容器使用默认策略将Consultant类映射到同一张表中的Person类。如果表中的person_type列的值为C,当前行就代表Consultant对象。否则,当前行代表普通的Person对象。
持久性档案
鉴于数据模型有一组添加了注释的EJB 3.0实体bean类,就可以把它们捆绑起来,部署到服务器环境中。EJB 3.0为实体bean定义了特殊的存档文件格式,名为持久性存档(文件后缀名为.par)。
.par文件是包括诸多实体bean类组成的一个jar文件,另外加上一个简单的配置文件:META-INF/persistence.xml。persistence.xml文件定义了持久性上下文的名称,它告诉EJB 3.0使用哪个后端数据库(数据源)用于这一组实体bean。persistence.xml还包含了针对特定实现的配置属性。譬如说,JBoss EJB 3.0在Hibernate 3.0上面实现。所以你可以传递persistence.xml文件中的任何Hibernate配置选项。下面是作为示例的persistence.xml文件,专门针对JBoss和Hibernate的配置属性有关于SQL方言和二级缓存。
|
实体管理器
一旦部署了实体bean, 必须通过EJB 3.0实体管理器API来访问及操纵它们。EJB 3.0容器为每个已部署的持久性上下文(即.par文件)提供了一个实体管理器对象。可以从EJB 3.0会话bean POJO,通过@PersistenceContext注释注入实体管理器对象,并传入上下文的名字。
|
基本操作
为了创建新的数据对象,并把它保存到数据库中,只要使用Java的new关键字来创建POJO,并把它传递给EntityManager.persist()方法。
|
为了从数据库中获取对象,可以使用EJB 3.0查询语言来搜索数据库。下面的示例演示了如何让Person数据库表中的所有行作为Person Java对象的集合体来返回。
|
可管理的POJO
由实体管理器保存及获取的对象在持久性上下文中加以管理。这意味着,如果对象后来发生改变,这种改变会被自动检测到,并且被赋予持久性、发送到数据库中。在下面的示例中,我们更新了可管理的POJO的属性。改变会被EJB 3.0容器自动检测到,并发送到数据库中。
|
因为EJB 3.0实体只是POJO,它们可以进行序列化处理,并通过网络传递。如果某个对象不是由容器创建(譬如它从网络连接传递过来,或者是远程过程调用的返回值),持久性上下文就不会管理它。可以通过调用EntityManager.merge()方法,把一个非管理的POJO合并到持久性上下文中。下面是把经过反序列化的POJO合并到当前持久性上下文中的示例。
|
数据库同步
实体管理器对象用于会话bean时,它与服务器的事务上下文绑在一起。服务器的事务在提交时,实体管理器提交,并且把同步内容发送给数据库。在会话bean中,默认情况下,服务器的事务在调用堆栈末尾处提交。当然,也可以通过注释为每个业务方法指定详细的事务属性。下面的示例演示了如何为会话bean方法声明新的事务。
|
如果需要在事务提交前把更新内容刷新到数据库中,可以通过显式方式调用EntityManager.flush()方法。或者可以为方法添加@FlushMode(FlushModeType.NEVER)注释,那样事务管理器不会在这个方法结束时(即事务结束时)把更新内容刷新到数据库中。这种情况下,可以手动刷新所有的数据库更新,以实现最大程度的控制。
EJB 3.0 提供了一种简单、有效的框架,用于把Java POJO映射到SQL数据库中的关系表。它所采用的明智的默认映射策略基于Java类的结构和命名。不过也可以改动任何默认设置,使用简单的一组注释来处理复杂的对象关系。
EJB 3.0实体管理器提供了简单的API来查找及搜索来自数据库的对象,并为其赋予持久性。每个实体管理器对象与一组映射的POJO相关联,并有自己的数据库设置。它还可以自动与服务器的事务管理器绑在一起。
链接:用EJB 3.0简化企业Java开发(上)
(责任编辑:铭铭)