Oracle TopLink 是最好的 Java 对象-关系持久性框架。它提供了一个在关系数据库表中存储 Java 对象和 Enterprise Java Bean (EJB) 的高度灵活和高效的机制。TopLink 为开发人员提供了极佳的性能和更多的选择机会,使他们可以使用任何数据库、任何应用服务器、任何开发工具集和进程以及任何 J2EE 体系结构。
本教程将讲述如何结合使用 JDeveloper 与 TopLink 将 Java 对象持久存储到关系数据库中。 您将通过一些说明下列概念的示例来了解这些概念:
| 基本映射(直对字段、一对一、一对多) | |
| ReadAll 和 ReadObject 查询 | |
| 有名查询 | |
| 按需加载 | |
| 自动更改跟踪和数据库更新 | |
| 使用联接读取(用于性能调整) |
大约 1 小时
本教程包括下列主题:
| 概述 | ||
| 情景 | ||
| 前提条件 | ||
| 执行基本查询 | ||
| 执行按需加载 | ||
| 更新对象属性 | ||
| 总结 | ||
| 相关信息 | ||
将光标放在该图标上加载并查看本教程的所有屏幕截图。(警告:因为此操作会同时加载所有屏幕截图,所以根据 Internet 连接的不同,响应时间可能会比较慢。)
注意:此外,还可以在下列步骤中将光标放在每个单独的图标上,从而仅加载和查看与该步骤相关的屏幕截图。
完成本教程之后,您将能够把 Java 类中的数据存储到关系数据库中。您还将能够从 Java 类检索和更新现有数据库信息。
本教程中使用的模型由三个类组成:Employee、Address 和 Phone。Employee 具有数个属性,其中两个属性引用该模型中的其他两个对象:address 和 phone。Phone 引用它所属的 employee;Address 不引用模型中的其他对象,只具有一些简单的属性。
本教程介绍了最常用的对象关系映射情形,即一个现有对象模型映射到一个现有数据模型。此情形称为“中间匹配”。下图显示了此系统的对象模型:

下图显示了关系数据库表:

