使用 EJB 3.0 Java Persistence API 设计企业应用程序

本文将介绍 Java™ Platform, Enterprise Edition (Java EE 5) 的一种设计方法,它利用了 Enterprise JavaBeans™ (EJB) 3.0 新的 Java Persistence API (JPA)。JPA 提供了一种标准的对象关系映射解决方案,该解决方案避免了依赖第三方框架(如 Hibernate)。您将看到示例应用程序的详细内容,其中验证了本方法并阐明关键设计决定。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

期待已久的下一版本 Java EE 5 即将发布(参见 参考资料 获得规范和预览版的链接)。Java EE 5 许多新功能都包含经过修补的 EJB 架构,其突出特性之一是 JPA。由于具有容器内和容器外持久性选项,JPA 为 J2EE 架构师带来一系列全新设计选择。本文将着重介绍容器内应用程序的设计,此类应用程序依赖 EJB 容器提供企业服务,如事务处理和安全性。

我将使用您熟悉的 PetStore 应用程序进行测试,以证明 JPA 的功能以及它如何向传统 J2EE 设计模式发起挑战。本应用程序比较琐碎,所以不提供详尽的实现细节。我将用代码摘录对设计注意事项进行说明(完整开放源码的连接请参见 下载 )。本文假设您熟悉 EJB 3.0 基本概念和对象关系(OR)映射基本概念。(若需了解这两个主题的更多信息,请参阅 参考资料 )。

设计概述

示例 PetStore 应用程序是基于 Web 的电子交易应用程序,它实现以下用例:

  • 浏览产品
  • 查找产品
  • 维护账户
  • 维护购物车
  • 创建订单

本应用程序被设计为具有三个主要逻辑层的多层 Java EE 应用程序:

  • 表示层(并非本文的重点)使用 Struts 框架。

  • 服务层是一种简单的服务 facade,将所有工作委托给其协作者。服务层的目的是分离服务供应与服务实现。

  • 数据访问层是一系列作为无状态会话 bean 实现的粗粒度 Data Access Objects (DAO)(参见 参考资料)。出于持久性的需要,它们都依赖 Java 持久性实体管理器。

应用程序域模型由 EJB 3.0 实体 bean 表示并用于层间的通信。当域对象离开数据访问层时,它与实体管理器脱离。当重新进入数据访问层时,它需要重新连接到实体管理器。

注释似乎是 Java 5 的一个广泛采用的特性,JPA 也不例外。注释可用于指定 OR 映射 —— 在 dW 文档和教程中您经常可以看到 —— 而 PetStore 应用程序出于相同目的使用它们。然而值得一提的是您还能通过映射文件的方式指定 OR 映射。本文稍后的 OR 映射 一节将探讨并比较这两种可选方式。

我在 Jboss 应用服务器中开发并部署 PetStore 应用程序(参见 参考资料)。我使用商用数据库完成大多数开发工作并将应用程序后端移植到 PostgreSQL 数据库(OR 映射 一节包含了关于使用 JPA 时您应该了解的数据库迁移的潜在影响的讨论)。

本案例分析的目的之一是符合设计标准,允许高度可测试的实现。如 测试 一节所见,您能够使用一系列测试技术来测试 PetStore 应用程序。

PetStore 应用程序充分利用了这一事实:它是规则的 Web 应用程序。主要优点是所有层能够运行在相同的 JVM 中,免除了组件分发的需要。本文的 远程处理 一节简要介绍了为应用程序添加远程处理功能的方法。




服务层

服务层被设计为服务 facade。它由 PetStoreService 这一无状态会话 bean 实现。Bean 要完全依靠其协作者来提供 Web 服务。

因为简化的 PetStore 要求被限定于从数据库检索数据并把数据存储于数据库,惟一的协作者就是 DAO。真正的应用程序能够调用 Web 服务,通过 RMI/IIOP 或资源适配器访问其他应用程序,并生成电子邮件消息等。所有此类型的功能都需要其他协作者支持。

可通过 @EJB@Resource 注释注入协作者(如清单 1 所示)或通过 @PostConstruct 方法注入协作者(如清单 2 所示):


清单 1. 使用 @EJB 注入协作者
@EJB(beanName = "AccountDao")
AccountDao accountDao;


清单 2. 使用 @PostConstruct 注入协作者
MessageSource messageSource;
@PostConstruct
public void init() {
messageSource = new MessageSourceImpl("exceptions");
}

选择 bean 实现类的测试策略的主要因素是类完全依赖协作者来提供服务。这意味着类和协作者的交互作用需要被验证。正如您在 测试 一节看到的,模仿对象方法完全满足该目标。


数据访问层

数据访问层被设计为一系列粗粒度的 DAO。DAO 被实现为无状态会话 bean,一个 bean 对应一个逻辑域:AccountDaoOrderDaoProductDao

每个 bean 都要把实体管理器注入到其中:

@PersistenceContext(unitName = "manager1")
protected EntityManager em;

