添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Oracle Account

Manage your account and access personalized content. Sign up for an Oracle Account

Sign in to Cloud

Access your cloud dashboard, manage orders, and more. Sign up for a free trial

作者:Chris Muir

了解如何通过将 Oracle Designer 表 API 与 Oracle JDeveloper 集成来为基于 Designer 的系统赋予新的活力。

2007 年 10 月发布

Oracle Designer 现在稳固地处于维护模式,但您还是会经常看到传统数据库模式到处都有 Designer 的痕迹,包含生成的表 API 和良好的旧 cg_ref_codes。此类结构对于众多新接触 Oracle 的开发人员来说可能并不熟悉,但是该方法曾经是最前沿的。

幸运的是,由于 Oracle JDeveloper 11 g 对应用开发框架业务组件 (ADF BC) 进行了多种增强,因此无需忽略或替换此类传统代码。通过包含 ADF BC 固有的对调用数据库 PL/SQL 的支持,您可以为传统的 Oracle Designer 数据库系统赋予新的活力。

本文专门探究如何将 Oracle Designer 表 API 与 Oracle JDeveloper 11 g (截至本文撰写之日为技术预览版 2)相集成。

Oracle Designer 蜕变

当前的 Oracle 开发情形与 10 年前完全不同。现在,我们有最前沿的工具,如 Oracle SOA 套件、Oracle XML DB、Oracle Application Express,当然还包括 Oracle JDeveloper 的 ADF。然而,过去能选择的工具只有 Oracle Forms 和 Reports,而通过 Oracle 自己的 CASE 工具 Oracle Designer 构建 100% 生成的 Forms 和数据库应用程序则是最高技术水平。

100% 生成的目标是定义和设计您的模块,通常为 Forms,一切都在 Oracle Designer 内完成。该方法的主要优势是它还包括您的表定义,而您的模块与该定义相关。对 Oracle Designer 中表定义的更改会自动流动到定义的模块上,如果该模块为 Oracle Forms 模块,则流动到定义的块上。例如,增加表列大小或删除列时,相关的 Form 将通过相应地扩大或删除列进行反映。如果对结果满意,开发人员将从准备就绪的 Oracle Designer“生成”一个实际的 Form。

虽然 Oracle Designer 在许多 Oracle 站点已渐渐被淘汰,而且 100% 生成已不再是共同目标,但是除了 Oracle Designer 创建的模块之外,在许多现有的传统系统(尤其是数据库)中仍能发现 Oracle Designer 生成的代码。在许多模式中都可以发现表 API、日志表和良好的旧 cg_ref_codes 等数据库产物。

这些传统数据库产物给 Oracle JDeveloper 11g 的 ADF 等现代工具提出了集成挑战吗?本文将通过了解将 Oracle Designer 的表 API 集成到 ADF 中的具体过程,来向您证明答案是:“根本没有”。

本文假设您了解 Oracle JDeveloper ADF BC(包括实体对象和视图对象)以及 Java 编码和 JDBC 编程。大量 SQL 和 PL/SQL 知识也会对读者有帮助。

对于本文,考虑以下简单数据模型:

图 1 数据模型

该数据模型包括 organisations 和 events 两个表,二者之间为一对多的关系。例如,随着时间的推移,Sage Computing Services 机构可能会在澳大利亚举办许多 Oracle 培训活动。

本文的 Oracle JDeveloper 11g 演示应用程序可从 这里 下载。它包含完整的源代码,但为简单起见,本文中的示例只截取了其中的一部分。

下载的 Oracle JDeveloper 应用程序包含一个项目数据库,其中包括用于创建演示模式的脚本和必需的数据库对象。使用您喜欢的数据库开发帐户在 SQL*Plus 中运行 install.sql 脚本以创建示例模式。然后,确保将 Oracle JDeveloper 应用程序默认连接详细信息更改为连接到所选的数据库和模式。

Oracle Designer 数据库产物:表 API

表 API 就是这样一个 Oracle Designer 数据库产物。

Oracle Designer 不仅能生成 Oracle Forms,还能创建一组包装指定模式表的程序包,这些程序包称为表应用程序编程接口(表 API)。有了这些程序包,您可以调用程序以通过表 API PL/SQL 程序包间接选择、插入、更新和删除相关的表数据。

