编写高性能 Java 数据访问应用程序,第 1 部分: pureQuery 带注释的方法风格简介

最大化安全性和可配置性

pureQuery 是一种高性能 Java™ 数据访问平台,其目标主要是简化数据访问应用程序的开发和管理。它由工具、API 和运行时组成。本文介绍 pureQuery 带注释的方法风格 —— 这是一种简单、灵活的风格,属于命名查询(named-query)范例,可以静态或动态地执行 SQL。本文先解释为什么开发人员要选择使用带注释的方法风格编写 pureQuery 应用程序,再解释带注释的方法风格与 pureQuery 内联编程风格之间的差异,并简要概述 pureQuery 带注释的方法的强大特性。

Heather Lamb, 软件工程师, IBM

Heather LambHeather Lamb 是硅谷 pureQuery 运行时小组的一名开发人员。



2008 年 8 月 12 日

概述

pureQuery

pureQuery 是为 IBM Data Studio 提供的一个特性,可以通过 IBM Data Studio pureQuery RuntimeIBM Data Studio Developer 获得。

本文讨论与带注释的方法的编程风格相关的以下主题:

  • 描述带注释方法的编程风格
  • 选择使用带注释方法编程风格的原因
  • 使用带注释的方法风格开发 pureQuery 应用程序的步骤(立即阅读该 小节)。
  • 描述代码生成,给出生成的代码示例
  • 描述在 pureQuery 接口中定义带注释的方法的需求
  • 使用 pureQuery 接口执行 SQL
  • 介绍带注释的方法风格的一些选择特性,例如批处理、生成的 RowHandlersParameterHandlers、生成的键以及使用 XML 配置文件修改代码生成器的输出

如果您已经准备好开始编程,那么可以跳到 技术性崩溃。接下来介绍一个简单的例子展示为什么开发人员要选择使用带注释的方法风格开发 pureQuery 应用程序。


什么是带注释的方法编程风格?

为了介绍带注释的方法编程风格,首先需要理解这两种 pureQuery 编程风格的背景知识。

内联编程风格 的开发目的是为了满足客户对快捷、简便的编程风格的需求,这种风格很容易被熟悉 Java™ Database Connectivity(JDBC)的开发人员掌握 —— 其特点就是可以更快、更简单地编程。内联风格最初的目标是减少 JDBC 程序员熟悉的一些重复的编程任务,同时提供一个 API,工具可以轻松地利用该 API 将数据访问开发与 Java 开发联系在一起。由于应用程序中定义 SQL 语句的方式,这种编程被称作 “内联”。在内联风格中,SQL 语句是在运行时声明或构造的,并作为 String 的实例传递给公共的 Data 接口方法。内联风格可以最大化编程速度和开发灵活性,并支持动态执行。后期文章将提供对公共 Data 接口 API 的概述。关于内联风格的更多信息,可以在 pureQuery 文档中找到(参见 参考资料)。

本文主要讨论带注释的方法编程风格,这种风格的演化和内联编程风格相似,但是它还有另外一个目标,那就是最大化编写的 pureQuery 应用程序的可配置性和安全性。带注释的方法风格是专门为同时支持动态和静态数据库访问而设计的。其目的是满足对类似于 Java Persistence API(JPA)的用于数据访问的命名查询编程接口的客户需求 —— 更快、更简单地编程,必要时能够支持静态执行。

与内联编程风格一样,带注释的方法也起源于应用程序定义 SQL 语句的方式。在带注释的方法风格中,SQL 字符串被定义为 Java 5.0 的一个元素:pureQuery Annotation。pureQuery 为此目的定义的方法注释有 @Select (注释 SQL 查询)、@Update (注释 SQL DML 语句)和 @Call (注释 SQL CALL 语句)。这些注释被放在用户定义接口内的用户定义方法声明中。代码生成器预处理接口,为每个已声明的、带有注释的方法生成实现代码。生成的实现代码使用 pureQuery 运行时执行注释中定义的 SQL 语句。在注释元素中预先定义 SQL 字符串,可以简化静态执行支持。

