考虑将 SQLJ 用于 DB2 V8 Java 应用程序

来源:百度文库 编辑:神马文学网 时间:2024/03/29 16:26:51

文档选项

将此页作为电子邮件发送

级别: 初级
Connie Tsui, DB2 解决方案集成团队, IBM 多伦多实验室
2003 年 2 月 01 日
了解为什么 SQLJ 是注重安全性、性能和简单性的开发人员所选择的语言。其中还包含了代码样本。
使用 Java™ 访问关系数据的标准方法有两种:SQLJ 和 JDBC™。对于 IBM® DB2® Universal Database™(UDB)应用程序,为什么应该考虑 SQLJ 呢?这是因为当应用程序员要考虑安全性、性能和简单性时,往往会选择 SQLJ 这样的语言。本文向您介绍了有关 SQLJ 的一些背景,讨论了相对于 JDBC,SQLJ 所具有的优势,还特别指出了 DB2 UDB V8.1 所提供的一些新增和改进的 SQLJ 特性。




回页首
1997 年 4 月,一个由数据库供应商组成的非正式和开放小组开始定期开会,交流有关如何在 Java 编程语言中使用静态 SQL 语句和构造的想法。主要参与者包括 IBM、Oracle、Compaq、Informix®、Sybase、Cloudscape 和 Sun Microsystems。该小组把他们正制定的规范命名为 JSQL。但他们发现术语 JSQL 是一个注册商标,因此就将 JSQL 重命名为 SQLJ。1997 年 12 月,Oracle 向其它成员提供了一个 Java 中嵌入式 SQL 的参考实现。这个参考实现可以在任何支持 JDK 1.1 的平台上运行,并且它与供应商无关。1998 年 12 月,完成了 Java 中嵌入式 SQL 规范的整个开发工作,并被接纳为 ANSI 标准 Database Language - SQL, Part 10 Object Language Bindings (SQL/OLB)ANSI x3.135.10-1998。这个规范一般称为 SQLJ 规范的第 0 部分。现在它被称为 SQL/OLB(对象语言绑定,Object Language Binding)。
SQLJ 规范目前由两部分组成:
SQL/OLB:Java 中的嵌入式 SQL
这部分标准规定了在 Java 方法中嵌入 SQL 的语法和语义,还规定了一些机制来确保生成的 SQLJ 应用程序的二进制可移植性。这正是本文要阐述的主题。 SQL/JRT:使用 Java 编程语言的 SQL 例程和类型
这部分标准包含以下内容: 将 Java 静态方法作为 SQL 存储过程和用户定义的函数来调用的规范。它定义了 SQL 扩展,用于在 SQL 系统中安装 Java 类,在 SQL 中以 SQL 函数和存储过程方式调用 Java 类的静态方法,获取指定的参数输出值以及返回 SQL 结果集。 将 Java 类用作 SQL 用户定义的数据类型的规范。它定义了 SQL 扩展,用于将 Java 类用作 SQL 中的数据类型。
术语:当我们在本文其余部分使用术语 SQLJ 时,仅指 SQL/OLB。




回页首
SQLJ 环境由两个阶段组成:开发和运行时。本节向您介绍每个阶段所涉及到的组件以及各组件间的关系。
使用 SQLJ 开发应用程序需要三个组件:转换程序、概要文件定制程序和概要文件绑定程序。有三个实用程序提供了支持这三个组件的功能,它们分别是: sqlj、db2sqljcustomize和 db2sqljbind。这里对图 1中演示的过程作一个概述:
首先,调用 SQLJ 转换程序(sqlj)以读取 SQLJ 源文件并检查该程序中 SQLJ 语法的正确性。转换程序生成一个 Java 源文件,可能不生成 SQLJ 概要文件或生成多个 SQLJ 概要文件,并且如果所生成的 Java 源文件中没有错误,那么它还可以选择将该源文件编译成字节码(缺省情况)。生成的 Java 源文件将嵌入式 SQL 替代为对执行 SQL 操作的 SQLJ 运行时的调用。 接着,调用 SQLJ 概要文件定制程序(db2sqljcustomize)来为生成的序列化概要文件创建 DB2 定制。该定制程序可以选择(缺省情况下) 联机检查能够动态编译的 SQL 语句。联机检查执行语法、语义和模式验证。也可以选择(缺省情况下)调用 SQLJ 概要文件绑定程序以绑定 DB2 包。 如果选择在概要文件定制期间不执行自动绑定,那么可以单独调用 SQLJ 概要文件绑定程序(db2sqljbind),以将先前定制的 SQLJ 概要文件绑定到数据库。 不管概要文件是否被定制,要查看其内容,可以使用 SQLJ 概要文件打印程序(db2sqljprint)以文本格式打印出概要文件的内容。