这样做的目的是生成的 Oracle Forms 将不会通过 DML 直接对表进行读取或写入操作,而是使用基于一个数据库程序包创建 Oracle Form 的块级特性。反过来,Oracle Designer 将允许在表 API 中生成特定的业务规则,现在这将在 Oracle Form 相关项上强制执行。在某些站点,该逻辑开发过程中相当多的编程逻辑和投资都可以通过表 API 结构创建。

如果我们考虑我们的演示数据模型中的 events 表,为表生成的表 API 程序包将如下所示。(注意,这是 Oracle Designer 生成的程序包针对本文所需的简化版本。在上面的“下载”一节中,您可以获得示例数据模型的表 API 的完整示例。)

TYPE cg$row_type IS RECORD (EVENT_NO cg$row.EVENT_NO%TYPE ,ORG_ID cg$row.ORG_ID%TYPE ,DESCRIPTION cg$row.DESCRIPTION%TYPE ,CONTACT_NAME cg$row.CONTACT_NAME%TYPE ,START_DATE cg$row.START_DATE%TYPE ,END_DATE cg$row.END_DATE%TYPE ,COMMENTS cg$row.COMMENTS%TYPE ,the_rowid ROWID); TYPE cg$ind_type IS RECORD (EVENT_NO BOOLEAN DEFAULT FALSE ,ORG_ID BOOLEAN DEFAULT FALSE ,DESCRIPTION BOOLEAN DEFAULT FALSE ,CONTACT_NAME BOOLEAN DEFAULT FALSE ,START_DATE BOOLEAN DEFAULT FALSE ,END_DATE BOOLEAN DEFAULT FALSE ,COMMENTS BOOLEAN DEFAULT FALSE); PROCEDURE ins (cg$rec IN OUT cg$row_type, cg$ind IN OUT cg$ind_type, do_ins IN BOOLEAN DEFAULT TRUE); PROCEDURE upd (cg$rec IN OUT cg$row_type, cg$ind IN OUT cg$ind_type, do_upd IN BOOLEAN DEFAULT TRUE, cg$pk IN cg$row_type DEFAULT NULL); PROCEDURE del (cg$pk IN cg$pk_type, do_del IN BOOLEAN DEFAULT TRUE); PROCEDURE lck (cg$old_rec IN cg$row_type, cg$old_ind IN cg$ind_type, nowait_flag IN BOOLEAN DEFAULT TRUE); PROCEDURE slct (cg$sel_rec IN OUT cg$row_type); END cg$EVENTS;

