Skip to content

1. 简介

1.1. 目标

该文件的PDF版本可<在此处>下载。

文档中的示例可在此处查看 |HERE|。

本文旨在探讨使用 JPA API(Java Persistence API)实现数据持久化的主要概念。阅读本文并测试示例后,读者应已掌握必要的基础知识,从而能够独立开展工作。

JPA API 相对较新,自 JDK 1.5 起才开始提供。JPA 层在多层架构中占据重要地位。让我们以一种相当常见的三层架构为例:

  • 第 [1] 层,此处称为 [ui](用户界面),是通过 Swing 图形界面、控制台界面或 Web 界面与用户交互的层。其作用是将用户提供的数据传递给第 [2] 层,或将第 [2] 层提供的数据呈现给用户。
  • 第 [2] 层,此处称为 [business],是应用所谓业务规则(即应用程序的特定逻辑)的层,而不关心其接收的数据来自何处,也不关心其生成的结果将去向何方。
  • 第[3]层,此处称为[DAO](数据访问对象),是向第[2]层提供预存数据(文件、数据库等)并存储第[2]层部分结果的层。
  • [JDBC]层是Java中用于访问数据库的标准层。这通常被称为DBMS的JDBC驱动程序。

为使开发人员更轻松地编写这些不同层,业界已付出了诸多努力。其中,JPA旨在简化[DAO]层的开发,该层负责管理所谓的持久化数据,因此该API得名Java Persistence API(Java持久化API)。近年来,Hibernate是该领域备受推崇的解决方案之一:

[Hibernate] 层位于开发者编写的 [DAO] 层与 [JDBC] 层之间。Hibernate 是一种 ORM(对象关系映射),它作为一座桥梁,连接了数据库的关系世界与 Java 操作的对象世界。 [DAO] 层的开发人员不再直接接触 [JDBC] 层或其希望利用内容的数据库表。他们仅看到由 [Hibernate] 层提供的数据库对象表示形式。数据库表与 [DAO] 层操作的对象之间的桥梁主要通过两种方式建立:

  • 通过 XML 风格的配置文件
  • 通过代码中的 Java 注解,该技术自 JDK 1.5 起才可用

[Hibernate] 层是一个旨在尽可能透明的抽象层。理想的目标是让 [DAO] 层开发人员完全意识不到自己正在操作数据库。如果他们并非负责编写连接关系型世界与对象世界的配置文件,这一目标是可行的。配置这一桥梁相当精细,需要一定的经验。

与数据库相对应的 [4] 对象层被称为“持久化上下文”。 基于 Hibernate 的 [DAO] 层会对持久化上下文中的对象执行持久化操作(CRUD:创建、读取、更新、删除);这些操作会被 Hibernate 转换为 SQL 语句。对于数据库查询操作(SQL SELECT),Hibernate 为开发者提供了 HQL(Hibernate 查询语言),用于查询持久化上下文 [4] 而非数据库本身。

Hibernate 虽广受欢迎,但掌握起来相当复杂。其学习曲线常被描述为平缓,实际上却相当陡峭。一旦数据库中包含具有一对多或多对多关系的表,配置关系型数据库与对象之间的桥梁便超出了普通初学者的能力范围。配置错误可能会导致应用程序性能不佳。

在商业领域,曾有一款名为 Toplink 的产品,其功能与 Hibernate 相当:

鉴于ORM产品的成功,Java的创建者Sun公司决定通过一项名为JPA的规范来标准化ORM层,该规范随Java 5一同发布。 的Toplink和Hibernate都实现了JPA规范。Toplink原本是一款商业产品,现已开源。借助JPA,原有的架构变为如下所示:

[DAO] 层现在与 JPA 规范(一组接口)进行交互。 开发者从这一标准化中获益良多。此前,若开发者更换ORM层,就必须修改其[DAO]层——因为该层原本是为与特定ORM交互而编写的。如今,他们只需编写一个与JPA层交互的[DAO]层。无论采用何种产品实现JPA层,向[DAO]层展示的接口始终保持一致。

本文将展示不同领域的 JPA 示例:

  • 首先,我们将重点探讨 ORM 层构建的关系型/对象桥梁。该桥梁将利用 Java 5 注解构建,适用于存在以下类型表关系的数据库:
    • 一对一
    • 一对多
    • 多对多

为说明这一部分,我们将构建以下测试架构:

我们的测试程序将是直接调用 JPA 层的控制台应用程序。通过这种方式,我们将探索 JPA 层的主要方法。我们将使用所谓的“Java SE”(标准版)环境进行开发。JPA 既可在 Java SE 环境中运行,也可在 Java EE5(企业版)环境中运行。

  • 一旦我们掌握了关系型/对象桥的配置以及 JPA 层方法的使用,我们将回归到更传统的多层架构:

将通过由[业务]层和[DAO]层组成的两层架构访问[JPA]层。将使用Spring框架[7],随后配合JBoss EJB3容器,将这些层连接在一起。