Data Studio 对带注释的方法风格的工具支持包括一个代码生成器,它可以创建用户编写的带注释的方法的实现。代码生成的结果就是第二个实现类,该实现类被编译并用于执行初始接口中声明的 SQL 语句。图 1 说明了用户定义的带注释的方法接口、代码生成器和生成的实现类之间的关系。

图 1. pureQuery 带注释的方法的代码生成
pureQuery 带注释的方法的代码生成

一个有趣的例子

本节介绍一个虚构的例子:Silver Castles 的数据访问开发小组,Silver Castles 是一家正在不断发展的公司,销售各种银制产品。该公司正在 Silver Castles 网站上开发一个新的基于 Web 的店面,并且已经决定使用 pureQuery 环境来开发数据访问应用程序的持久层。当他们观看了 pureQuery 工具演示 并阅读了 pureQuery 教程 之后,很快就决定使用 pureQuery。他们了解到,使用 motivating 快速开发、测试和部署用于多平台的基于 Java 的数据访问持久层非常容易。当这个小组决定何时使用带注释的方法编程风格、何时使用内联编程风格时,还会受到其他一些因素的影响。有一个叫做 Bob 的开发人员决定成为带注释的方法编程风格方面的专家。他收集了一些知识,以帮助小组决定何时使用这两种编程风格,后面介绍了这些知识。

决定使用带注释的方法

Bob 首先审视了带注释的方法编程风格。几小时后,他回到小组,并给出一份清单,上面列出带注释的方法风格的一些亮点。

范例:

  • 遵从命名查询的风格,目标是等同或超过现有对象-关系映射器和持久化解决方案的功能
  • 鼓励将持久层(CRUD)语句与应用程序在带注释接口中的其余部分分离。其结果是,开发中对 SQL 语句的更改不会扩散到整个应用程序,也不会影响负责其他层的开发人员。带注释的接口为管理整个应用程序的 SQL 语句提供一个集中控制点。
  • SQL 语句文本是在开发时知道的。但是可以提前对 SQL 语句文本进行分析,以便开发人员优化输出对象的分配。

静态支持:

  • pureQuery 带注释的方法编程风格使得执行静态 SQL 成为一个部署选项,而不是设计选项。带注释的方法代码可以动态地部署,也可以使用 IBM Data Servers 上的静态 SQL 包来部署,而不必修改任何代码
  • 从应用程序开发人员和前端应用程序的角度透明转换到静态执行。
  • 选择针对静态执行进行部署可以获得静态 SQL 的全部好处:安全、性能、监控和预运行时优化。

代码生成:

  • 最小化手动编写代码:先声明接口方法签名和 SQL 语句,然后由 pureQuery 代码生成器产生数据访问代码实现。
  • 返回查询结果,由 pureQuery 预处理为多种对象,例如已填充的 pureQuery beanIterators,或者列名的 Lists,或者列值 Maps
  • 要对数据访问进行预处理和后处理,使用 Hook 定制生成的代码的行为。
  • 使用 XML 配置文件 为每个目标数据库定义不同的 SQL。

开发和使用带注释的方法

下面的清单 1 显示了 Bob 编写的 pureQuery 接口的一部分,亲自体验带注释的方法风格。他使用该接口向小组成员解释带注释的方法风格的基础。pureQuery 带注释的接口是带注释的方法风格的构建块。pureQuery 接口由以下部分组成:

  1. 一个 Java interface 定义包含:
    1. 一个或多个方法声明
    2. 对于每个方法声明,有一个 pureQuery 数据访问注释 - @Select@Update@Call。每个注释包括一个 SQL 语句,其中包含方法被调用时要执行的 SQL 语句(也可以选择在一个配置文件、而不是注释元素中提供该 SQL 语句)。
    3. 对于每个方法声明,有一个 Java 返回类型,表明 pureQuery 运行时将数据访问结果返回给调用者时需要使用的对象格式。
    4. 对于每个方法签名,有一些 Java 参数类型,包括表明用于执行 SQL 语句的参数的对象类型。