这是应用程序中 持久性调用类(persistence-aware) 最多的层。它广泛使用全新的 Enterprise JavaBeans Query Language(EJB QL)(参见 参考资料)。所有持久性相关的行动都在该层发生,例如:

profile = (UserProfile) em.createQuery(
"from UserProfile up where up.login = :login").setParameter(
"login", login).getSingleResult();

下面是另一个例子:

em.persist(account);

事实上这些类是持久性调用类(persistence-aware),需要一种容器内测试策略,这将在 测试 一节进行描述。


域模型

您可以把 JPA 看作是众所周知的透明持久性技术(如 JDO 和 Hibernate)的继承者。尽管透明持久性可看作一个附加(add-on)服务,可被应用到忽略持久性的 Plain Old Java Objects (POJO)中,但 JPA 还是对域对象施加了少量限制。

首先,您通常要具有一个映射到对应数据库表主键的(代理)对象标识符:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "AccountSeq")
@Column(name = "account_sysid")
public Integer getAccountId() {
return this.accountId;
}

其次,在联机事务过程(OLTP)环境中,需要一个 version 字段或 JavaBean 属性以进行乐观并发控制:

@Column(name = "row_version")
@Version
public int getVersion() {
return version;
}

最终,如果您选择使用注释方式的映射,映射注释将分布在代码周围。

另一种限制源自于这样一个事实:域对象可以通过由实体管理器所管理的域对象所在的层访问,也可以通过分离它的所有其他层访问。此外,对象可能是新的或已删除的。一般情况下,对象的行为取决于其持久性状态。例如,假设在实体管理器中执行以下这行代码:

int size = attachedProduct.getItems().size();

项集合使用给定产品的项填充,size 变量的值大于 0。如果使用默认懒加载(lazy)配置的一对多关联,并在表示层中执行同一行代码,size 变量的值为 0。 换句话说,在实体管理器之外(在实体管理器内也没关系)无法把 非活动(lazy) 对象与 活动(eager) 对象区分开。解决此问题的方法是强制执行项目规范和约定。

在域对象中,您可以把相同的推理应用到业务方法。无论是否在实体管理器中,它们都应该准备好在任何层中调用。这就是把域对象中的业务方法数量限制为必要的最少数量的理由。

虽然有上述各种情况,域对象依然保留许多 POJO 特性。这意味着您可以使用 plain old Java test (POJT) 对业务方法进行测试,POJT 这一术语引自 Expert One-on-One J2EE Development without EJB 一书(参见 参考资料。)



OR 映射

OR 映射是围绕 JPA 设计的应用程序的重要元素。它直接影响实体管理器填充域对象的方式。因此,变更映射能够在表示层觉察到。变更获取类型或级联类型可能将产生非常不利的影响。

正如前面所述,有两种定义映射的方法:元数据(注释)和映射文件。尽管我们高度提倡采用元数据的方法,但您应该也注意到了它所带来的不便。从本质上 讲,此方法牵涉应用程序的两个逻辑层:域模型和映射信息。因为这两个层是分散的,所以这两个层需要使用不同技术进行单独测试。元数据方法本身不会影响层的 可测试性。更确切的说,元数据方法使这两个层好像只是一个层,由于受一系列因素的影响,这可能会引起问题。

影响映射方法选择的一个因素是项目小组结构。在只有少数开发人员参与的小型项目中,表的数量很少(一般说来少于 100 个表)而且没有专职的映射人员,因为通常使用注释会更加快速,所以把注释方法看作定义映射也许是最佳选择。对于拥有专职映射人员或映射小组的中型和大型项 目来讲,使用映射文件的方法才是更好的选择。该方法可以降低资源争用并使开发过程具有另一种自由度。基于元数据的映射方法证实是 PetStore 应用程序更加节省时间的方法。

OR 映射层的目的是使其余应用程序免受底层数据库变更的影响。当把 PetStore 应用程序后端迁移到 PostgreSQL 时,无需对映射层作出任何变更。这可能要归结于这一事实:原始数据库和 PostgreSQL 这两者均支持序列,因此主键生成策略保持完好。一般情况下,您应该对与对象 ID 处理相关的映射区域进行重写。

对映射的全面测试覆盖极为重要。必须覆盖所有关系映射以确保对获取行为和过渡持久性进行测试。您能够利用容器外使用的 JPA 来执行该任务(将在下一节进行详细介绍)。



测试

服务层设计的关键要素是,关注与底层的协作以提供请求的服务。这需要考虑到使用动态模仿对象的可靠测试策略。我使用 EasyMock 框架(参见 参考资料)来实现测试方法。

DAO 层具有强大的数据库内聚力。这就是可靠测试需要某类容器内策略和数据库访问的原因。尽管这对于远程 EJB 很容易,还是需要考虑适合本地 bean 的有意义的方法。此处令人困扰的因素是:

  • 需要容器内测试 facade
  • 域对象是非连续的,所以全部验证需要发生在容器内。