为访问数据库,SQLJ 运行时要依靠 JDBC 驱动程序来获取数据库连接。未定制的 SQLJ 应用程序可以与任何 JDBC 2.0 驱动程序一起运行。在开发期间,为了测试,只需运行未定制应用程序。要运行定制的 SQLJ 应用程序,可以使用 V8 基于 CLI 的 JDBC 类型 2 驱动程序 - 通用 JDBC 驱动程序(类型 2 或类型 4)来建立数据库连接。本节中,我们只描述用于定制的 SQLJ 应用程序的运行时环境。
当您运行 SQLJ 应用程序时,SQLJ 运行时从定制的概要文件中读取有关 SQL 操作的信息,并执行与存储在定制中的包关键信息(包名、包一致性标记和集合名)相符的 DB2 包中的语句。





回页首
SQLJ 规范和 JDBC 规范都描述了如何使用 Java 来访问关系数据库。本节从以下几个方面讨论它们之间的差异:
标准和 SQL 规范级别安全性性能语法SQLJ 和 JDBC 互操作性类型和模式检查
表 1汇总了我们在本节中所描述的 SQLJ 和 JDBC 之间的差异。
SQLJ 是 ISO/IEC 9075-10:2000 Information technology -- Database languages -- SQL -- Part 10: Object Language Bindings (SQL/OLB)的实现。SQLJ 不属于 J2EE 平台。
JDBC 是 J2SE 1.4 和 J2EE 1.4 平台规范的一个必不可少的组件。它自 Java 软件开发工具箱(Java Software Development Kit,JDK)V1.1 之后已成为其核心部件。它包含在 java.sql 包中。JDBC 驱动程序必须至少支持 Entry SQL-92 语句,在该规范中还定义了一些扩展。
SQLJ 中实现的安全性权限模型是用户考虑使用 SQLJ 的一个主要原因。使用静态 SQL,安全性特权就被指派给了包创建者,并被存储在 DB2 包中。
使用定制的 DB2 SQLJ,静态地执行 SQL;因此使用包所有者的特权来执行 SQL 语句。任何运行 SQLJ 应用程序的其他用户都必须被授予具有该包的 EXECUTE 特权。即,被授权可以运行程序的用户未必有权对该程序所查询或正在修改的同一表或视图执行 SELECT、UPDATE、DELETE 或 INSERT 操作,除非显式地授予该用户相应的特权。
拥有连接到数据库并执行 JDBC 应用程序特权的人可以执行这些应用程序中的 SQL 语句。因此,用户必须获得访问表的特权。
SQLJ 允许在 Java 程序中嵌入 SQL 语句,这类似于 SQL-92 允许 SQL 语句嵌入到 C、COBOL、FORTRAN 以及其它编程语言中的方式。但是,根据 SQLJ 概要文件是否被定制,可以决定 SQLJ 应用程序是动态还是静态地运行。当将包存储在 DB2 数据库中时,就会预编译 SQLJ 应用程序并优化 SQL 语句的路径长度。静态执行的 SQLJ 应用程序的性能会优于 JDBC 的性能。
如果想利用静态执行(我们建议这样做),必须使用 SQLJ 概要文件定制程序来定制概要文件。
JDBC 提供了 SQL 语句的动态执行。如果这些语句中存在语法或语义错误,那么在该应用程序运行时任何此类异常都会产生。
使用 DB2 UDB 监视器可以验证静态或动态的 SQL 语句处理。监控方法有两种:快照监控和事件监控。快照监视器提供有关数据库在某个特定时间点的活动信息。事件监视器记录了 DB2 UDB 事件发生的特定位置。下面的清单 1 摘自从 JDBC 程序生成的事件监视器的样本输出。“Type: Dynamic”告诉您动态执行了 SELECT job FROM staff WHERE name = ? 语句。
10) Statement Event ... Appl Handle: 23 Appl Id: G91AA377.G576.00F306261BF2 Appl Seq number: 0001 Record is the result of a flush: FALSE ------------------------------------------- Type : Dynamic Operation: Prepare Section : 1 Creator : NULLID Package : SYSSH200 Consistency Token : SYSLVL01 Package Version ID : Cursor : SQL_CURSH200C1 Cursor was blocking: FALSE Text : SELECT job FROM staff WHERE name = ?
清单 2摘自从 SQLJ 程序生成的事件监视器的样本输出。输出中的“Type: Static”和“Package: SRQT402”告诉您,对 SRQT402 包静态执行了该语句。
10) Statement Event ... Appl Handle: 12 Appl Id: G91ABD18.G47D.00F306C01D63 Appl Seq number: 0001 Record is the result of a flush: FALSE ------------------------------------------- Type : Static Operation: Execute Section : 1 Creator : NULLID Package : SRQT402 Consistency Token : SARoQCAp Package Version ID : Cursor : Cursor was blocking: FALSE
注:有些语句对于定制的 SQLJ 程序会正确执行,但对于未定制的 SQLJ 程序就不会正确执行。可滚动游标的 UPDATE/DELETE WHERE CURRENT OF 就是这样一个示例。一般而言,如果底层 JDBC 驱动程序不支持某个功能,那么未定制的 SQLJ 程序也不会支持该功能。
性能技巧:
对于单个 select 查询,与盲目地对庞大的 JDBC ResultSets 执行操作相比,可以通过使用由 SQLJ 提供的 SELECT INTO 语法来减少网络活动。
图 3比较了用于单个 select 查询的 SQLJ 和 JDBC 语法。
SQLJ 语法:
#sql [conCtx] { SELECT job INTO :job FROM staff WHERE name = :name };
JDBC 语法:
PreparedStatement pstmt = con.prepareStatement( "SELECT job FROM staff WHERE name = ? FETCH FIRST 1 ROW ONLY" ); ResultSet rs = pstmt.executeQuery(); if ( rs.next() ) job = rs.getString(1); else job = null; pstmt.close();
图 3表明 SQLJ 语法在简单性方面优于 JDBC。SQLJ 的简单性受到了许多 Java 开发人员的欢迎。编写 SQLJ 模块通常要比 JDBC 模块简洁且容易。这暗示着 SQLJ 可以使开发周期缩短并减少开发和维护成本。图 4向您显示了 SQLJ 可以多么简单地向数据库插入一行数据。如果您已有了用其它语言(如 C 或 COBOL)编写的嵌入式 SQL 应用程序,那么就可以使用 SQLJ 轻松地将应用程序迁移到 Java。
SQLJ 语法:
sql [conCtx] { INSERT INTO sales VALUES(:date, :salesperson, :region, :sales) };
JDBC 语法:
PreparedStatement pstmt = con.prepareStatement( "INSERT INTO sales VALUES (?, ?, ?, ?)" ); // set input parameter pstmt.setObject(1, date); pstmt.setString(2, salesperson); pstmt.setString(3, region); pstmt.setInteger(4, sales); pstmt.executeUpdate(); pstmt.close();
SQLJ 语言允许您在 SQLJ 应用程序中使用 JDBC 语句。要使 JDBC 和 SQLJ 之间便于交互,SQLJ 提供了一种方法以便在同一应用程序内共享 SQLJ 连接和 JDBC 连接,这种方法还可以从 SQLJ 迭代器中获取 JDBC 结果集,或从 JDBC 迭代器中获取 SQLJ 结果集。
何时需要在 SQLJ 应用程序中使用 JDBC?
您需要将 JDBC 用于动态操作时;即,在编写程序时不清楚 SQL 操作的时候。清单 3演示了在 SQLJ 程序内用 JDBC 来执行动态查询(WHERE 子句中的名称在开发时是未知的),以及如何将 JDBC 结果集转换到 SQLJ 迭代器。
与 SQLJ 不同的是,JDBC 不能识别 SQLJ 语法,而且 SQL 语句不能嵌入到 JDBC 应用程序。
Public class ResultSetInterop { #sql public static iterator Employees (String name, double salary); public static void main(String[] argv) throws SQLException { // the code for creating the SQLJ connection context (conCtx) and // the Connection object (con) is omitted // create a JDBC statement object to execute a dynamic query Statement stmt = con.createStatement(); String query = "SELECT name, salary FROM staff WHERE "; query += argv[0]; ResultSet rs = stmt.executeQuery(query); Employees SalReport; // turn a JDBC result set to an SQLJ interator using the CAST statement #sql [conCtx] SalReport = { CAST :rs }; while (SalReport.next()) { System.out.println( SalReport.name() + " earns " + SalReport.salary() ); } SalReport.close(); stmt.close(); } }
SQLJ 与 Java 类似的一点是,它也是强类型的。在将 SQLJ 源文件转换成 Java 源文件时,SQLJ 转换程序会检查 SQLJ 语法。这类似于其它 DB2 预编译器。而且,在转换阶段的 Java 编译期间执行迭代器数据类型的转换。例如,在禁止使用双精度的迭代器列(如雇员工资)中,Java 编译器就会阻止该列使用双精度类型。因此, String hv = employees.salary(); 这样的赋值在编译时就会生成一个错误。另外,在概要文件定制期间也执行联机检查,以便可以较早地捕获编程错误。
JDBC 不能在运行时之前进行语法或语义检查。如果存在语法或语义错误,那么在应用程序运行时任何此类异常都会产生。
注:
在 V8.1 中,在概要文件定制期间执行联机检查,而在以前的发行版中这一操作是在转换阶段执行的。 有些 SQLJ 错误只有在运行时才被捕获到。另外,不能动态编译的语句不会进行联机检查。




回页首
表 1汇总了 SQLJ 和 JDBC 之间的差异。
SQLJ JDBC
标准 ISO/ANSI(不属于 J2EE) Sun(属于 J2EE)
SQL 规范级别 SQL-1999 N/A(必须至少支持 Entry Level SQL-92)
安全性 强 一般
性能 较快(在开发期间创建了静态存取方案) 较慢(在应用程序执行期间创建了动态存取方案)
语法 高级(紧凑) 低级(繁琐)
SQLJ 和 JDBC 互操作性 是 N/A
类型和模式检查 强(在开发期间执行) 弱(在运行时期间执行)




回页首
DB2 UDB V8.1 提供了新设计的 SQLJ 驱动程序,它有几个新特性。新的 SQLJ 驱动程序基于一种称为 Distributed Relational Database Architecture™(DRDA®)的开放分布式协议。基于 CLI 的 JDBC 驱动程序(类型 2 和类型 3)以及 V8.1 中引入的新的通用 JDBC 驱动程序(类型 2 和类型 4)都支持该协议。
SQLJ 的主要增强功能可以概括如下:
新的 SQLJ 实用程序和运行时消除了特定于平台的文件一些新特性
DB2 UDB V8.1 中的 SQLJ 实现了纯 Java SQLJ 实用程序和运行时,并带有一些新选项和可选的格式。新的运行时性能比 V7 的性能好得多。
在 V8.1 中,SQLJ 转换程序 sqlj 在缺省情况下总是编译所生成的 Java 源文件。在 V7 中,这个编译选项不能与某些 JDK 一起使用,因此您必须手工编译 Java 文件。
V8.1 中新的概要文件打印程序 db2sqljprint 不再需要您提供 URL,而且它提供了有关要执行的 SQL 语句的详细信息,例如 DB2 语句的类型、节号以及 DB2 结果集元数据信息。
V8.1 中的 SQLJ 概要文件定制程序包含新的序列化概要文件格式,它不必使用 DBRM 文件和绑定文件(.bnd 文件)。新格式完全可以移植到所有平台。它包含所有 BIND 操作所需的所有信息,用户不必在目标系统(UNIX®、Windows®、OS/390® 和 z/OS™)上重新定制序列化概要文件就可以部署在任何服务器平台上。
DB2 UDB V8.1 中添加的主要特性包括以下各项:
使用 DataSource 创建 SQLJ 连接上下文可滚动的迭代器批处理更新
有了 V8.1 SQLJ,您可以使用 JDBC DataSource 接口来创建 SQLJ 连接。而且,缺省连接上下文的实现也更改了。要获得缺省连接上下文,SQLJ 运行时要执行 JNDI 查询以获得 jdbc/defaultDataSource。如果没有注册任何 jdbc/defaultDataSource,那么在驱动程序试图访问上下文时,就会抛出一个空上下文异常。因此,必须向 JNDI 注册 jdbc/defaultDataSource,或者通过调用 DefaultContext.setDefaultContext(userctxt) 来设置缺省上下文。
建议:在 SQLJ 子句中使用显式的连接上下文。
// Create connection context class Ctx with the new dataSource keyword #sql public static context Ctx with (dataSource="jdbc/sampledb"); String userid, password; String empname; ? // Create connection context object conCtx for the connection to jdbc/sampledb Ctx conCtx = new Ctx(userid, password); #sql [conCtx] { SELECT lastname INTO :empname FROM emp WHERE empno = '000010' }; ? conCtx.close();
可滚动的迭代器允许您向前移、向后移或移动到指定行。与 JDBC 中可滚动的游标相似,可滚动的迭代器可以是 不敏感的,也可以是 敏感的。
不敏感的迭代器意味着在迭代器打开后,它不能看到底层表中的更改。不敏感的迭代器是只读的。 敏感的迭代器意味着迭代器可以看到迭代器或其它处理对底层表所作的更改。例如,清单 5中的代码演示了如何使用指定的迭代器对雇员表的所有行以逆序方式检索雇员号和雇员的姓氏。
// Declare a scrollable iterator. #sql iterator ScrollIter implements sqlj.runtime.Scrollable with (sensitivity = SENSITIVE) (String EmpNo, String LastName); { ScrollIter scrlIter; #sql [conCtx] scrlIter={ SELECT empno, lastname FROM emp }; scrlIter.afterLast(); while (scrlIter.previous()) { System.out.println(scrlIter.EmpNo() + " " + scrlIter.LastName()); } scrlIter.close(); }
批处理更新允许将语句集中到一起,随后将其以批处理方式发送到数据库,一次执行完这些语句。您可以在批处理更新中包含以下几种类型的语句:
搜索到的 INSERT、UPDATE 或 DELETE 语句 CREATE、ALTER、DROP、GRANT 或 REVOKE 语句 只带输入参数的 CALL 语句
与 JDBC 不同,SQLJ 允许异构批处理,这些批处理中包含带有输入参数或主机表达式的语句。因此在同一个 SQLJ 语句批处理中可以包含同一语句的实例、不同语句的实例、带输入参数或主机表达式的语句的实例以及不带输入参数或主机表达式的语句的实例等。
建议:在关闭批处理或在结束使用 ExecutionContext(会使批处理打开)之前,要显式地调用 executeBatch()。这将确保执行经过批处理的所有语句。
清单 6中的代码段向您显示了如何以批处理方式执行更新操作来给所有管理人员加薪。
#sql iterator getMgr(String); { getMgr deptIter; String mgrnum = null; int raise = 400; int currentSalary; String url = null, username = null, password = null; testContext conCtx = new testContext (url, username, password, false); // Acquire execution context. // All statements that execute in a batch must use this execution context. ExecutionContext exeCtx = new ExecutionContext(); // Invoke ExecutionContext.setBatching (true) to create a batch. exeCtx.setBatching(true); #sql [conCtx] deptIter = { SELECT mgrno FROM dept }; #sql {FETCH :deptIter INTO :mgrnum}; while (!deptIter.endFetch()) { #sql [conCtx] { SELECT SALARY INTO :currentSalary FROM emp WHERE empno = :mgrnum}; #sql [conCtx, exeCtx] { UPDATE emp SET SALARY = :(currentSalary+raise) WHERE empno =:mgrnum }; #sql { FETCH :deptIter INTO :mgrnum }; } exeCtx.executeBatch(); exeCtx.setBatching(false); #sql [conCtx] {COMMIT}; deptIter.close(); exeCtx.close(); conCtx.close(); }




回页首
相对于 JDBC,SQLJ 的 DB2 实现具有明显的优势。在预编译时 SQLJ 较简单语法和类型以及模式检查大大降低了开发成本。SQLJ 还具有在 SQLJ 应用程序中嵌入 JDBC 语句这样的灵活性。这意味着一个应用程序可以同时利用 SQLJ 和 JDBC 的优点。当安全性和性能对 Java 应用程序至关重要时,SQLJ 是正确的选择。




回页首
Meet the Experts: John Campbell on Java Performance for DB2 Applications
http://www7b.software.ibm.com/dmdd/library/techarticle/0202campbell/0202campbell.html DB2 Universal Database V8 Application Development
http://www.ibm.com/software/data/db2/udb/ad/v8/java 使用 DB2 版本 8 开发企业 Java 应用程序
http://www.ibm.com/developerworks/cn/dmdd/library/techarticles/0209hutchison/index.shtml DB2 V8 信息中心
http://www.ibm.com/cgi-bin/db2www/data/db2/udb/winos2unix/support/index.d2w/report 在使用 SQLJ 和 JDBC 时获取最优的 DB2 性能(教程)
http://www.ibm.com/developerWorks/cn/cndmdd.nsf/dmdd-onlinecourse-bynewest/9607CCEEE2E42D09C8256D18002359F4?OpenDocument IBM Java 技术专区
http://www.ibm.com/java/ DB2 开发者园地 Java 专区
http://www.ibm.com/software/data/db2/java SQL/OLB 标准
http://www.ansi.org/



Connie Tsui是 IBM 多伦多实验室 DB2 解决方案集成团队的专职软件分析师。她从多伦多大学(University of Toronto)获得了计算机科学学士学位。她目前主要从事 DB2 和 WebSphere® 的集成