除了以上描述的那些方法声明或定义外,接口定义文件不包含任何其他方法声明或定义。定义好 pureQuery 接口之后,应用程序的其他层在概念上可以将该接口当作一个数据库 —— 可以很容易地通过调用声明的 Java 数据访问方法来从 Java 中访问。当另一个应用程序层调用一个带注释的方法时,它将收到执行相关联的 SQL 语句的结果,形式为一个适当格式化的 Java 对象。开发其他应用程序层时,不需要 SQL 方面的知识。

清单 1. pureQuery 带注释的方法风格的接口
package com.ibm.db2.pureQuery; 

import java.util.Iterator;
import com.ibm.pdq.annotation.Select; 

public interface CustomerData {
   // Select all PDQ_SC.CUSTOMERs and populate Customer beans with results
   @Select(sql="select CID, NAME, COUNTRY, STREET, CITY, PROVINCE, ZIP, PHONE,
                      INFO from PDQ_SC.CUSTOMER")
   Iterator<Customer> getCustomers();

   // Select PDQ_SC.CUSTOMER by parameters and populate Customer bean with results
   @Select(sql="select CID, NAME, COUNTRY, STREET, CITY, PROVINCE, ZIP, PHONE, 
                      INFO from PDQ_SC.CUSTOMER where CID = ?") 
   Customer getCustomer(int cid); 
  ...
}

生成一个实现

定义好上述接口后,Data Studio 中的 pureQuery 项目工具自动将它提供给 pureQuery 代码生成器。pureQuery 代码生成器产生该接口的一个实现,放在一个名为 CustomerDataImpl.java 的文件中。为便于读者查看,清单 2 中给出了生成的文件。注意:生成的文件中的所有代码(清单 2)都不是由开发人员手动编写的。

您不需要查看生成的代码。如果您想看看 Silver Castle 应用程序如何使用以上接口,可以跳到 下一步。接下来,了解更多关于生成的代码的信息。

代码生成器和生成的代码

代码生成器以用户定义的 pureQuery 接口为输入,例如由 SilverCastles 开发人员定义的简单的 CustomerData。它生成的代码用来执行针对接口中每个方法进行注释的每个 SQL 语句。它还生成用于将结果处理为所声明类型的代码。生成的代码调用 pureQuery 运行时来执行每个方法所需的处理。

关于生成的代码有一些重要的注意事项:

  • 生成的元素:对于每个声明的方法,生成的元素包括方法定义、一个内部 pureQuery StatementDescriptor、一个生成的 RowHandlerResultHandler,以及一个内部 pureQuery ParameterHandler
  • 实现类名称:生成的实现文件的名称以初始的用户定义接口的名称为基础,在名称后面加上 “Impl”。在这个例子中,开发人员编写了 CustomerData 接口,代码生成器产生 CustomerDataImpl 类。生成的类实现 CustomerData 接口。数据访问应用程序的其他层不会使用实现类的名称;它们总是引用和使用用户定义接口。但是,知道实现文件的名称还是有用的,以便检查生成的代码。
  • 实现超类:除了实现用户定义接口外,生成的实现类还扩展内部的 pureQuery 类 BaseData,后者实现外部的 Data 接口。这个超类是 pureQuery 运行时的一部分,负责处理访问数据库和处理结果的固定的、重复的操作。
  • 看到生成的代码时,不要感到困惑。开发人员不需要查看这些代码,除非他们执意要这么做。

使用带注释的方法

至此,项目已经构建完毕,Data Studio pureQuery 工具已经调用了 pureQuery 代码生成器,并且接口实现也已经生成并编译。开发人员现在可以通过调用 pureQuery 带注释的接口中声明的带注释的方法,从应用程序的其他层访问数据。这个过程很简单。应用程序通过发送给 DataFactory 的一个请求实例化 pureQuery 接口的一个实例(概念上的数据库)。然后,它调用数据访问方法,如下所示:

清单 3. 调用带注释的方法
...
java.sql.Connection con = ...;

// use the DataFactory to instantiate the CustomerData interface
CustomerData cd = DataFactory.getData(CustomerData.class, con);

// execute the SQL for getCustomers() and get the results in Customer beans
Iterator<Customer> cust = cd.getCustomers();

// the application can now consume the Iterator of Customer beans
...

通过几行代码,就访问了数据库,执行了所需的 SQL 语句,并将结果处理成方便的 Customer 类的数据 bean 的 Iterator,因此应用程序可以直接使用。更加令人印象深刻的是,即使要实现持久层方法,开发人员也只需声明这里所调用的方法。pureQuery 代码生成器和运行时会处理其余的工作。


分解带注释的方法风格

本节将这个示例分解成带注释方法编程风格的几个重要组成部分。pureQuery 文档包含对这些和其他 pureQuery 编程概念的完整描述(参见 参考资料)。

pureQuery 接口

在以上例子中,用户定义的 pureQuery 接口被命名为 CustomerData。合理地命名接口,以反映它所代表的信息源,这样做很有帮助,如 Silver Castles 示例一样。例如,CustomerData 方法检索和更新关于公司客户的信息。

如上所述,pureQuery 接口只包含带注释的方法的声明。

带注释的方法的声明

pureQuery 接口中的每个方法声明包含以下必要元素:

  • 三个 pureQuery 注释中的一个:@Select@Update@Call
  • 对于每个注释,有一个 sql=<string> 元素,其中的 string 包含一个有效的 SQL 语句,这个 SQL 语句将在方法被调用时执行(可选地,也可以在一个 XML 配置文件中提供 SQL 语句)。
  • 一个标准的 Java 方法声明

已声明的返回类型

方法声明的返回类型表明 SQL 语句的结果返回什么样的对象格式。取决于所使用的注释,受支持的返回类型有所不同。例如,对于查询,可以返回各种集合和简单类型。对于更新,可以使用标准的更新计数格式。除了其他格式外,StoredProcedureResult 返回类型可以方便地封装存储过程调用的结果。

还可以由 pureQuery 引擎将结果处理成用户定义 pureQuery bean 的一个集合。在我们的示例中,开发人员声明的返回类型是 Customer bean 的一个 Iterator。pureQuery 使用一组 bean 约定和需求 将数据库查询结果直接映射到用户定义的 bean 类。pureQuery bean 中的注释 可以覆盖默认的映射行为。而且,可以由用户提供的 RowHandler 将结果手动映射到 bean。否则,生成的 RowHandler 将在把 bean 结果返回给调用者之前,自动执行默认的映射,将结果映射到用户定义的 bean 类。

声明的参数类型

带注释的方法声明中的参数类型决定 pureQuery 如何在运行时获得 SQL 语句参数值。SQL 语句中的参数占位符与带注释的方法中声明的参数之间可能存在一对一的映射,但是也不一定如此。pureQuery 遵从 参数占位符语法规则 来决定如何从带注释的方法的参数列表中声明的参数映射 SQL 语句参数。

与其他数据访问 API 相比,pureQuery 中的参数可以采用更多种类的类型。例如,一个声明的 pureQuery bean 参数可以为 一些 SQL 语句参数占位符 提供运行时值。对于 pureQuery 带注释的方法,pureQuery bean 和 Java 集合类型都受支持。请查看带注释的方法 语法图,以获得可能声明的参数类型的完整列表。带注释方法的声明的参数类型可以在很大程度上影响 SQL 语句的执行。例如,可以通过使用带注释的方法 @Update 中的某些参数类型发起批量更新。对于批量更新,需要使用一个集合参数多次执行一条 SQL 语句,每次执行时使用不同的参数值。

带注释的方法的参数可以直接使用,也可以赋给变量,以利用 pureQuery 引擎的特定处理功能。批量更新处理是 pureQuery 引擎底层自动优化代码和改进开发的一个例子。对数据 bean 参数的特殊 pureQuery 处理还可以节省开发时间和精力。例如,@GeneratedKey pureQuery bean 注释使 pureQuery 引擎可以用插入或更新操作后生成的数据库值自动更新 bean 参数的字段。

Hook

由于大多数代码都是使用带注释的方法的风格生成的,pureQuery 开发小组中有些人一开始有所顾虑。Bob 解释道,Hook 在执行生成的代码期间为进行特殊处理提供了一种简单的方法。现在不需要手动编写方法调用,以便将每个应用程序调用围绕一个带注释的接口方法,而是采用另一种方法,定义一个 Hook 以注册到带注释的接口。在进入和退出每个带注释的方法时,pureQuery 运行时回调 Hook。这提供了一种回调机制,以便将生成的数据访问代码与特殊的处理关联起来。Hook 定义所需的 pre() 方法,Hook 进行注册后,在进入一个带注释的方法时得到调用。每个 Hook 还将定义一个 post() 方法,当该 Hook 注册后,在从一个带注释的方法返回控制之前,该方法会被立即调用。

Hook 方法为特殊的运行时回调处理提供上下文感知,以防 pre()post() 的用户定义实现需要它。这种感知是通过 pre()post() 方法调用中的参数获得的。可以根据这些参数的值设计不同的特殊处理。例如,取决于调用 Hook 方法的接口方法的名称、提供给那个方法的运行时参数值或者该方法执行的 SQL 语句类型的不同,处理也会有所不同。此外还以 Data 参数的形式提供了 Hook 所注册的 pureQuery 接口的一个句柄。这些值都可以供 Hook 的实现者查看和修改。此外,返回值在被返回给调用者之前,还可以供 post() 方法的实现者使用。下面是 Bob 为了向 Silver Castles 小组进行演示而编写的 Hook 处理代码的一个例子:

清单 4. 用于特殊处理的 Hook
public static class TrackingHook implements Hook {

   public void pre(String methodName, Data objectInstance,
                    SqlStatementType sqlStatementType, Object... parameters)  {
      System.out.println(methodName + "**Customer data has been accessed**"); 
   }

   public void post(String methodName, Data objectInstance,
                    Object returnValue, SqlStatementType sqlStatementType,
                    Object... parameters) {
     // do nothing
  }
}

至此,已经使用 Hook 定义了特殊的处理,开发人员可以通过向进行实例化的接口注册他们的 Hook 的一个实例,以确保可以运行。清单 5 展示如何注册一个 Hook

清单 5. 注册一个 Hook
... 
Connection con = ...;

// use the DataFactory to instantiate the interface and
// provide an instance of Hook to be registered with the instance
   
CustomerData cd = DataFactory.getData(CustomerData.class, con, new TrackingHook());

// execute the SQL for getCustomers() and get the results,
// the pre() and post() methods are automatically called
Iterator<Customer> cust = cd.getCustomers();

// the application now consumes the Iterator of Customer beans 
...

这是有关 Hook 的特殊处理的一个非常简单的例子。请参阅 pureQuery 文档中的 Hook 示例,以查看可以使用 Hook 实现的更复杂的类型示例。

针对多目标的开发

Bob 向他的小组解释了带注释的方法的最后一个特性,使用 XML 配置文件 将 SQL 语句与应用程序的 Java 代码实现完全分离。这样一来,当将 Java 应用程序部署到一个要求不同 SQL 语句的目标数据库时,就可以避免重复编写该 Java 应用程序。例如,如果小组想将同一个应用程序部署到一个遗留数据源上,且该数据源使用稍有不同的模式,那么他们不需要重新编写带注释的接口或 pureQuery bean。

清单 6 展示了用于初始模式的 SQL 语句。

清单 6. 用于初始模式的 SQL 语句
select CID, NAME, COUNTRY, STREET, CITY, PROVINCE, ZIP, PHONE, INFO
     from PDQ_SC.CUSTOMER

清单 7 展示了用于遗留模式的 SQL 语句。

清单 7. 用于遗留模式的 SQL 语句
select CUSTID, NAME, COUNTRY, STREET, CITY, PROV, ZIP, PHONE, INFO
     from PDQ_SC.CUSTOMER

注意,在清单 7 中,CID 和 PROVINCE 列的名称变成了 CUSTID 和 PROV。这改变了用于发出查询的 SQL 语句,也改变了从结果到 Customer 数据 bean 的默认映射。

清单 8 展示了 customer pureQuery bean。

清单 8. Customer pureQuery bean
package com.ibm.db2.pureQuery;

public class Customer  {

     // Class variables
     protected int cid;
     protected String name;
     protected String country;
     protected String street;
     protected String city;
     protected String province;
     protected String zip;
     protected String phone;
     protected String info;
...

小组无需编写新的带注释的接口或 Customer bean 类来支持遗留模式,只需使用一个 XML 配置文件向生成器提供附加的输入,以支持遗留模式。下面列出了 XML 配置文件的一个片段,它展示了如何覆盖一个 SQL 字符串和用户定义 bean 类映射。为了将应用程序部署到遗留系统上,小组将初始的带注释的接口定义和下面的 XML 文件提供给生成器。生成器产生正确的生成代码,从而部署到遗留数据库上:

清单 9. 生成替换代码的 XML 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm">

<named-native-query name=" com.ibm.db2.pureQuery.CustomerData#getCustomers()">
  <query><![CDATA[select CUSTID, NAME, COUNTRY, STREET, CITY, PROV, ZIP, PHONE, INFO 
                     from PDQ_SC.CUSTOMER]]>
  </query>
</named-native-query>
...
<entity class="com com.ibm.db2.pureQuery.Customer">
        <attributes>    
                    <basic name="cid">
                      <column name="CUSTID" />
                    </basic>
                    ...                
                    <basic name="province">
                       <column name="PROV" />
                    </basic> 
                    ...           
        </attributes>
        </entity> 
</entity>
</entity-mappings>

要了解关于将 XML 配置文件提供给生成器,以便为 pureQuery 接口提供备选 SQL 语句和/或对象映射的更多信息,请参阅 pureQuery 在线文档


结束语

本文概要地介绍了 pureQuery 带注释的方法编程风格,以及开发小组选择使用 pureQuery 带注释的方法进行编程的动机。本文还列出了开发带注释的方法风格的应用程序所需的步骤。另外也介绍了这种风格的部分特性。

如果您有兴趣进一步了解如何开发 pureQuery 带注释的方法风格的应用程序,请访问本文正文和参考资料小节中提供的 pureQuery 在线文档、其他文章和相关教程的链接。

参考资料

学习

  • 您可以参考本文在 developerWorks 全球站点上的 英文原文
  • 阅读 pureQuery 文档,获得关于该工具的完整描述。
  • pureQuery demo 是对 pureQuery IDE 工具的最简单易懂的介绍。
  • “使用全新的 IBM pureQuery 工具提高 Java 数据库开发生产力” 系列 介绍了该工具的基础知识,展示了如何检测和修复 Java 程序中的 SQL 问题,并提供了关于 pureQuery 快速应用程序开发的一个完整的教程。
  • developerWorks Information Management 专区:在这里可以学到更多关于信息管理的知识。还可以找到技术文档、how-to 文章、培训、产品信息等等。
  • 通过访问 Data Studio 资源中心 学习,有关 Data Studio 开发的更多方法和技巧。
  • 随时关注 developerWorks 技术活动和网络广播
  • 技术书店:浏览关于这些主题和其他主题的书籍。

获得产品和技术

  • 在线文档中介绍了 pureQuery javadoc
  • 使用可直接从 developerWorks 下载的 IBM 试用软件 构建您的下一个开发项目。
  • 使用可直接从 developerWorks 下载的 IBM Data Studio 体验 Data Studio 的强大功能。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management, Java technology
ArticleID=330338
ArticleTitle=编写高性能 Java 数据访问应用程序,第 1 部分: pureQuery 带注释的方法风格简介
publish-date=08122008