除了能够将 Java 对象映射到数据库表外,TopLink 还提供了一个 API 来执行常见查询。 本教程提供了几个使用此 API 的示例。
人力资源部门用一个关系数据库跟踪员工信息。您被要求实施一个 Java 系统来从该数据库查询和更新数据,而您决定使用 TopLink 来简化该任务。
开始本教程之前,您应该:
| 1. |
能够访问 Oracle 数据库;您可以从 OTN 下载 Oracle 数据库 10g。
|
||||||||||||||||
| 2. |
能够访问或已经安装了 Oracle JDeveloper10g J2EE Edition 10.1.3 版;该工作区在以前版本中无法打开。您可以从 OTN 下载 Oracle JDeveloper10g J2EE Edition 10.1.3 版。
|
||||||||||||||||
| 3. | 以 SYSTEM 用户或有权创建其他用户的用户身份打开 SQL*Plus 并连接该数据库,然后输入下列命令: CREATE USER tlpersist IDENTIFIED BY tlpersist; 退出 SQL*Plus。
|
||||||||||||||||
| 4. | 将 tlpersistence.zip 下载到您的硬盘,并将其解压缩到
|
||||||||||||||||
| 5. | 在 JDeveloper 中,创建一个到前面所创建的 TLPERSIST 架构的连接。将此连接命名为 tlpersist。 要创建此连接,请执行下列操作:
|
||||||||||||||||
| 6. | 在 JDeveloper 中,从菜单中选择 File | Open,导航至 在 Applications Navigator 中,展开 TLPersistence、Employees 和 Resources。右键单击 initialize.sql,然后从上下文菜单中选择 Run in SQL*Plus | tlpersist,运行在 TLPERSIST 架构中创建表的脚本。 注意:如果这是您第一次在 JDeveloper 中运行 SQL*Plus,则会要求您指定 SQL*Plus 可执行文件的位置。 |
TLPersistence 工作区包含了三个 Java 类,分别表示员工、电话和地址;它还包含几个使用 TopLink API 查询数据库表以获得这些类所表示数据的示例。但是,您必须先创建 Java 对象与数据库表之间的映射才能使用 TopLink API。在本主题中,您将通过执行下列步骤,使用 TopLink 自动创建必要的映射:
| 详细了解和运行基本查询示例 | ||
| 导入脱机数据库表 | ||
| 将 Java 对象映射到表 | ||
第一个 TopLink API 示例查询所有员工记录。要详细了解和运行此示例,请执行下列步骤:
| 1. |
在 Applications Navigator 中,展开 TLPersistence、Employees、Application Sources 和 example。双击 ReadAllEmployees.java 在代码编辑器中将其打开。请注意编辑器中从该类读取所有 Employee 对象的关键代码行: List employees = session.readAllObjects(Employee.class); 此行使用 TopLink API session 方法 readAllObjects(),该方法使查询存储在数据库中的某个类的所有实例变得非常简单。
|
| 2. |
在运行该示例之前,请将该示例中的数据库连接信息更改为您自己的数据库信息。 在 Applications Navigator 中,展开节点 TLPersistence、Employees、Application Sources 和 TopLink。双击 TopLink Mappings,打开 TopLink Mappings 编辑器,并选择 Database Info 选项卡。从 Deployment Connection 下拉列表中选择 tlpersist 连接。则 Information for 'tlpersist' 应该更改为您自己的数据库信息了。
|
| 3. | 在 Applications Navigator 中右键单击 TopLink Mappings,然后从上下文菜单中选择 Generate toplink-deployment-descriptor.xml。当被问及是否覆盖现有文件时,单击 Yes。生成完成时,单击 OK。
|
| 4. | 运行该示例的另一个必要要素已经包括在工作区中了:sessions.xml 文件。此文件用于配置各种属性,如记录级别和事务控制。无需编辑这个提供的 sessions.xml 文件。 右键单击 ReadAllEmployees.java,然后从上下文菜单中选择 Run。 请注意,日志窗口显示了错误。 这是因为您尚未将 Java 对象映射到其相应的数据库表和属性。 |
开始映射前,先将数据库表定义导入您的项目中。要导入数据库表,请执行下列步骤:
| 1. |
在 Applications Navigator 中,右键单击 Employees,然后从上下文菜单中选择 New。
|
| 2. |
在 New Gallery 中,展开 Categories 列表中的 Database Tier。选择类别 Offline Database Objects,然后从 Items 列表中选择 Offline Database Objects Imported from Database。单击 OK。
|
| 3. |
如果显示 Import Offline Database Objects 向导的 Welcome 页,则单击 Next。 在该向导的 Select Source Connection 页中,选择 tlpersist 连接,然后单击 Next。
|
| 4. |
在该向导的 Select Objects 页中,从 Available 列表选择 Employee、Address 和 Phone 对象,然后单击 Add 注意:如果 Available 列表中没有显示任何对象,则单击 Query 或选择 Auto-Query。 单击 Next。
|
| 5. |
在该向导的 Select Target Schema 页上,确保选中 TLPERSIST 架构,然后单击 Next。
|
| 6. |
在该向导剩下的一页中单击 Finish。脱机表出现在 Applications Navigator 的 Database Objects 节点下面。 单击 Save All |
您已经导入了脱机表,现在需要为每个想映射到数据库的类创建一个描述符。TopLink 描述符会捕获类的映射元数据以及您将在后面示例中详细了解的该类的其他相关信息。要创建 TopLink 描述符并将 Java 类映射到表,请执行下列步骤:
| 1. |
在 Applications Navigator 中,展开节点 TLPersistence、Employees、Application Sources 和 TopLink。选择 TopLink Mappings,然后在 Structure 窗口中右键单击 TopLink Mappings,再从上下文菜单中选择 Add or Remove Descriptors。
|
| 2. |
在 TopLink Descriptors 窗口中,选择 Available Package/Classes 列表中的 model 程序包,然后单击 单击 OK。 此时 TopLink Mapping Editor 在 JDeveloper 编辑器中打开。
|
| 3. |
如果需要,您还可以使用 TopLink 将每个属性分别映射到一个数据库列或引用。而且,您可以使用 TopLink 的自动映射特性快速执行这种映射。 右键单击 Structure 窗格中的 model 程序包,然后从上下文菜单中选择 Automap。
|
| 4. |
如果显示 Automap 向导的 Welcome 页,则单击 Next。 请注意,在该向导的 Associate with Database Table 页中,TopLink 已经使用一些假设自动将每个 Java 类映射到了其相关的表;如果这些映射不正确,则您可以更改它们。在这里,TopLink 已经正确识别了表映射,因此请单击 Next。
|
| 5. |
除了将 Java 类映射到特定的数据库表外,还需要将 Java 类属性映射到数据库表的特定列或引用。 在该向导的 Determine the Mapping Type 页中,您可以再次查看 Java 属性到数据库表列的映射。 对于大多数属性,TopLink 都执行“直对字段”映射,即一个 Java 类属性正好对应其相关数据库表中的一个列。这是大多数属性的正确映射类型。 例如,展开 Address 类下面的 city 节点。 该 city 属性的映射即为直对字段映射。也就是说,当从 ADDRESS 数据库表读取 Address 对象时,TopLink 会将 CITY 列值放到 Address 的 city 属性中,该属性是一个 Java 字符串。向数据库写入 Address 对象的过程正好相反,即将 city 属性值放到 CITY 列中。TopLink 会替您完成上述操作,因此您无需编写任何代码。
|
| 6. | 展开 Employee 类下面的 phones 节点。 您可以看到,TopLink 自动使用“一对多”映射映射了此属性,这是因为一个员工可能拥有多个电话号码。Toplink 自动生成到 Phone 表中相应外键引用的映射。
|
| 7. | 展开 Employee 类下面的 address 节点。该 address 属性映射到另一个表,而非 Employee 表中的某个特定列,并且由于每个员工只有一个地址,因此将“直对字段”映射更改为“一对一”映射。TopLink 再次自动执行了到 Address 表中相应引用的映射。
|
| 8. | 以相似的方式,将 Phone 类 employee 属性的映射更改为“一对一”映射,然后单击 Next。 在该向导接下来的两页中,单击 Next,然后单击 Finish。单击 Save All |
现在您已经将 Java 对象映射到了表,接下来要重新生成部署描述符,并重新运行查询。 为了在运行时使用,将所有映射元数据都编译到一个部署描述符 xml 文件中。 要创建 toplink-deployment-descriptor.xml 文件并重新运行该查询,请执行下列步骤:
| 1. |
重新生成 TopLink 部署描述符。在 Applications Navigator 中右键单击 TopLink Mappings,然后从上下文菜单中选择 Generate toplink-deployment-descriptor.xml。当被问及是否覆盖现有文件时,单击 Yes。生成完成时,单击 OK。 单击 Save All
|
| 2. |
右键单击 ReadAllEmployees.java,然后从上下文菜单中选择 Run。 日志窗口显示,查询现在正确检索了员工数据: TopLink 连接了数据库,生成了所需的 SQL,并且将结果转换为了对象 -- 所有这些均无需您进行任何编码。 |
此示例演示了如何使用 JDeveloper 构建 TopLink 查询,然后如何执行该查询。要构建一个查询来读取具有某个特定姓氏的所有员工,请执行下列步骤:
| 定义有名查询 | ||
| 重新运行有名查询示例 | ||
| 1. |
在 Applications Navigator 中,双击 ReadEmployeesWithLastName.java 在代码编辑器中将其打开。详细了解 run() 方法。此方法的第一行执行该查询:
此代码旨在读取那些姓氏为 Smith 的员工。尚未定义有名查询 findAllByLastName。
|
| 2. | 右键单击 ReadEmployeesWithLastName.java,然后从上下文菜单中选择 Run。
|
| 3. | 日志窗口显示下列消息:Query named [findAllByLastName] is not defined. |
| 1. |
在 Applications Navigator 中,选择 TopLink Mappings。在 Structure 窗口中,展开 model,然后双击 Employee,在编辑器窗口中显示 Toplink Mappings for Employee。
|
| 2. |
在 TopLink Mappings 编辑器中,单击 Queries 选项卡,然后单击 Named Queries 选项卡。单击 Add。将这个新查询命名为 findAllByLastName。单击 OK。
|
| 3. | 在出现的警告窗口中单击 OK
|
| 4. | 在 Named Queries 选项卡的右侧为三个选项卡。在 General 选项卡中,定义查询的类型和参数。 打开 Type 的下拉列表。您可以定义两种类型的查询:读取多个对象的 ReadAllQuery;只读取一个对象的 ReadObjectQuery。您要定义 ReadAllQuery,因此保留 Type 的默认设置。 在查询定义的 Parameters 部分,单击 Add。
|
| 5. | 在 Class Browser 窗口中,展开 java,然后展开 lang。向下滚动,选择 String,然后单击 OK。
|
| 6. | 在参数 Name 域中单击,然后将 arg 替换为 lastName。
|
| 7. | 单击 Format 选项卡。请注意,TopLink 支持三种指定查询的方法:Expression、SQL 和 EJBQL。在一个项目中,您可以使用一种、两种甚至所有这三种类型的查询。 确保选中了 Expression,然后单击 Edit。
|
| 8. | 单击 Add。您将看到开始构建 EQUAL 条件。
|
| 9. | 在该窗口的 First Argument 部分,单击 Edit。
|
| 10. | 在 Choose 窗口,选择 lName,然后单击 OK。
|
| 11. | 在该窗口的 Operator 部分,打开下拉列表。 您可以看到该查询可以使用的几个运算符。而您要使用的是 EQUAL 运算符,因此保留默认选择 EQUAL。
|
| 12. | 在该窗口的 Second Argument 部分,选择 Parameter,该下拉列表中填充了已经添加到该查询的参数。打开该下拉列表您可以看到 lastName 是唯一可用的参数,这是因为您在上面的步骤 6 中添加了这个参数。单击 OK。 |
| 1. |
在 Applications Navigator 中右键单击 TopLink Mappings,然后从上下文菜单中选择 Generate toplink-deployment-descriptor.xml。当被问及是否覆盖现有文件时,单击 Yes。生成完成时,单击 OK。
|
| 2. |
再次仔细了解 ReadEmployeesWithLastName.java 中的代码。对该有名查询的调用位于 run() 方法中:
第一个参数为刚刚定义的有名查询的名称。第二个参数为 该查询所属的类,最后一个参数是要传递给该有名查询的 String 参数。 右键单击 ReadEmployeesWithLastName.java,然后从上下文菜单中选择 Run。
|
| 3. | 日志窗口显示姓氏为 Smith 的员工。 |
在该对象模型中您可以看到,一个 Employee 可以有多个 Phone。但是当您读取某个 Employee 时,是否要读取所有相关的 Phone?对该问题的回答取决于应用程序。TopLink 非常灵活,它既能在读取员工时读取该员工的所有电话,也能仅在需要时读取这些电话。按需加载相关对象的 TopLink 机制称为间接 (indirection)。
使用间接对象可以改善 TopLink 对象关系的性能。间接对象取代了应用程序对象,从而只在需要该应用程序对象时才从数据库中读取它。