我们之前提到,JPA 既可在 SE 环境中使用,也可在 EE5 环境中使用。Java EE5 环境为访问持久化数据提供了众多服务,包括连接池、事务管理器等。开发人员利用这些服务可能会带来好处。Java EE5 环境目前尚未被广泛采用(2007 年 5 月)。它目前可在 Sun Application Server 9.x(Glassfish)上使用。 应用服务器本质上是一种 Web 应用服务器。如果您使用 Swing 构建独立的图形化应用程序,则无法利用 EE 环境及其提供的服务。这确实是一个问题。我们开始看到“独立”的 EE 环境,即那些可以在应用服务器之外使用的环境。JBos EJB3 就是这样的环境,本文档中我们将使用它。

在 EE5 环境中,各层由称为 EJB(企业级 Java Bean)的对象实现。在 EE 的早期版本中,EJB(EJB 2.x)被认为难以实现和测试,且有时性能欠佳。 EJB 2.x 区分“实体”Bean 和“会话”Bean。简而言之,EJB 2.x “实体”对应于数据库表中的一行,而 EJB 2.x “会话”则是用于实现多层架构中 [业务] 和 [DAO] 层的对象。 针对使用 EJB 实现的分层架构,主要批评之一在于它们只能在 EJB 容器内使用,而 EJB 容器是 EE 环境提供的一项服务。这使得单元测试变得困难。因此,在上图中,对使用 EJB 构建的 [业务] 和 [DAO] 层进行单元测试,需要搭建一个应用服务器,这是一种相当繁琐的操作,实际上并不鼓励开发人员频繁进行测试。

Spring框架的诞生正是为了应对EJB2的复杂性。Spring在SE环境中提供了大量通常由EE环境提供的服务。因此,在本文关注的“数据持久化”部分,Spring提供了应用程序所需的连接池和事务管理器。Spring的出现促进了单元测试文化的形成,使得单元测试的实施突然变得容易得多。 Spring 允许使用标准 Java 对象(POJO,即普通 Java 对象)来实现应用层,从而使其能够在其他场景中被复用。最后,它能够相当透明地集成众多第三方工具,尤其是像 Hibernate、iBatis 等持久化工具……

Java EE 5 的设计旨在弥补先前 EE 规范的不足。EJB 2.x 已演进为 EJB 3。这些是带有注解的 POJO,当它们位于 EJB 3 容器内时,这些注解会将其标记为特殊对象。在容器内,EJB 3 可以利用容器提供的服务(连接池、事务管理器等)。 在 EJB 3 容器之外,EJB 3 便成为一个标准的 Java 对象。其 EJB 注解将被忽略。

上文中,我们已将 Spring 和 JBoss EJB3 描绘为多层架构的一种可能的基础架构(框架)。正是这一基础架构将提供我们所需的服务:连接池和事务管理器。

  • 在 Spring 中,各层将通过 POJO 实现。这些 POJO 将通过依赖注入访问 Spring 的服务(连接池、事务管理器):在构建这些 POJO 时,Spring 会注入它们所需服务的引用。
  • JBoss EJB3 是一个能够在应用服务器外部运行的 EJB 容器。其工作原理(从开发者的角度来看)与 Spring 的描述类似。我们发现两者之间差异甚微。

  • 本文将以一个三层 Web 应用程序的示例作为结尾——该示例虽然简单,却极具代表性:

1.2. 参考文献

[ref1]:《Java Persistence with Hibernate》,作者:Christian Bauer 和 Gavin King,Manning 出版社出版

[ref1] 是本文的参考依据。这是一本超过 800 页的综合性著作,详细阐述了在两种不同场景下使用 Hibernate ORM 的方法:即使用 JPA 与不使用 JPA 的情况。对于使用 JDK 1.4 或更早版本的开发者而言,不使用 JPA 的 Hibernate 用法确实仍然具有参考价值,因为 JPA 直到 JDK 1.5 才出现。

读完这本书的四分之三以上内容并略读了其余部分后,我深感书中的一切都极具价值。对于经验丰富的 Hibernate 用户而言,这 800 页内容中提供的几乎所有信息都应已耳熟能详。Christian Bauer 和 Gavin King 的阐述虽详尽周全,却极少陷入对那些读者永远不会遇到的情况的赘述。 全书都值得一读。本书采用教学风格撰写:作者真正致力于将所有知识点都阐释得清清楚楚。由于本书既涵盖了配合JPA使用Hibernate的情形,也涵盖了不使用JPA的情况,这对于仅对其中一种技术感兴趣的读者来说,不免构成了一定挑战。 例如,作者通过大量实例,分别阐述了这两种场景下的关系型/对象桥梁。由于JPA深受Hibernate启发,所涉及的概念非常相似。但两者仍存在差异。这种差异甚至导致某些在Hibernate中成立的结论在JPA中不再适用,从而给读者造成困惑。