JBoss Embeddable EJB3 容器(参见 参考资料) (撰写这篇文章时尚处于测试版阶段)被证实是更适当的选择。因为能够从单元测试启动 JBoss Embeddable EJB3 容器,这样所有代码都运行在同一 JVM 中。使用可嵌入容器的容器内测试可实现它的目标,但过程比较缓慢,因为容器启动时间就需要大概 30 秒。这种问题可能是由较早的产品状态造成的,可通过合理的配置改进。

我采用 POJT 对域模型类的业务方法进行测试。不需要其他测试技术,况且其他测试技术不适合这些类。

OR 映射是一个需要穷举测试覆盖的主要层。该层对数据库非常敏感,所以该层不能应用模仿对象或 POJT 技术。但是,您可以利用 JPA 的容器外功能。我就使用这种策略来测试 PetStore 或 OR 映射层。

您需要牢记测试中的测试关联和过渡持久性行为的重要性。这样您才能及早注意到获取类型的变更或级联类型值的变更,并采取适当的措施。


远程处理

PetStore 应用程序设计的关键特性是它的本地特性。使应用程序的所有逻辑层运行在同一 JVM 中,这种方法具有很多优点。有关该主题的详细讨论,请参阅 Expert One-on-One J2EE Development without EJB(参见 参考资料)。

不过,应当说明的是:通过远程处理 facade,您可以轻松向应用程序添加远程处理功能。远程处理 facade(而不是我在前面描述的服务 facade)公开了一个远程界面,它具有两个职责:进行域模型和顺序数据传输对象(DTO)(参见 参考资料)之间的相互转换和在服务 facade 上调用适当的方法。

使用远程无状态会话 bean 能够实现本应用程序。唯一障碍是创建其他 DTO 层和进行它们与域模型之间相互转换。然而您需要它来确保实现整洁的界面以及与远程客户机的松散耦合。


结束语

EJB 3.0 和 JPA 毫无疑问将是 Java EE 5 的主要卖点。在某些领域中,它们给正常的 Java 社区带来竞争优势,并使 Java 在其他领域与竞争对手不分伯仲。(不可否认的是,目前某些领域尚不存在基于标准的方法。)

过去数年来,Spring Framework(参见 参考资料)一直是 EJB 在企业领域的主要竞争对手。EJB 3.0 规范解决了很多促进 Spring 兴起的问题。随着它的出现,EJB 3.0 毫无疑问比 Spring 提供了更好的开发体验 -- 最引人注目的优势是它不需要配置文件。

JPA 提供一种标准的 OR 映射解决方案,该解决方案完全集成到 EJB 3.0 兼容的容器中。JPA 的前辈将会继续稳定发展,但是业务应用程序中的 raw 使用将可能会减少。实现 JPA 兼容的实体管理器似乎很可能是此类技术的发展方向。

在撰写本文时,EJB 3.0 规范还处在建议的最终草案(Proposed Final Draft)阶段(参见 参考资料)。以下是一些未解决的问题以及与 JPA 相关的预实现:

  • • 当前形式的 JPA 规范没有定义只读实体 bean。这让人困惑,因为兼容 EJB 2.1 规范的实体 bean 支持这种特性。Spring 框架也支持只读事务。

  • • 可插入的持久性提供者概念仍处于未交付的阶段。

  • • 标准乐观并发异常 -- OptimisticLockException -- 首次出现在 EJB 3.0 Proposed Final Draft 中。在持久性提供者执行它以前 ,您还需要使用特定于提供者的异常,如 Hibernate 的 StaleObjectStateException,来检测乐观并发问题。暂时,这种情况限制您的实现只能采用特定的持久性提供者。

Java EE 系列规范的较大问题与 JPA 没有任何关系。Java EE 系列规范的问题涉及到 Web 和 EJB 容器之间的集成。Spring 在此领域仍然具有主要竞争优势。JBoss 的 Seam 项目(参见 参考资料)尝试使用自定义的方法来解决这一问题。Caucho Resin 应用服务器(参见 参考资料)试图扩展容器边界并支持在 Web 容器中使用 @EJB 注释。我们希望 Java EE 5.1 将解决层集成的问题,为我们提供一个全面而标准的依赖性注入方法。



下载

描述 名字 大小 下载方法
PetStore j-ejb3jpapetstore.zip 149KB HTTP
关于下载方法的信息


参考资料

学习

获得产品和技术


关于作者

Borys Burnayev 是一名具有超过 12 年从业经验的软件开发人员,过去 5 年中,他从事 Java 及相关技术的工作,最近开始专注于 J2EE 设计问题与对象关系技术。Borys 拥有应用数学的硕士学位。他还拥有 Sun Certified Architect 技术认证和 Sun、Oracle 、IBM 多家公司的其他技术认证。

阅读更多
上一篇EJB 倡导者: 使用何种 EJB 组件组装来自服务的数据
下一篇第20节 三个败家子(20)——强极必辱
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