学习如何才能将 IBM DB2 V9 用作存储表单数据的存储库,以及如何集成 DB2 和 IBM Workplace Forms。DB2 V9 引入了以原生格式存储 XML 数据的功能,从而提供了与 Workplace Forms 表单之基于 XML 结构的理想集成。
通常,关系型 DBMS 将 XML 数据作为单个的列-行实体(例如 CLOB、BLOB 或 VARCHAR)进行存储,或将 XML 数据分解(“撕碎”)为多个列。这些方法给很多应用程序带来了问题,其中包括增加开发和查询的复杂性、难于使模式满足新的业务需求以及较差的子文档查询运行时性能。通过在数据库本地存储 XML 数据,现在用户可以充分利用全新的 XML 查询功能,使用 DBMS 的健壮索引和数据存储功能,并且考虑了 XML 模式的增长和更改。
IBM Workplace Forms 文档完全采用 XML 格式进行描述,而将这些表单保存在(能够以原生形式管理 XML 的)数据库中是一种理想匹配。本文所描述的整个架构如图 1 所示。表单包括一个提交按钮,它将执行传到后端 servlet 的 HTTP post。servlet 使用 IBM Workplace Forms Server API 来提取数据、操作表单或同时执行这两种操作。另外,也可以执行任何其他业务逻辑,例如将表单放置在工作流过程上。最后,servlet 使用 IBM DB2 JDBC API 在 IBM DB2 V9 中存储表单。
本文的其余内容划分为一系列步骤,分别描述了:
若要运行示例 servlet,必须安装并配置 Workplace Forms Server API。servlet 使用这些 API 来提取表单数据。虽然可以使用基本 XML 解析器来提取数据,但是使用 Workplace Forms Server API 可带来很多益处,其中包括代码较少、处理数字签名以及使用压缩的表单数据的能力。
重要的是,请注意 JAVA API 使 JAVA 本地接口(Java Native Interface,JNI)可调用本地库(即 DLL)。要查找这些 DLL 文件,请确保 “C:\Program Files\ibm\Workplace Forms\Server\2.6\API\redist\msc32” 目录位于系统路径中。另外,在运行时期间,API 将查找名为 PureEdgeAPI.ini 的文件,该文件确定了所使用的 API 版本。在 C:\Windows 目录中创建名为 PureEdgeAPI.ini 的文件,使用以下内容:
[API]可以在 Workplace Forms Server API Installation and Setup Guide 中找到有关安装和配置 Workplace Forms Server API 的更多详细内容。
必须安装 DB2 V9,并且必须创建用来存储表单的数据库。(DB2 Express-C 可以 免费下载。)如果正在使用本文随附的示例,则从 DB2 命令窗口运行以下命令:
最后,若要学习如何设计集成 servlet,将使用 IBM Rational Application Developer for WebSphere Software。另外,servlet 是在带有 Rational Application Developer 的 IBM WebSphere Application Server V6.0 Test Environment 中运行的。可以获取 免费试用版的 Rational Application Developer。
必须存在一种机制,可以按照该机制提交已完成的表单,用于处理和存储。方法之一是使用简单的 XFDL 按钮,将整个表单提交到 servlet。(另一种方法是使用 XForms 按钮,仅允许提交数据实例。)对于本例,我们对保存整个表单比较感兴趣。要演示提交过程,可使用示例 Purchase Order 表单,可以从本文结束部分下载该示例表单。(请注意添加提交按钮的步骤也适用于自己的定制表单)。使用 IBM Workplace Forms Designer 进行设计,示例 Purchase Order 表单包含简单的文本字段,用于捕获用户数据(参见图 2)。
若要更新 Purchase Order 表单,则启动 Workplace Forms Designer。接着单击 File - New - Project 来创建新项目。选择创建简单项目并将其命名为 FormsDB2V9。创建了项目后,按照以下步骤导入示例 Purchase Order 表单:
将表单导入项目后,双击表单,在 Designer Perspective 中打开它。从这里,可以向表单添加 XFDL Submit Button,如图 3 所示。将按钮放置到表单上后,右键单击按钮,然后选择 Convert Items - XFDL 以确保按钮具有正确的类型。
应按照表 1 所描述的值来设置按钮的属性:
| 属性名 | 值 |
|---|---|
| sid | cmdSubmit |
| value | Submit |
| type | Submit |
| url | http://localhost:9080/SaveFormApp/SaveForm |
添加并配置提交按钮后,通过右键单击表单并在弹出菜单中选择 Export 来导出表单。选择 File System 作为导出目的地,然后按照其余提示将已更新的表单保存到所选择的目录中。在 IBM Workplace Forms Viewer 中打开表单时,应显示最新添加的提交按钮。但是由于尚未实现 servlet,因此按钮仍不起作用。
既然已经更新示例 Purchase Order 表单,包含了 submission 按钮,那么将重点转到接收表单提交的 servlet 上。虽然本文中的步骤是特定于 Rational Application Developer 的,不过您可以在所选择的 IDE 中开发 servlet 代码。若要学习 servlet 是如何操作的,则将随附的 Web 应用程序资源(Web application resource,WAR)文件导入新项目。从 Rational Application Developer 切换到 Web Perspective (Windows - Open Perspective),并执行以下步骤来导入随附的示例:
所导入的 Web 应用程序包含两个类文件:SaveForm.java 和 DBController.java。SaveForm. 类是 servlet 类,它将执行接收表单和使用 Workplace Forms Server API 来提取表单数据的功能。DBController 类是由 SaveForm. 调用的,该类使用 DB2 JDBC API 来存储表单数据值并将表单存储到 DB2 中。虽然完成了示例,不过还是值得浏览一下实现的特定部分。打开 SaveForm. servlet 类并找到 init() 方法(清单 1)。当容器第一次初始化 servlet 时,将调用此方法。请注意在这里通过调用 DTK.initialize() 方法,来实现对 Workplace Forms Server API 的初始化。
public void init(ServletConfig config) throws ServletException
{
try
{
// Initialize the API
DTK.initialize("SaveFormApp", "1.0.0", "7.0.0");
}
catch (Exception e)
{
System.out.println(e.getMessage());
e.printStackTrace();
}
}
|
接下来,查看 doPost() 方法(清单 2),向 servlet 提交表单时(即,用户单击先前添加到表单的提交按钮时),将调用该方法。请注意调用了 saveForm() 方法将表单保存到数据库。此方法包含从表单提取必需的实例数据并将数据保存到数据库的逻辑。
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
try
{
System.out.println("Received request to save form.");
// Call method to save form. saveForm(req);
// If no errors thrown, then return success message
resp.getWriter().println("PO succesfully submitted.");
}
catch (Exception e)
{
// Error was thrown, so return error message
resp.getWriter().println("Error: " + e.getMessage());
}
}
|
虽然在表中将整个表单作为单个 XML 结构进行存储是可能的,不过在某些情况下,可能选择沿着基于 XML 的表单将主要的表单值存储在其自身的列中。本文所附的示例就是如此:将客户 ID、客户名以及订单项作为其自身列中的值进行存储。将表单存储在第四列中。这样提供了一种以纯粹的关系型方式方便地访问最常用数据的方法,而且还可以查询并使用其余具有 XML 结构的数据。
从表单提取数据并引导数据存储到数据库的主要任务是在 saveForm() 方法中进行处理的(清单 3)。请注意表单本身只是基于 XML 的文档,并且可以使用标准的基于 Java 的 XML 解析器来操作表单。如前所述,使用 Workplace Forms Server API 来处理表单具有某些优势,并且在处理 Workplace Forms 表单时推荐使用该方法。Workplace Forms Server API 为基于 Java 的 XML 解析器提供包装器,因此减少了代码量。操作表单字段和值、处理数字签名以及处理二进制附件仅仅是这些 API 所提供一些附加功能。另外,如果以压缩格式(一种可用选项)提交表单,则 Workplace Forms Server API 将透明地处理表单数据的解压缩。
清单 3 对使用 Workplace Forms API 从请求对象获取表单以及从表单提取字段值进行了说明。多次使用了 FormNodeP 对象的 extractXFormsInstance() 方法,用于从表单提取特定数据。该方法的第一个参数是所提取模型的 ID。因为本例使用默认的模型,所以指定的值为 null。第二个参数指向所要提取的数据实例项(即名为 INSTANCE1 的数据实例中的 custnumb)。第三个参数确定是否还要提取非相关实例数据。第四个参数确定是否忽略约束或验证错误。第五个参数 null 指定该方法所操作的节点继承了所需的名称空间。最后一个参数表示输出流,用于保持或存储所提取的数据。注意 destroy() 方法的使用也是比较重要的。由于 API 使用 JNI 来访问底层本地库,所以 servlet 必须明确使用 destroy() 方法来清理所使用的资源,从而避免内存泄漏。
重要的是,请注意实例化 DBController 类对象时,示例 servlet 将数据库用户 ID 和密码作为参数进行传递。本例中将用户 ID 和密码都设置为 db2admin。若要成功运行示例 servlet,可能需要更新这些值以匹配您的环境。
private void saveForm(HttpServletRequest req)
{
FormNodeP theForm. = null;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try
{
// Obtain the XFDL object from the IFSSingleton static object
XFDL theXFDL = IFSSingleton.getXFDL();
// Read the form. from the HTTP request inputstream
theForm. = theXFDL.readForm(req.getInputStream(), 0);
// Extract the customer num value from the form. getValue()
// is a private method that strips the < and > signs.
stream.reset();
theForm.extractXFormsInstance(null,
"instance('INSTANCE1')/customer/custnumb", false, false, null, stream);
String customerNum = getValue( stream.toString() );
System.out.println("customerNum=" + customerNum);
// Extract the customer name value from the form. stream.reset();
theForm.extractXFormsInstance(null,
"instance('INSTANCE1')/customer/custname", false, false, null, stream);
String customerName = getValue( stream.toString() );
System.out.println("customerName=" + customerName);
// Extract the order details from the form. This is the entire xml
// structure for data instance INSTANCE2 and corresponds to the
// items items selected in the order table.
stream.reset();
theForm.extractXFormsInstance(null,
"instance('INSTANCE2')", false, false, null, stream);
String rderDetail = stream.toString();
System.out.println("orderDetail=\n" + orderDetail);
// Store the entire form. as a variable so we can also store it
// into the database along with the metadata collected above.
// This also is an xml data structure.
stream.reset();
theForm.writeForm(stream, null, 0);
String form. = stream.toString();
// Use the DBController class to store the customer id, customer name,
// order details, and form. into the database.
// Note the order details and form. are stored in their
// native XML format into the database.
DBController dbController = new DBController("db2admin", "db2admin", "ordersdb");
dbController.createOrder(customerNum, customerName, orderDetail, form);
}
catch (Exception e)
{
System.out.println(e.getMessage());
e.printStackTrace();
}
finally
{
// The finally block of code is called right before this method exists
// (whether of not an error is thrown). In here we call the XFDL.destroy()
// method to clean up resources. IF YOU FORGET TO CALL THIS METHOD THEN
// A MEMORY LEAK WILL OCCUR BECAUSE UNDERLYING NATIVE LIBRARIES WILL
// NEVER RELEASE THE MEMORY ALLOCATIONS MADE!!!
if ( theForm. != null )
{
try
{
theForm.destroy();
}
catch (Exception e)
{
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
}
|
该实现中比较重要的其他代码片段是 DBController.createOrder() 方法(如清单 4 所示)。提取表单数据后,sameForm() 方法将调用 DBController.createOder() 方法,将表单保存到数据库。如果您以前使用 JDBC API,那么应熟悉这个代码片段。请注意两个重要差别:第一个是在 SQL 命令中使用了 xmlparse 函数,用于执行 XML 存储数据的显式解析。第二个是 preserve whitespace 选项,将保留 XML 文档中的边界空格符。忘记包含该选项将导致表单在存储到数据库之前发生改变(即移除边界空格符)。这样的话,如果对表单进行了签名,则不再有效。
public void createOrder(String customerNum, String customerName,
String orderDetail, String theForm) throws Exception
{
Connection conn = null;
try
{
conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO
ACME.ORDERS (CUSTOMERNUM, CUSTOMERNAME, ORDERDETAIL, ORDERFORM)
VALUES (?, ?, xmlparse(document cast(? as clob(5k)) preserve whitespace),
xmlparse(document cast(? as clob(512k)) preserve whitespace))");
pstmt.setString(1, customerNum );
pstmt.setString(2, customerName );
pstmt.setString(3, orderDetail );
pstmt.setString(4, theForm. );
int rc = pstmt.executeUpdate();
pstmt.close();
}
catch (Exception e)
{
System.out.println("Error storing form. in DB. Msg=" + e.getMessage());
throw new Exception(e.getMessage());
}
finally
{
try
{
if (conn!=null) conn.close();
}
catch (Exception e) {}
}
}
|
为了测试并开发 servlet,将使用 WebSphere Application Server V6.0 Test Environment。这样做允许您快速更改并重新部署 servlet 代码。另外,可以在代码中创建断点,以便使用调试器。在 Rational Application Developer 中执行以下步骤来创建该项目的服务器配置:
若要查看服务器控制台,单击 Console 选项卡 (Window - Show View - Console)。在这里将看到服务器启动的过程。另外,这里将显示集成 servlet 所输出的任何错误/消息。
为了使 servlet 成功使用 DB2 JDBC API,必须更新应用服务器类路径。未能进行更新将导致异常,如无法找到 COM.ibm.db2.jdbc.app.DB2Driver 类。启动服务器后,执行以下步骤,使用 WebSphere Application Server Admin Console 来更新类路径:
运行 servlet 之后,准备对集成进行测试。通过打开先前已更新并从 Workplace Forms Designer 导出到文件系统的 PurchaseOrder 文件来完成上述操作。完成表单之后,单击 Submit 按钮。这时 viewer 将执行提交动作中所指定的传到 URL 的 HTTP post,在本例中是 http://localhost:9080/SaveFormApp/SaveForm。在数据库中存储表单之前,servlet 将用户数据输出到控制台,如图 6 所示。
如果抛出类似于以下内容的错误,请确认包含 pe_cc.dll 文件的目录位于系统路径中:
SRVE0100E: Did not realize init() exception thrown by servlet SaveForm.通过使用 DB2 Control Center 或从 DB2 命令行发布查询,可以验证是否将数据保存到 DB2 中。若要使用 DB2 Control Center,则执行以下步骤:
另外,可以从 DB2 Command Window 运行简单查询来测试表单数据是如何存储的。表 2 描述了一些示例查询。
| 查询命令 | 描述 |
|---|---|
| select * from ACME.ORDERS | 返回定单表中存储的全部数据,包括关系型数据和 XML 数据。 |
| xquery db2-fn:xmlcolumn('ACME.ORDERS.ORDERDETAIL')/order/item | 以 XML 格式返回订购的项。 |
| xquery db2-fn:xmlcolumn('ACME.ORDERS.ORDERDETAIL')/order/item[itemname='Nail']/quantity | 返回 Nail 项的订购数量。 |
在本文中,学习了如何集成 IBM Workplace Forms 与 IBM DB2 V9。更确切地说是如何将表单保存到 DB2 V9 数据库中,同时保持其基于 XML 的数据实例结构。方法是向 Purchase Order 表单添加提交按钮,按下该按钮将向 servlet 提交整个表单。然后在 IBM Rational Application Developer 中建立了开发环境,用于测试示例集成 servlet。该 servlet 使用 IBM Workplace Forms Server API 来提取表单数据。然后使用 JDBC API 将数据和表单保存到数据库。最后,使用 DB2 Control Center 来验证确实已将表单保存到数据库并确认保存了 XML 层次结构。
通过添加必需的代码将所提交的表单放置在工作流过程上,您可以改进本文所描述的集成 servlet。另外,在存储表单之前,可以使用 IBM Workplace Forms API 来更新具有惟一 ID 的表单。这样的话,如果稍后从数据库检索表单、更新表单和重新提交表单,则 servlet 可以更新数据库中相应的行,而不是生成新行。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/14751907/viewspace-434637/ ,如需转载,请注明出处,否则将追究法律责任。