cg$EVENTS.ins 过程采用以下参数:

  • cg$rec:在同一程序包中定义的一个 PL/SQL 记录类型,包含表的所有列。通过记录传递到过程的值将被插入到 events 表中。表 API 可能会修改提交的值,因此 cg$rec 被作为 OUT 参数返回,以允许调用模块获得这些更改并将它们应用到自己的数据集。
  • cg$ind:cg$EVENTS 程序包中定义的另一个 PL/SQL 记录类型,包括大量布尔列用以指示填充了哪些 cg$rec 列。该参数由表 API 内的 ins 过程返回用以指示它是否修改或默认了任何值,从而使调用模块获悉更新了哪些列。
  • do_ins:指示过程进行实际插入操作的简单布尔标志。
  • cg$EVENTS.upd 过程采用一组与 ins 过程类似的参数,增加了 cg$pk 记录类型来表示要更新的记录的主键值,还增加了一个进行更新操作的标志。

    cg$EVENTS.del 过程采用 cg$pk 记录来指示要删除的记录,并增加了一个标志来指示是否应该进行该操作。

    cg$EVENTS.lck 过程用于在数据库中更新或删除某条记录之前锁定该记录。该过程采用 cg$old_rec(包含更新或删除语句发生之前的所有原有值)和 cg$old_ind(指示已更新的列)两个记录类型。

    nowait_flag 布尔值指示在去掉 lock-wait 的过程中,锁定语句是带 no-wait 选项发出的。

    将表 API 与 ADF 业务组件结合使用

    在 Oracle JDeveloper 的 ADF BC 中,您通常会创建一个实体对象 (EO) 来表示要在应用程序中使用的数据库模式中的每个表。EO 被设计为在用户更改您应用程序中相关数据时整理数据并针对数据库发出 DML 插入、更新和删除语句以及行锁定语句。

    《面向 Forms/4GL 开发人员的 Oracle 应用开发框架开发人员指南》 在第 26.4 节概述了基于数据库 PL/SQL 程序包 API 创建 EO 的基础。下面概述了量身设计 Oracle Designer 表 API 所需的操作。

    通过更改 EO 的自定义 EntityImpl 类中的阻塞点 DML 方法 doDML(),可以轻松改写默认的 EO 数据操作语言 (DML) 功能。默认的 doDML() 方法如下所示:

    protected void doDML(int operation, TransactionEvent e) { if (operation == DML_INSERT) { // Insert logic } else if (operation == DML_UPDATE) { // Update logic } else if (operation == DML_DELETE) { // Delete logic } else // unlikely but you never know super.doDML(operation, e);

    现在在每个 if 代码块中,您可以编写自己的逻辑来调用数据库程序包过程。为此,您将使用 JDBC 整理 EO 数据并发出对您自己的对数据库的自定义调用,传入来自 EO 的值并接受从相应的 cg$EVENTS 过程返回的值的更改。

    利用 Oracle 对象类型克服 JDBC 限制

    通常,使用 JDBC 调用传入简单数据类型的数据库过程时不会遇到什么问题。但不幸的是,JDBC 并不直接支持 cg$EVENTS.ins 过程采用的两个 PL/SQL 记录类型。

    一个简单的解决办法是创建 JDBC 确实支持的数据库对象类型。为此,执行以下步骤:

  • 对于您关心的这两个 PL/SQL 记录类型,您需要创建一个反映相同数量的列的数据库对象类型和一个含有行 ID 的大型字符串。例如,下面的 cg$events_cg$row_type 对象类型反映了 cg$EVENTS.cg$row_type PL/SQL 记录类型。
    CREATE OR REPLACE TYPE cg$events_cg$row_type AS OBJECT (event_no NUMBER(6) ,org_id NUMBER(4) ,description VARCHAR2(75) ,contact_name VARCHAR2(30) ,start_date DATE ,end_date DATE ,comments VARCHAR2(1000) ,the_rowid VARCHAR2(256)); CREATE OR REPLACE TYPE cg$events_cg$ind_type AS OBJECT (event_no NUMBER(1) ,org_id NUMBER(1) ,description NUMBER(1) ,contact_name NUMBER(1) ,start_date NUMBER(1) ,end_date NUMBER(1) ,comments NUMBER(1)); PROCEDURE ins (row IN OUT cg$events_cg$row_type ,ind IN OUT cg$events_cg$ind_type ,doInsert IN BOOLEAN) rowPlsqlItem cg$events.cg$row_type; indPlsqlItem cg$events.cg$ind_type; FUNCTION to_int(aBool BOOLEAN) RETURN number IS BEGIN IF aBool THEN RETURN 1; ELSE RETURN 0; END IF; BEGIN rowPlsqlItem.event_no := row.event_no; rowPlsqlItem.org_id := row.org_id; rowPlsqlItem.description := row.description; rowPlsqlItem.contact_name := row.contact_name; rowPlsqlItem.start_date := row.start_date; rowPlsqlItem.end_date := row.end_date; rowPlsqlItem.comments := row.comments; rowPlsqlItem.the_rowid := row.the_rowid; indPlsqlItem.event_no := ind.event_no = 1; indPlsqlItem.org_id := ind.org_id = 1; indPlsqlItem.description := ind.description = 1; indPlsqlItem.contact_name := ind.contact_name = 1; indPlsqlItem.start_date := ind.start_date = 1; indPlsqlItem.end_date := ind.end_date = 1; indPlsqlItem.comments := ind.comments = 1; cg$events.ins(rowPlsqlItem, indPlsqlItem, doInsert); row.event_no := rowPlsqlItem.event_no; row.org_id := rowPlsqlItem.org_id; row.description := rowPlsqlItem.description; row.contact_name := rowPlsqlItem.contact_name; row.start_date := rowPlsqlItem.start_date; row.end_date := rowPlsqlItem.end_date; row.comments := rowPlsqlItem.comments; row.the_rowid := rowPlsqlItem.the_rowid; ind.event_no := to_int(indPlsqlItem.event_no); ind.org_id := to_int(indPlsqlItem.org_id); ind.description := to_int(indPlsqlItem.description); ind.contact_name := to_int(indPlsqlItem.contact_name); ind.start_date := to_int(indPlsqlItem.start_date); ind.end_date := to_int(indPlsqlItem.end_date); ind.comments := to_int(indPlsqlItem.comments); END ins;

    考虑到本文的目的,未对上述代码进行模块化以保留可读性和清晰性。很显然,模块化会简化好代码的重用,而可下载的示例代码使用更多的过程和函数来减少代码重复。

    此外,为简单起见,该代码没有列出功能等同的 events_pkg 更新、删除和锁定函数,而可下载的示例代码则显示全部代码。

    注意,events_pkg.lck 过程在只需要主键值列表时需要以下额外的对象类型:

    Object preRowArray[] = new Object[attributes.length + 1]; Object postRowArray[] = new Object[attributes.length + 1]; Boolean preIndArray[] = new Boolean[attributes.length]; Object postIndArray[] = new Object[attributes.length]; for (int i = 0, size = attributes.length; i < size; i++) preRowArray[i] = getAttribute(i); for (int i = 0, size = attributes.length; i < size; i++) preIndArray[i] = isAttributeChanged(i); Connection conn = getDBTransaction().createStatement(1).getConnection(); StructDescriptor rowStructDesc = StructDescriptor.createDescriptor(dbRowTypeName, conn); StructDescriptor indStructDesc = StructDescriptor.createDescriptor(dbIndTypeName, conn); oracle.sql.STRUCT rowStruct = new STRUCT(rowStructDesc, conn, preRowArray); oracle.sql.STRUCT indStruct = new STRUCT(indStructDesc, conn, preIndArray); OracleCallableStatement statement = (OracleCallableStatement)conn.prepareCall(dbStatement); statement.setSTRUCT(1, rowStruct); statement.setSTRUCT(2, indStruct); statement.registerOutParameter(1, OracleTypes.STRUCT, dbRowTypeName); statement.registerOutParameter(2, OracleTypes.STRUCT, dbIndTypeName); statement.executeUpdate(); rowStruct = statement.getSTRUCT(1); indStruct = statement.getSTRUCT(2); postRowArray = rowStruct.getAttributes(); postIndArray = indStruct.getAttributes(); for (int i = 0, size = attributes.length; i < size; i++) { Boolean postInd = ((BigDecimal)postIndArray[i]).toString().equals("1"); if (preIndArray[i] != postInd) { String attributeType = getStructureDef().getAttributeDef( attributes[i]).getJavaType().getName(); // This code is incomplete and does not handle all the // datatypes that could come back from the database if (postRowArray[i] == null) setAttribute(attributes[i], null); else if (attributeType.equals("oracle.jbo.domain.Date")) setAttribute(attributes[i] ,new Date((Timestamp)postRowArray[i])); else if (attributeType.equals( "oracle.jbo.domain.Number")) setAttribute(attributes[i] ,new Number((BigDecimal)postRowArray[i])); else if (attributeType.equals("java.lang.String")) setAttribute(attributes[i] ,(String)postRowArray[i]); throw new JboException( "PlsqlEntityImpl.insertUpdate() datatype?"); } catch (SQLException ex) { throw new JboException(ex);

    insertUpdate() 函数采用以下参数:

  • int[] attributes:包含要写入数据库的 EO 中每个属性的索引位置的数组。这将通过原始的 EntityImpl 类中的 static final int attributes 常量生成,我很快就将对此进行介绍。
  • String dbRowTypeName:数据库行对象类型的名称,如 CG$EVENTS_CG$ROW_TYPE
  • String dbIndTypeName:数据库指示器对象类型的名称,如 CG$EVENTS_CG$IND_TYPE
  • String dbStatement:要执行的实际 PL/SQL 调用。例如, BEGIN events_pkg.ins(?, ?, TRUE); END; BEGIN events_pkg.upd(?, ?, TRUE); END; insertUpdate() 函数进行以下不同的操作:
  • 08 到 18 行:对于当前的 EO,将一个包含所有 EO 属性值的数组 preRowArray、一个包含 preIndArray 中的 EO 属性更改指示器的数组以及两个空白的数组集合到一起,来保留从即将进行的 JDBC 调用返回的结果
  • 20 到 31 行:利用已经就绪的属性和更改指示器数组,创建两个 JDBC STRUCT 值,将它们与对应的数据库对象类型相关联,并使用组合的数组填充它们
  • 33 到 34 行:准备对在 dbStatement 中指定的 PL/SQL 程序包和函数的 JDBC 调用
  • 36 到 42 行:将两个 STRUCT 值写入 PL/SQL 函数调用并准备检索两个 OUT 参数
  • 44 行:调用数据库以执行 PL/SQL 函数调用,传入 STRUCT 值并检索 OUT 参数供以后使用
  • 46 到 50 行:数据库插入和更新例程均返回生成的行值和指示器,以标记已更改的列。这些行处理 JDBC prepareCall() 语句生成的结果集合,填充 postRowArray 和 postIndArray 供您检查。
  • 52 到 74 行:迭代通过 postRowArray,为相应的 postIndArray 在其中指明更改的每个属性捕获新属性并将其写入 EO 属性。注意本示例从第 61 行开始是如何只处理有限数量的数据类型的。完整的解决方案需要涵盖从数据库返回的所有数据类型,包括 LOB。
  • 编写 JDBC 删除函数

    处理通过 JDBC 调用删除的方法与 insertUpdate() 函数类似:

    Object preRowArray[] = new Object[pkAttributes.length + 1]; for (int i = 0, size = pkAttributes.length; i < size; i++) preRowArray[i] = getAttribute(i); Connection conn = getDBTransaction().createStatement(1).getConnection(); StructDescriptor rowStructDesc = StructDescriptor.createDescriptor(dbPkTypeName, conn); oracle.sql.STRUCT rowStruct = new STRUCT(rowStructDesc, conn, preRowArray); OracleCallableStatement statement = (OracleCallableStatement)conn.prepareCall(dbStatement); statement.setSTRUCT(1, rowStruct); statement.registerOutParameter(1, OracleTypes.STRUCT ,dbPkTypeName); statement.executeUpdate(); } catch (SQLException ex) { throw new JboException(ex);
  • int[] pkAttributes:包含要写入数据库的 EO 中每个主键属性的索引位置的数组
  • String dbPkTypeName:数据库 pk 对象类型的名称,如 CG$EVENTS_CG$PK_TYPE
  • String dbStatement:要执行的实际 PL/SQL 删除调用。例如, BEGIN events_pkg.del(?, ?, TRUE); END;

    如您所见,delete() 函数与 insertUpdate() 函数十分相似,尽管 delete() 函数不需要处理任何返回的数据。

    编写 JDBC 锁定函数

    处理锁定的方法与前面的函数类似:

    try { Object preRowArray[] = new Object[attributes.length + 1]; Boolean preIndArray[] = new Boolean[attributes.length]; // Note call to getPostedAttribute rather than getAttribute for (int i = 0, size = attributes.length; i < size; i++) preRowArray[i] = getPostedAttribute(i); for (int i = 0, size = attributes.length; i < size; i++) preIndArray[i] = isAttributeChanged(i); Connection conn = getDBTransaction().createStatement(1).getConnection(); StructDescriptor rowStructDesc = StructDescriptor.createDescriptor(dbRowTypeName, conn); StructDescriptor indStructDesc = StructDescriptor.createDescriptor(dbIndTypeName, conn); oracle.sql.STRUCT rowStruct = new STRUCT(rowStructDesc, conn, preRowArray); oracle.sql.STRUCT indStruct = new STRUCT(indStructDesc, conn, preIndArray); statement = (OracleCallableStatement)conn.prepareCall(dbStatement); statement.setSTRUCT(1, rowStruct); statement.setSTRUCT(2, indStruct); statement.registerOutParameter(1, OracleTypes.STRUCT ,dbRowTypeName); statement.registerOutParameter(2, OracleTypes.STRUCT ,dbIndTypeName); statement.executeUpdate(); } catch (SQLException e) { if (e.getMessage().indexOf("TAPI--54") > 0) throw new RowInconsistentException(getKey()); throw new JboException(e);

    lock() 函数的参数几乎与 insertUpdate() 方法的参数完全相同,只有一个关键的区别。在第 14 行,该函数并不通过对 getAttributes() 的调用检索当前的 EO 属性,而是调用 getPostedAttributes() 检索当前的 DML 操作之前的属性值。这是因为表 API lck 方法需要以前的属性值,以便通过将原有值与数据库中的当前值进行比较来查看数据库记录是否已更改。如果值已更改,则表明其他用户已经更新了该记录。如果值未更改,该例程将对选定的行进行锁定。

    在第 45 到 49 行的 SQLException 内,您仍然需要检查返回的数据库异常消息是否包括字符串“TAPI--54”。该消息由 API lck 方法专门标记,指示另一个用户已经对该记录进行锁定。在这种情况下,您通过引发 JBO 异常 RowInconsistentException 来标记该错误,这是标记行锁定错误的一般 ADF BC 机制。

    改写 EO doDML() 方法以调用您的 JDBC 函数

    完成对 JDBC 的插入、更新、删除和锁定调用的方法后,您现在可以使用以下代码完成用于所有 DML 操作的 doDML() 方法:

    static final int attributes[] = new int[] { EVENTNO, ORGID, DESCRIPTION, CONTACTNAME, STARTDATE, ENDDATE, COMMENTS }; static final int pkAttributes[] = new int[] { EVENTNO }; static final String rowType = "CG$EVENTS_CG$ROW_TYPE"; static final String indType = "CG$EVENTS_CG$IND_TYPE"; static final String pkType = "CG$EVENTS_CG$PK_TYPE"; protected void doDML(int operation, TransactionEvent e) { try { if (operation == DML_INSERT) { super.insertUpdate(attributes, rowType, indType, "BEGIN events_pkg.ins(?, ?, TRUE); END;"); } else if (operation == DML_UPDATE) { super.insertUpdate(attributes, rowType, indType, "BEGIN events_pkg.upd(?, ?, TRUE); END;"); } else if (operation == DML_DELETE) { super.delete(pkAttributes, pkType, "BEGIN events_pkg.del(?, ?, TRUE); END;"); } else // unlikely but you never know super.doDML(operation, e); } catch (SQLException ex) { throw new JboException(ex);

    该代码执行以下步骤:

  • 01 到 04 行:在 EntityImpl 内,您创建一个包含各个 EO 属性索引的数组以及一个包含 EO 中主键属性索引的数组,将它们传递到您的 JDBC 方法,最终传递到表 API 方法。
  • 06 到 08 行:包含您的 Oracle 对象类型名称的 static final String。
  • 13 到 20 行:针对每种操作类型,在超类 PlsqlEntityImpl 内调用您的 JDBC 方法,传入要调用的相关的数组、Oracle 对象类型名称和 PL/SQL 程序包函数。
  • 改写 EO lock() 方法以调用您的 JDBC 函数

    最后,您还需要改写 EO lock() 方法以间接调用等同的表 API cg$EVENTS.lck 方法。如您所见,该代码比 doDML() 方法还要简单:

    public void lock() { super.lock(attributes, rowType, indType, "BEGIN events_pkg.lck(?, ?); END;");

    完成上述步骤后,您的 Oracle JDeveloper 代码将通过 Oracle Designer 数据库表 API 运行。

    将传统的代码集成到大多数新系统中是一个挑战。然而,由于 Oracle Designer 的表 API 广泛支持 JDBC 数据库调用以及清晰的框架检查点方法(可以改写以实现自定义的解决方案),从而使得集成 Oracle JDeveloper 内的成熟 ADF 变得轻而易举。这应该为任何对采用新技术同时集成原有的数据库产物感到担心的站点吃了一颗定心丸,就是说以前的和今后的开发投资都将是安全的。

    Chris Muir [ http://one-size-doesnt-fit-all.blogspot.com ] 是 Oracle ACE 总监(Oracle 融合中间件)和高级顾问,还是 SAGE Computing Services 在澳大利亚的 Oracle 培训师。他在传统的 Oracle 开发方面有 10 多年的工作经验,最近在使用、培训和推广 Oracle JDeveloper 和 ADF 方面硕果累累。