如果没有间接,则当 TopLink 检索一个持久对象时,它还会检索该对象引用的所有对象。 这可能会导致某些应用程序的性能降低。使用间接使 TopLink 能够为相关对象创建“替身”,从而显著改善性能,尤其当应用程序只对所检索对象的内容感兴趣,而不对它的相关对象感兴趣时更是如此。
您可以在 phone 属性的 Employee 描述符映射中配置间接。要构建一个在读取员工电话号码时使用按需加载的查询,请执行下列步骤:
| 详细了解按需加载示例 | ||
| 定义使用间接的有名查询 | ||
| 运行按需加载示例 | ||
| 1. |
双击 ReadEmployeeAndPhones.java 在代码编辑器中将其打开。
|
| 2. |
详细了解 run() 方法,该方法会读取 ID 为 1 的员工,将其输出,然后输出该员工的众多电话。此代码使用有名查询 findById。 您必须先为 Employee 定义有名查询 findById,然后才能执行此程序。 |
| 1. |
在 Applications Navigator 中,选择 TopLink Mappings。在 Structure 窗口中,展开 model,然后双击 Employee,在编辑器窗口中显示 Toplink Mappings for Employee。
|
| 2. |
在 TopLink Mappings 编辑器中,单击 Queries 选项卡,单击 Named Queries 选项卡,然后单击 Add。将这个新查询命名为 findById。单击 OK,然后在出现的警告窗口中再次单击 OK。
|
| 3. | 选中 findById 查询,并通过在下拉列表中选择 ReadObjectQuery,将该窗口右侧的查询类型更改为该类型。
|
| 4. | 在 Parameters 部分,单击 Add,然后添加一个 java.lang.Long 参数。
|
| 5. | 将该参数的名称更改为 id。
|
| 6. | 单击 Format 选项卡。确保选择了 Expression,然后单击 Edit。
|
| 7. | 在 Expression Builder 中,单击 Add。选择 id 参数作为 Second Argument,然后单击 OK。
|
| 1. |
在 Applications Navigator 中右键单击 TopLink Mappings,然后从上下文菜单中选择 Generate toplink-deployment-descriptor.xml。当被问及是否覆盖现有文件时,单击 Yes。生成完成时,单击 OK。
|
| 2. |
运行 ReadEmployeeAndPhones 示例。请注意,已经生成了一行 SQL,该行 SQL 用来读取该员工,另一行 SQL 则用来在以后发出以读取该员工的电话。 提示:如果右键单击日志窗口,然后从上下文菜单中选择 Wrap,则可以看到该窗口中的所有文本。
|
| 3. | 因为程序的需要,所以这些电话是只读的。注释掉以下代码:
然后重新运行该程序。请注意,没有生成任何 SQL 来读取电话。 |
| 1. |
在 Applications Navigator 中,选择 TopLink Mappings。在 Structure 窗口中,展开 model 和 Employee。双击 phones 属性,使 Toplink Mappings for Employee phones 显示在编辑器窗口中。取消选中 Use Indirection。
|
| 2. |
重新生成部署描述符,让输出电话的一行代码仍然保持注释状态,然后再次运行 ReadEmployeeAndPhones 示例。 您可以看到,生成了检索这些电话的 SQL 语句,尽管程序不使用该信息。如果不使用间接,则当 TopLink 读取某个 Employee 时,它还会读取所有 Phone,这是因为 Employee 引用 Phone。
|
| 3. | 重新打开间接,重新生成部署描述符,然后再继续下面的过程。否则每当在后面的示例中读取 Employee 时,您还会继续看到获取电话的 SQL。在映射编辑器中,确保间接的类型设置为 Transparent,而非 Value Holder。 |
对象-关系持久性框架(如 TopLink)的特性之一就是使开发人员集中精力处理对象和属性而非表和列。这在更新对象时尤为明显。TopLink 自动判断某个事务 (TopLink UnitOfWork) 中的对象更改,并生成更新数据库所需的最少的 SQL。
要了解 TopLink 如何简化了数据库事务,请执行下列步骤:
| 1. |
在代码编辑器中打开 UpdateEmployeeName.java。 在此示例中,要请求一个 UnitOfWork,读取一个 Employee,对它的名字进行翻转,然后提交该 UnitOfWork。您还记得吧,员工的名字属性是作为直对字段映射到 F_NAME 数据库列的。
|
| 2. |
运行 UpdateEmployeeName 示例,检查控制台。 您应该会看到一个 SELECT 语句和一个 UPDATE 语句,前者读取员工,后者则只更新名字。 Java 开发人员不必操心如何确定更改的内容以及需要构建哪些 SQL 来将这些更改传回数据库 -- TopLink 将自动完成所有这些操作。与直接使用 JDBC 相比,编程工作得到了大大减少。
|
| 3. | 注释掉以下代码:
然后重新运行该示例。这次您应该只看到一个读取员工的 SELECT 语句,而不会看到 UPDATE。这是因为 Employee 没有更改。TopLink 判断没有发生任何更改,因此就结束了该事务。
|
TopLink 为开发人员提供了几个用来控制查询数据方式的选项。可以覆盖 TopLink 默认值以便进行性能调整。使用按需加载,您可以对读取员工时地址的读取进行控制 — 在读取员工时读取该员工的地址,或者仅在需要地址时才读取该地址。不同的应用程序具有不同的数据访问方式;您可以针对每个这样的访问方式调整 TopLink 以改善应用程序性能。
在上面获取电话列表的示例中,您使用了间接。TopLink 可以使用 java.util.List 接口的实现来透明地执行间接。使用一对一引用时,TopLink 并非总是能够透明地引入间接。在本主题的示例中,您通过将 ValueHolder 用于 address 属性实现间接。ValueHolder 存储的是检索数据库中真正对象所必需的信息。
下图显示了从数据库读取的 Employee 对象。仅在访问 Address 对象时才读取和创建该对象。

