JDBC 数据中介服务和服务数据对象的最佳性能实践,第 1 部分: 使用 JDBC DMS 和 SDO 创建应用程序
2009-10-21 00:00:00 来源:WEB开发网SDO 和 JDBC 数据中介服务简介
J2EE 编程模型的引入标志着开发和交付应用程序的方式发生了重大的改变。尽管 J2EE 编程模型在不断地发展,但是该模型的一个主要的不足之处在于,客户需要理解不同的业务数据对象及其相关联的 API 的表示,例如:
值对象
Enterprise JavaBeans
JDBC 结果集
JAX/RPC Web 服务绑定类
在努力简化 J2EE 数据编程模型的过程中,IBM 和 BEA 共同提出了服务数据对象 (SDO) 概念。SDO 提供一个框架,通过这个框架,开发人员可以统一访问和操作来自各种数据源的数据,而无需考虑通常与数据源相关联的特定于技术的 API。
这篇由两部分组成的文章研究了 SDO 最适合的使用模式和方案,并且提供了一个循序渐进示例,演示了如何使用 SDO 以及 WebSphere Application Server V6 附带的 Java 数据库连接 (JDBC) 数据中介服务 (DMS)。使用 JDBC DMS,您可以从后端 JDBC 数据源检索和操作数据,而不必编写将数据从 SQL 结果集转换为可用的业务对象所需的代码。然后,本文推荐了一些其他方面的提示和技术,以便更有效地使用 SDO 和 JDBC DMS。
我们使用一个实验室计算机系统的签出和保留应用模型的示例,首先编写从数据库访问应用程序数据所需的最简单的代码。然后我们通过一系列更改的迭代来改进这一实现。对于每个步骤,我们都仔细研究更改的动机及利弊,并且提供一些性能结果,以便量化这种改进。具体来说,我们研究了以下潜在的优化:
被动连接包装程序
静态定义的 JDBC DMS 元数据
根据更好的元数据定义减少返回数据(使用外部表)
用户提供的 SQL
静态定义的数据模型
本文的第 2 部分的最后总结了推荐的最佳实践、性能方面的改进以及将这些最佳实践应用于 SDO 应用程序的权衡。
SDO 何时有意义?
与为大家所接受的 pessimistic entity Bean 技术——将 一个 J2EE 事务当前正在使用的数据的锁保存在数据库中(称为 connected)——不同,SDO 支持disconnected 和optimistic 编程模型。optimistic 编程模型最适合只读数据或以后要在并发数少的情况下检索、处理和推回到数据源的数据。遵循这种数据访问模型的应用程序非常适合 SDO。大多数基于 Web 的应用程序都使用这种类型断开连接的数据模型,但是这意味着每个用于 Web 的应用程序现在都应该使用 SDO 吗?
没有必要。一些 Web 应用程序仍然需要与连接像 EJB 可以提供的编程模型的数据紧密耦合。即使 JDBC DMS 提供了数据更新和用于解决更新冲突的 optimistic 并发控制 (OCC) 机制,SDO 也不可能适合所有的 Web 应用程序。SDO 提供了通过 ChangeSummary 跟踪对数据对象的更改的能力,而 JDBC 数据中介服务允许指定 OCC 列来管理更新中的数据冲突。对于处理更新数量相对少且并发级别相对低的情况,这足够了。但是,SDO 的断开连接(disconnected)的性质并不很适合需要进行大量更新且并发级别较高的应用程序。
业务数据模型(Hardware Reservation 应用程序)
在本文中,我们选择建模一个简单的基于 Web 的 Hardware Reservation 系统,该系统可以用于跟踪谁在共享环境中保留了特定的计算机系统。该模型相当简单,可以轻松地进行演示,并且还非常灵活,可以突出显示用于 SDO 和 JDBC DMS 的许多使用方案和有效的编码技术。此外,该应用程序本质上主要是用于读取的,因而可以很容易地将其转换为 SDO 实现。
用于 Hardware Reservation 系统的数据模型由三个主要的对象组成:用户 (UserAccount)、硬件系统 (HWSystem) 和签出记录 (CheckOutRecord)。此数据模型的 UML 关系图如下所示:
图 1. Hardware Reservation 数据模型
从该数据模型中可以看出,UserAccount 对象负责维护 Hardware Reservation 系统中用户的帐户信息。HWSystem 对象代表用户可以根据系统的可用性保留的计算机系统。CheckOutRecord 对象使用支持此模型的 CheckOutRecord 表中的外键将特定的用户链接到保留的系统。此模型不仅提供保留计算机系统的有效方式,而且还提供了维护每个用户和计算机系统的历史记录的功能。
JDBC DMS 元数据和基本查询
所有的 J2EE 持久化技术都提供了一些机制来将业务数据对象映射回持久性数据源。使用容器管理的持久性实体 EJB 技术,可以由部署时生成的代码管理数据库中 CMP 字段和列之间的映射,并且可以使用 EJB 查询语言 (EJBQL) 定义查找程序和选择方法之间的查询。具有直接 JDBC 访问功能的应用程序使用 SQL 定义查询,并且依靠自定义代码将结果集数据映射回到实际业务对象。
JDBC 数据中介服务提供了一个元数据 API 来指定这种对象到关系映射。这种元数据 API 不仅定义关系数据存储中数据的形式,而且定义与该数据相关联的查询,并建立了不同对象之间的关系。以上一部分中定义的 HWSystem 对象为例。将此对象的元数据定义为 JDBC DMS 的代码如下所示:
清单 1. 设置 HWSystem 元数据...
MetadataFactory mFactory = MetadataFactory.eINSTANCE;
Metadata hwsystem = mFactory.createMetadata();
Table table = hwsystem.addTable("HWSYSTEM");
Column id = table.addIntegerColumn("ID");
table.addStringColumn("HOSTNAME");
table.addStringColumn("IPADDRESS");
table.addStringColumn("MODEL");
table.addStringColumn("NOTES");
table.addStringColumn("CPUTYPE");
table.addIntegerColumn("CPUSPEED");
table.addIntegerColumn("NUMCPUS");
table.addIntegerColumn("MEMORY");
table.addBooleanColumn("AVAILABLE");
id.setNullable(false);
table.setPrimaryKey(id);
hwsystem.setRootTable(table);
...
该元数据 API 通过确切的数据库表和列名定义了 HWSystem 数据对象的形式。该元数据 API 还提供了定义可以为空的列的能力,并设置了表之间的关系。
通过将数据库的形式定义为 JDBC DMS,我们拥有从数据库检索 HWSystem 数据对象所需的全部信息。基于上面定义的元数据,以下代码示例可以通过 ConnectionWrapper 连接到数据库,构造一个 JDBC DMS 实例,然后对数据库执行缺省 find all 查询。请注意,为了简化示例代码,我们删除了 try/catch 块。
清单 2. 使用中介执行查询 // Establish connection and connection wrapper
DataSource datasource = (DataSource) initialContext.lookup(...);
ConnectionWrapperFactory factory = ConnectionWrapperFactory.soleInstance;
Connection conn = datasource.getConnection();
conn.setAutoCommit(false);
ConnectionWrapper wrapper = factory.createConnectionWrapper(conn);
...
// Create the mediator and perform the query based on the
// hwsystem metadata defined above.
JDBCMediatorFactory mFactory = JDBCMediatorFactory.soleInstance;
JDBCMediator med = mFactory.createMediator(hwsystem, wrapper);
DataObject system = med.getGraph();
...
// Close the connection
wrapper.getConnection().close();
要对数据库执行更复杂的查询,必须定义元数据过滤器。下面的代码创建一个针对 HWSystem 元数据的 find by hostname 过滤器,该过滤器将返回具有所需主机名的 HWSystem 数据对象(如果找到一个的话)。
清单 3. 创建一个过滤器 // Building upon Code Sample 1
...
Filter filter = mFactory().createFilter();
filter.setPredicate("HOSTNAME = ?");
FilterArgument arg = mFactory().createFilterArgument();
arg.setName("HOSTNAME");
arg.setType(Column.STRING);
filter.getFilterArguments().add(arg);
table.setFilter(filter);
...
假定将此过滤器应用于清单 1 中定义的 HWSystem 的元数据,则通过中介执行该 find by hostname 查询所需的代码如下所示:
清单 4. 执行带有过滤器的查询 // Establish connection and connection wrapper as in Code Sample 2
...
JDBCMediatorFactory mFactory = JDBCMediatorFactory.soleInstance;
JDBCMediator med = mFactory.createMediator(hwsystem, wrapper);
DataObject args = med.getParameterDataObject();
args.setString("HOSTNAME", hostname);
DataObject system = med.getGraph(args);
...
// Close the connection
此代码和用于执行 find all 查询的代码的唯一不同之处在于,现在创建了参数数据对象,并且使用该对象在 getGraph() 调用中将 hostname 参数传递给中介。
关系和复杂查询
与 EJB 支持通过容器管理的关系的概念导航数据库中的主键和外键的关系一样,使用 JDBC DMS 元数据,您还可以根据数据库结构定义 SDO 之间的关系。请回过头看一看图 1 中的 Hardware Reservation 系统,您将注意到 UserAccount 和 CheckOutRecord 之间的关系。从关系数据库的角度来看,这种关系是由 CheckOutRecord 表中的外键字段 (USERACCOUNT_ID) 支持的,CheckOutRecord 表提供指向特定 UserAccount 的主键的链接。如果要将这种关系重新转换为 JDBC DMS 数据,您必须在建立关系之前定义两个表。
清单 5. 设置带有关系的复杂元数据...
MetadataFactory mFactory = MetadataFactory.eINSTANCE;
Metadata userWithRecord = mFactory.createMetadata();
// Define UserAccount table
Table userTable = userWithRecord.addTable("USERACCOUNT");
Column userID = userTable.addIntegerColumn("ID");
userTable.addStringColumn("SHORTNAME");
userTable.addStringColumn("FULLNAME");
userTable.addStringColumn("EMAIL");
userTable.addStringColumn("PASSWORD");
userId.setNullable(false);
userTable.setPrimaryKey(userID);
// Define CheckOutRecord table on the same set of metadata. Both tables
// are needed in the metadata to establish the relationship between
// the PrimaryKey of the UserAccount table and the associated ForeignKey
// in the CheckOutRecord table.
Table recTable = userWithRecord.addTable(CHECKOUTRECORD);
Column recID = recTable.addIntegerColumn("ID");
recTable.addStringColumn("USAGE");
recTable.addTimestampColumn("CHECKIN");
recTable.addTimestampColumn("CHECKOUT");
recTable.addIntegerColumn("SYSTEM_ID");
Column userFK = recTable.addIntegerColumn("USERACCOUNT_ID");
recId.setNullable(false);
recTable.setPrimaryKey(recID);
// Set the UserAccount table as the root table of the datagraph
userWithRecord.setRootTable(userTable);
// Define the relationship
Key record_userAccountFK = record.addForeignKey(userFK);
userWithRecord.addRelationship(userTable.getPrimaryKey(),
record_userAccountFK);
...
有了这种关系,getGraph() 调用中的结果数据图现在将由两个单独的数据对象清单组成,带有将 UserAccount 数据对象直接链接到它们相应的 CheckOutRecord 数据对象(反之亦然)的引用。基于这一元数据模型,我们现在可以执行查询,返回当前控制着系统的所有用户。为此,我们可以定义元数据过滤器来搜索 CHECKIN 值为空的签出记录。由于数据对象是通过关系链接的,因此所有当前控制着系统的用户的签出记录和相关的用户帐户都将返回。至此,您应该对 JDBC 数据中介服务元数据了然于胸了,并且清楚地知道如何使用这种元数据来形成结果 SDO 数据图。
Hardware Reservation 应用程序的示例查询
现在我们已经介绍了如何使用 JDBC DMS 元数据定义 SDO 应用程序的简单和复杂查询,接着将研究中介 API 编码技术和可以用来改进此应用程序的性能的其他优化。
为了演示这些技术和相关的性能改进,我们将继续构建 Hardware Reservation 系统数据模型。前面关于元数据的讨论已经描述了我们关注的三种查询:
allSystems(清单 1 和 2)
systemByHostname(清单 3 和 4)
usersHoldingSystems(清单 5)
除了这些查询之外,我们还将介绍其他两种查询:recordsByShortname 和 systemsCheckedOutByUser。下面将对伪元数据代码和结果数据图结构进行简要的介绍。
元数据描述 | 添加 HWSystem 表和列 将 HWSystem 表设置为根表 |
结果数据图 | 所有的 HWSystem 数据对象 |
元数据描述 | 添加 HWSystem 表和列 将 HWSystem 表设置为根表 创建主机名过滤器 |
结果数据图 | 单个 HWSystem 数据对象 |
元数据描述 | 添加 CheckOutRecord 表和列 添加 UserAccount 表和列 将 CheckoutRecord 表设置为根表 创建 CheckOutRecord 和 UserAccount 表之间的关系 创建 UserAccount 简称列过滤器 |
结果数据图 | 单个 UserAccount 数据对象和所有相关联的 CheckOutRecords 数据对象 |
元数据描述 | 添加 UserAccount 表和列 添加 CheckOutRecord 表和列 将 UserAccount 表设置为根表 创建 UserAccount 和 CheckOutRecord 表之间的关系 创建 CheckOutRecord CheckInDate 列过滤器 |
结果数据图 | 多个 CheckOutRecord 数据对象和所有相关联的 UserAccount 数据对象 |
元数据描述 | 添加 HWSystem 表和列 添加 CheckOutRecord 表和列 创建 HWSystem 和 CheckOutRecord 之间的关系 添加 UserAccount 表和列 创建 CheckOutRecord 和 UserAccount 之间的关系 将 HWSystem 表设置为根表 将 UserAccount 表设置为外部表 创建 HWSystem Available 列过滤器 创建 CheckOutRecord CheckInDate 列过滤器 |
结果数据图 | 多个 HWSystem 数据对象以及相关联的 CheckOutRecord 数据对象 |
性能测试方法
为了量化与即将发布的本文的第 2 部分中讨论的优化有关的性能改进,我们编写了一些服务方法来调用每个测试中所用的特定测试方案。这些服务方法是通过调用 System.getCurrentTimeMillis() 函数包装的,以获得单个循环的启动和执行时间。然后将该执行块放在一个 for 循环中,并且将执行时间的总和作为给定测试方案的总执行时间记录下来。为了显示不同方案的性能差异,循环的次数经过了调整,并且应该在每个方案图中加以注明。
在一些情况下,服务方法执行的操作仅限于演示特定点的单个操作。但是,大部分测试方案都有更宽的范围,重点在于使用 JDBC 数据中介服务生成 SDO 所需的整个操作序列。因此,服务方法执行获得连接到数据库所需的全部操作,基于元数据构造中介实例,生成随机选择的参数(如有必要),执行中介 getGraph() 调用,并关闭数据库连接。
在分析即将发布的本文的第 2 部分中提供的性能数据时,请记住,这些测量是基于总执行时间而不是吞吐量的。因此,在显示数量比较的图中,数值越小表示性能越好。这与吞吐量性能图形成了对比,在吞吐量性能图中,数值越大表示性能越好。在阅读本文的剩余部分时,请记住这一细微的差别。
结束语
现在您应该已经基本了解了服务数据对象、 WebSphere Application Server V6.x 提供的 JDBC DMS,以及这些技术如何适应不断发展的许多持久性数据对象技术(如 Enterprise JavaBeans)。这些技术不是每个应用程序的最佳体系结构,所以在决定何时将 SDO 和 JDBC 数据中介服务用于应用程序时一定要慎重。
到现在为止,我们讨论了如何编写 JDBC 数据中介服务代码来执行一些基本选择查询和更高级的查询(涉及过滤器和数据库中的多个表)。在第 2 部分中,我们将重点讨论具体编码技术和可以用来优化 JDBC DMS 和元数据代码性能的最佳实践。我们不仅将把这些技术和最佳实践应用于在此处为 Hardware Reservation 系统定义的五个事务查询,而且将基于我们刚刚讨论的性能测试方法分析与每项技术和最佳实践有关的性能改进。准备好了这些信息,我们就可以在下一部分中全面讨论这些技术。
更多精彩
赞助商链接