作者在EJB3容器的背景下提供了三层应用的示例。他们并未涉及Spring。不过,我们将在一个示例中看到,Spring的使用更为简便,且其适用范围比[ref1]中使用的JBoss EJB3容器更为广泛。尽管如此,《Java Persistence with Hibernate》仍是一本优秀的著作,我推荐大家阅读,因为它系统地讲解了ORM的基础知识。

对于初学者而言,使用 ORM 是一项复杂的任务。

  • 要配置关系型/对象桥梁,需要理解一些概念。
  • 其中涉及持久化上下文的概念,以及对象处于“已持久化”、“已脱离”或“新建”状态的区别
  • 还有围绕持久化的机制(事务、连接池),这些通常是由容器提供的服务
  • 还有与性能相关的配置(二级缓存)
  • ……

我们将通过实例来介绍这些概念。我们不会深入探讨其背后的理论。我们的目标很简单:在每个案例中,让读者能够理解示例并将其内化,达到能够自行修改或将其应用于不同场景的程度。

1.3. 使用的工具

本文中的示例使用了以下工具。其中部分工具在附录中进行了说明(下载、安装、配置、使用方法)。对于此类情况,我们将提供相应的段落编号和页码。

1.4. 下载示例 e

在本文档的网站上,所涵盖的示例可以 ZIP 文件的形式下载,解压后将生成以下文件夹:

  • 在 [1] 中:示例的目录结构
  • 在 [2] 中:<annexes> 文件夹包含附录部分第 5 段中列出的内容。特别是,<jdbc> 文件夹包含教程示例中使用的数据库管理系统(DBMS)的 JDBC 驱动程序。
  • 在 [3] 中:<lib> 文件夹将教程中使用的各种 .jar 存档文件分组到 5 个文件夹中
  • [4]:<lib/drivers> 文件夹包含以下驱动程序: - 用于数据库管理系统(DBMS)的JDBC驱动程序 - 单元测试工具 [TestNG] - 日志工具 [log4j]
  • 在 [5] 中:JPA/Hibernate 实现的存档以及 Hibernate 所需的第三方工具
  • 在 [6] 中:JPA/TopLink 实现的归档文件
  • 在 [7] 中:Spring 2.x 的归档文件以及 Spring 所需的第三方工具
  • 在 [8] 中:JBoss EJB3 容器的归档文件
  • 在 [9] 中:<hibernate> 文件夹包含使用 JPA/Hibernate 持久化层的示例
  • 在 [10] 中:<hibernate/direct> 文件夹包含直接在 [Main] 类型程序中使用 JPA 层的示例。
  • 在 [11] 和 [12] 中:示例展示了在多层架构中通过 [business] 和 [DAO] 层使用 JPA 层的情况,这是标准用例。[business] 和 [DAO] 层所使用的服务(连接池、事务管理器)由 Spring [11] 或 JBoss EJB3 [12] 提供。
  • 在[13]中:<toplink>文件夹包含了来自<hibernate>文件夹[9]的示例,但这次使用的是JPA/Toplink持久层,而非JPA/Hibernate。由于无法构建出一个由Toplink提供持久层、由JBoss EJB3容器提供服务的可运行示例,因此[13]中没有<jbossejb3>文件夹。
  • 在 [14] 中:<web> 文件夹包含三个采用 JPA 持久化层的 Web 应用程序示例:
  • [15]:一个使用 Spring / JPA / Hibernate 的示例
  • [16]:使用 Spring / JPA / Toplink 的相同示例
  • [17]:使用 JBoss EJB3 / JPA / Hibernate 的相同示例。该示例无法运行,很可能是由于配置问题尚未解决。尽管如此,仍将其收录其中,以便读者查阅并可能找到解决此问题的办法。

本教程经常提及该目录结构,特别是在测试相关示例时。建议读者下载并安装这些示例。下文中,我们将上述目录结构简称为 <examples>。

1.5. -示例的Eclipse项目配置

示例使用“用户”库。这些是以单一名称归类的 .jar 归档文件。当此类库被包含在 Java 项目的类路径中时,其中包含的所有归档文件也会被包含在该类路径中。让我们看看如何在 Eclipse 中实现这一点:

  • 在 [1] 中:[窗口 / 首选项 / Java / 构建路径 / 用户库]
  • 在 [2] 中:创建一个新库
  • 在 [3]:为其命名并确认
  • 在 [4] 中:选择将作为 [jpa-divers] 库组成部分的 JAR 文件
  • 在 [5] 中:选择 <examples>/lib/divers 文件夹中的所有 JAR 文件
  • 在 [6] 中:已定义用户库 [jpa-divers]
  • 在 [7] 中:重复相同步骤再创建 4 个库:
库 JAR 文件夹
jpa-hibernate
<examples>/lib/hibernate
jpa-toplink
<示例>/lib/toplink
jpa-spring
<示例>/lib/spring
jpa-jbossejb3
<examples>/lib/jbossejb3