第一次访问地址时,ValueHolder 会读取并返回该 Address 对象,如下图所示:

后续的地址请求不会访问该数据库,如下图所示:

要解释如何调整 TopLink 读取对象的方式(即使在使用间接时),请执行下列步骤:
| 1. |
在 Applications Navigator 中,选择 TopLink Mappings。在 Structure 窗口中,展开 model 和 Employee。双击 address 属性,使 Toplink Mappings for Employee address 显示在编辑器窗口中。 请注意,Use ValueHolder Indirection 默认情况下是选中的。
|
| 2. |
在代码编辑器中打开 ReadAllEmployeesAndAddress.java。详细了解 run() 方法可看到,该代码读取所有 Employee 对象。一行被注释掉;在下一步之前保持该行被注释掉。 运行 ReadAllEmployeesAndAddress.java 示例,然后详细了解该 SQL。您会看到一行读取所有员工的 SQL,然后是一系列查询,其中一个是查询每个 Employee 的 Address。帮助类方法 printEmployeesAndAddresses() 会对已读取员工的集合进行迭代,并输出每个员工及其地址。当访问某个员工的地址时,会按需加载该地址,从而生成系列 SQL 查询。
|
| 3. | 在 ReadAllEmployeesAndAddress.java 中,取消注释以下行:
然后重新运行该示例。现在您只会看到一行 SQl,它选择所有员工及其地址。您可能还会注意到该查询的运行速度比以前快多了。 当您将 address 作为查询的联接属性进行添加时,则告知 TopLink 在同一个 SQL 语句中同时读取员工及其相关地址,从而覆盖了间接的映射指定。覆盖查询中映射的功能使开发人员能够调整数据库访问,从而使用最少的 SQL 检索所需的对象。这不会更改在地址映射中指定的默认行为。 |
在本教程中,您结合使用了 JDeveloper 与 TopLink 来将 Java 对象持久存储到一个关系数据库中。您将 Java 对象映射到了关系表和属性,并使用了 TopLink API 来查询和更新关系表。
在本课程中,您学会了如何执行下列任务:
|
使用 TopLink 定义基本映射 |
|
|
执行 ReadAll 和 ReadObject 查询 |
|
|
构建和执行有名查询 |
|
| 执行按需加载 | |
| 自动跟踪更改和执行更新 | |
| 使用联接读取调整查询性能 |
|
“开发 J2EE 应用程序 > 业务层开发 > 开发 Toplink 映射”下的在线帮助主题 |
|
|
OTN 上的 TopLink 产品页 |
|
| 有关本 OBE 教程的问题,请在 OBE 论坛上提问。 |