科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道使用 DB2 pureXML 实现动态模型定制

使用 DB2 pureXML 实现动态模型定制

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

DB2 V9.5(VIPER II)相比较 DB2 V9 进一步增强了对 XML 操作的支持,提供了更加强大的功能和更加优异的性能。

作者:builder.com.cn 来源:来源网站 2007年11月23日

关键字: DB2

  • 评论
  • 分享微博
  • 分享邮件
随着软件逐渐渗透应用到各行业的核心领域,行业的业务复杂性给软件开发者在建模时带来了前所未有的挑战。DB2 pureXML 的灵活性赋予开发者一种新思路,将业务模型转交给最终用户来管理,从而降低行业知识带来的专业门槛。
声明
IBM 并不对本文中提出的新编程方法提供任何保证。

简介

即将发布的 DB2 V9.5(VIPER II)相比较 DB2 V9 进一步增强了对 XML 操作的支持,提供了更加强大的功能和更加优异的性能。可以对 XML 数据做更加精细化的加工处理。

下面将介绍如何利用 DB2 V9.5 的 pureXML 特性来对 XML 和 XML Schema 数据进行管理,从而借助 DB2 pureXML 的能力来实现用户自由定制模板的功能,满足动态模型定制的需求。

在该场景中使用 XML 的功能主要包含以下方面:

  • 利用 DB2 pureXML 的节点更新功能对 XML Schema 文档的元素进行即时添加、删除、修改;
  • 根据 XML Schema 描述的结构动态展现录入界面;
  • 根据动态录入界面的内容构造 XML 文档并保存;
  • 利用 XSLT 展示 XML 文档中的内容;




回页首


模型定制的必要性

每个行业都有自己的行话,即业务知识,过去的思路是让软件工程师来理解这些行话,然后处理其中的业务模型和逻辑。这些行话可能异常复杂,导致软件工程师无法正确把握里面的正确内涵,设计出来的软件不符合最终用户的需求初衷。例如在医疗行业中,有体格检查,病例,病程,病案等,其中每一项的内容和结构都极其复杂且灵活,即使是医疗行业的资深软件开发工程师,也很难完全掌握其中的细节。

从另一方面,由于业务模型的复杂性和多样化,而软件开发商的需求通常是通过采样分析获取的,并不能完全覆盖所有最终用户的业务需求。因此在开发软件的时候,抹煞了需求的多样性,导致了开发出来的软件不能完全满足最终用户的独特需求。最终用户迫切需要获得主动权,来对业务模型进行定制。例如在医疗行业,就病例模型而言,每个医生都有自己习惯的个性化书写习惯;而软件开发商则经过对部分医生的调研,设定了若干通用的书写方式,无法满足不同科室医生的个性输入要求。医生期待软件开发商提供更加灵活的方式,允许医生自主定义病例模型。

因此,实现模型定制对软件开发商和最终用户都有积极的意义,一方面使得软件开发商更加集中经历关注软件质量,降低其对不同行业的入门门槛;另一方面,最终用户不再受制于软件,可以更加充分的发挥其主动性,使得软件真正成为解决问题的帮手。

下面,针对医疗行业的病例模型,我们将举例说明,如何使用 pureXML 来完成病例模型的定制。





回页首


模型定制的需求

数据模型抽象地描述了数据的结构、展现和使用方法,以及数据的内部完整性约束。传统意义上,这些内容都是封闭在软件内部,并由软件开发商以某种表现形式展现到最终用户面前。对最终用户而言,这些数据模型的内容,结构和展现形式都是固化的。

要允许最终用户定制数据模型,必须提供以下功能:

  1. 允许定义模型的元素,包括其名称,类别等信息;在本案例中,即病例的条目,如病史,主诉,体格检查等;
  2. 允许定义模型的结构,即模型元素的前后顺序,父子关系等,如体格检查包含头部检查,胸部检查,腹部检查等;
  3. 允许定义模型元素的约束条件,包含取值范围约束,数值大小约束,字符长度约束,字符模式约束等;如血压值取值范围,联系人电子邮件模式等;

通过提供以上的功能,基本可以满足最终用户对数据模型定制化的需求。





回页首


如何使用 XML 描述数据模型

在传统意义上,数据模型即为数据库的表结构,系统设计师要考虑业务逻辑,然后利用数据库理论将业务逻辑抽象出来,拆分为若干数据表;越是复杂的业务逻辑,抽象出来的数据表以及之间的关联关系也越复杂,千丝万缕的关联关系给数据库设计和维护都带来了麻烦。而特别地,当复杂的数据模型处于容易发生变化的场景下,更是给数据建模工作带来了极大的挑战。

DB2 pureXML 考虑到了这种复杂性,在数据库层面对 XML 提供了更强有力的支持。从而减轻数据建模的复杂度。

XML 作为自我描述的可扩展标记语言,无疑是用来描述数据模型的首选载体。首先 XML 文档内部即为可无穷嵌套的元素,可用以满足复杂逻辑结构的构建;且 XML 规范为 XML 元素提供了丰富的数据类型,可以满足普通数据模型的类型需求; XML 元素本身在文档中就是按照顺序排列的,这种特性则满足了数据模型对于数据元素之间逻辑顺序的要求。

用来描述 XML 逻辑结构的 XML Schema 则成为了描述数据模型的最佳选择。除了定义数据元素,数据元素的数据类型,数据元素的顺序关系之外,XML Schema 同时提供了更加丰富的限制约束,允许定义元素的枚举范围,数值元素的小数位数,总位数,最大值,最小值等,对于特殊需求,还可以用正则表达式定义字符元素的模式。

依照前面举的病例的例子,下面的 XML 文档片段可用于描述该病例模型:


清单 1. 用于描述病例模型的 XML
                
<?xml version="1.0" encoding="UTF-8"?>
<病例>
<病史>
    <现病史 />
    <过去史 />
    <个人史 />
  </病史>
  <主诉 />
  <体格检查>
    <头部检查 />
    <胸部检查 />
    <腹部检查 />
  </体格检查>
</病例>

可见,使用 XML 可以很自然清晰的表达模型的内容,以及互相之间的结构关系。不管模型包含多少内容,结构如何复杂,通过 XML 都可以轻松的描述。

另一方面,XML 的结构可以使用 XML Schema 来描述,包括如上所述的模型内容,模型结构,还可以定义各个模型的数据类型,例如数值型,字符型,时间日期型等。给模型定义者充分的自由度。

下面给出描述上述模型结构的 XML Schema 片段:


清单 2. 病例模型 XML Schema 片段
                
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://www.ibm.com/NewXMLSchema"
  xmlns:tns="http://www.ibm.com/NewXMLSchema"
  elementFormDefault="qualified">

  <element name="病例">
    <complexType>
      <sequence>
        <element name="病史">
          <complexType>
            <sequence>
              <element name="现病史" type="string"></element>
              <element name="过去史" type="string"></element>
              <element name="个人史" type="string"></element>
            </sequence>
          </complexType>
        </element>
        <element name="主诉" type="string"></element>
        <element name="体格检查">
          <complexType>
            <sequence>
              <element name="头部检查" type="string"></element>
              <element name="胸部检查" type="string"></element>
              <element name="腹部检查" type="string"></element>
              <element name="颈部检查" type="string"></element>
            </sequence>
          </complexType>
        </element>
      </sequence>
    </complexType>
  </element>
</schema>





回页首


如何使用 DB2 pureXML 管理模型

前面谈到了 XML 可以用来简便的描述模型,也谈到了使用 Schema 来描述 XML 模型结构的便利性。这些模型以及模型结构可以分别以 XML 形式和 XML Schema 形式存储到数据库中,并有效的组织管理起来。由于 XML Schema 本身也是 XML,因此,它们均可保存在 DB2 的 XML 字段中。

下面是采用 DB2 管理模型的一个架构示意图。


DB2 管理模型架构示意图

图 1. DB2 管理模型架构示意图

  1. 开发商根据最初采集到的需求预定义若干模型,并将这些模型定义存储在 DB2 pureXML 中;
  2. 随着业务的发展,最终用户根据自身的需要来定义模型,并将这些模型定义存储在 DB2 pureXML 中;
  3. 程序从 DB2 数据库中获取出来模型定义,并解析这些模型,提供出来可供最终用户录入模型数据的视图,以及修改模型数据的视图,并根据模型定义的约束进行输入有效性验证;
  4. 用户通过该视图提交表单,程序将解析表单的输入内容,并将其组装成为符合模型定义的 XML 文档,从而存储到 DB2 数据库中;
  5. 程序读取出来用户存储的模型,展现到视图上。

从开发商的角度来讲,由于专注在软件开发领域,不可能完全洞悉客户的所有需求,在建模上只需要根据最初和客户洽谈下来的需求定义若干预定义模型,然后提交给客户进行维护,避免了因为客户需求变更或者需求理解偏差而产生的大量修改工作。从而避免了很大的风险。当产品成型之后,事实上,对不同客户的开发工作转移到不同的模型的定制上来了。

反过来,从客户的角度来讲,业务是掌握在自己的手中,碰到业务的扩展,制度的变更等再也不需要请开发商来重新开发,或者修改原有系统。只需要自己定义新的模型,或者扩展原有的模型,就可以满足新的需求。从而最大程度的减少了成本。

在具体的实现层面,关键点在于:

  1. 为最终用户提供友好的模型定制视图;
  2. 根据模型定义展现视图,使得用户可以输入,并辅助进行校验;
  3. 读取用户输入,并组装成为 XML,并存入 DB2。

下面以 Web 方式提供一个实现的个案,开发者可以根据自身的条件和需求,以任何方式实现,实现的方法都是相通的。

为最终用户提供友好的模型定制视图

为了定义模型,模型元素需要可以随意的扩展增加,并可随意添加子元素。由于模型定义最终存储为 DB2 中的 XML Schema,程序中只需要借助 DB2 pureXML 的 xmlquery() 函数读取 DB2 中 XML Schema 的根节点下的顶级子节点,并以表格的形式展现出来。


清单 3. 读取 XML Schema 并以表展现出来
                
SELECT t.*,xmlquery('declare namespace xsd="http://www.w3.org/2001/XMLSchema"; 
  $doc/xsd:schema/xsd:complexType/xsd:sequence/xsd:element' 
  passing schema_info as "doc") 
  as schema_name FROM template t where record_id=?

用户就可以对该表格的各行(即各元素)进行重新命名,定义其元素类型,增加元素约束,添加新元素等。这些操作都可以通过 DB2 pureXML 的节点更新功能来完成目标。关于 XML 节点更新的语法,可以参考 参考资源


清单 4. 通过更新 XML Schema 来更新模型
                
update template set schema_info = xmlquery(
'declare namespace xsd="http://www.w3.org/2001/XMLSchema";
transform copy $new:=$i modify do insert 
<xsd:element name="newElement">
<xsd:simpleType>
<xsd:restriction base="xsd:integer">
 <xs:minInclusive value="0"/>
 <xs:maxInclusive value="100"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
 as last into $new/xsd:schema/xsd:element/xsd:complexType/xsd:sequence
 return $new'
passing schema_info as "i")
 where XMLEXISTS('declare namespace 
 xsd="http://www.w3.org/2001/XMLSchema";$doc/xsd:schema/xsd:element[@name=$parentName]'
PASSING schema_info AS "doc", “parentName” as $parentName);


用户也可以通过点击某行,进入该元素的下一个级子元素列表,定义下一级子元素的模型,从而递归进行操作,达到模型定制的基本要求。

根据模型定义展现视图获取用户输入并进行校验

由于 XML 模型本身的结构是动态的,因此在展现该模型的时候也应该采用动态的方法才能满足要求。

这里使用了 XSLT 来处理最通用的情形,当然,开发者可以根据自身的模型特点,编写不同的 XSLT 转换样式,使得展现的更加人性化。除了可以利用 Java API javax.xml.transform.Transformer 类的 transform() 方法来实现 XSLT 转换之外,DB2 pureXML 也提供了转换函数 XSLTransform(xmldoc USING xsltdoc),方便用户使用。

该样式表取得 /xsd:schema/xsd:element/xsd:complexType/xsd:sequence/xsd:element 列表,并递归应用样式,对不同的约束条件,展现不同的输入界面。

如对于 <xsd:enumeration> 的约束限制,则显示为下拉框,供最终用户选择输入;

对于 <xsd:minInclusive><xsd:maxInclusive> 等,则在 <input> 元素增加属性 t_min,t_max 等,并在提交表单时检查这些属性和输入值的关系,验证输入值是否满足约束。


清单 5. XSL 示例
                
<?xml version="1.0" encoding="gb2312"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsl:output method="html" encoding="gb2312" />
  <xsl:template match="/">
    <xsl:apply-templates
      select="/xsd:schema/xsd:element/xsd:complexType/xsd:sequence/xsd:element">
      <xsl:with-param name="level" select="'1'" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="xsd:element">
    <xsl:param name="level" />
    <table border='0' width="100%" id="faqLev1"
      style="background:#FFFFFF;margin-left:16px;float:none;
      margin-bottom:10px;DISPLAY: block;">
    
      <TR><TD height="25">
      <div id="openlev" style="cursor:hand;display: block" 
        onclick="javascript:toggleLev2Div(this)">+Toggle</div>
      </TD></TR>

      <tr style="DISPLAY: none">
        <td width="100%">
          <b><xsl:value-of select="@name" /></b>

          <xsl:for-each select=".">
            <xsl:choose>
              <xsl:when test="xsd:restriction">
                <xsl:for-each
                  select="./xsd:restriction">
                  <xsl:choose select=".">
                    <xsl:when
                      test="xsd:enumeration">
                      <xsl:element
                        name="select">
                        <xsl:attribute
                          name="name">
                          <xsl:value-of
                            select="../@name" />
                        </xsl:attribute>
                        <xsl:attribute
                          name="t_level">
                          <xsl:value-of
                            select="$level" />
                        </xsl:attribute>
                        <xsl:for-each
                          select="./xsd:enumeration">
                          <xsl:element
                            name="option">
                            <xsl:attribute
                              name="value">
                              <xsl:value-of
                                select="@value" />
                            </xsl:attribute>
                            <xsl:value-of
                              select="@value" />
                          </xsl:element>
                        </xsl:for-each>

                      </xsl:element>
                    </xsl:when>
                    <xsl:otherwise>
                      <input type="text"
                        value="">
                        <xsl:attribute
                          name="id">
                          <xsl:value-of
                            select="../@name" />
                        </xsl:attribute>

                        <xsl:attribute
                          name="name">
                          <xsl:value-of
                            select="../@name" />
                        </xsl:attribute>
                        <xsl:attribute
                          name="t_level">
                          <xsl:value-of
                            select="$level" />
                        </xsl:attribute>
                        <xsl:if
                          test="xsd:minInclusive">
                          <xsl:attribute
                            name="t_min">
                            <xsl:value-of
                              select="xsd:minInclusive/@value" />
                          </xsl:attribute>
                        </xsl:if>
                        <xsl:if
                          test="xsd:maxInclusive">
                          <xsl:attribute
                            name="t_max">
                            <xsl:value-of
                              select="xsd:maxInclusive/@value" />
                          </xsl:attribute>
                        </xsl:if>
                      </input>
                    </xsl:otherwise>
                  </xsl:choose>
                </xsl:for-each>
              </xsl:when>
              <xsl:otherwise>
                <input type="text" value="">
                  <xsl:attribute name="id">
                    <xsl:value-of select="./@name" />
                  </xsl:attribute>

                  <xsl:attribute name="name">
                    <xsl:value-of select="./@name" />
                  </xsl:attribute>

                  <xsl:attribute name="t_level">
                    <xsl:value-of select="$level" />
                  </xsl:attribute>
                </input>
              </xsl:otherwise>
            </xsl:choose>

          </xsl:for-each>
          <xsl:for-each select="@type">
            <xsl:if test=".='xsd:string'">(Input String)</xsl:if>
            <xsl:if test=".='xsd:double'">(Input Numeric)</xsl:if>
            <xsl:if test=".='xsd:date'">
              (Input date : yyyy-MM-dd)
            </xsl:if>
          </xsl:for-each>

          <xsl:apply-templates select="xsd:element">
            <xsl:with-param name="level" select="$level +1" />
          </xsl:apply-templates>
        </td>
      </tr>
    </table>

  </xsl:template>
</xsl:stylesheet>

关于 XSLT 的写法,可以参考文章。

将用户输入组装成 XML 模型并存储入库

由于模型的录入界面本身就是根据 Schema 的定义,按照定义的顺序依次排列出来模型元素的。同时,还为每个模型元素的录入元素设定了一个属性 t_level,因此,通过以下的 javascript 即可以实现将各个输入元素组装成为 XML。

在页面上,定义一个 <input name=”xmlarea” type=”hidden”>, 用以将组装的 XML 内容提交给表单。从而传递给后台应用程序进行处理,并入库。


清单 6. 组装 XML
                
function getXMLFragment(){
  var stack = new Array(); 
  var inputs=frm.elements;
  var lastElement=null;
  var xml="";
  var element;
  for(var i=0;i<inputs.length;i++){
      element = inputs[i];
      if(element.t_level==undefined)continue;
      // 同级别的元素 , 将上个元素结尾
      if(lastElement!=null && element.t_level==lastElement.t_level){
        xml=xml+"</"+lastElement.name+">";
      }else if(lastElement!=null && element.t_level>lastElement.t_level){
      // 碰到子元素,将父元素压栈
          stack.push(lastElement);
      }else if(lastElement!=null && element.t_level<lastElement.t_level){
      // 碰到更高级元素,将同级别的父元素出栈
        xml=xml+"</"+lastElement.name+">";
        var startTag;
        do{
          startTag=stack.pop();
          if(startTag!=undefined){
            xml=xml+"</"+startTag.name+">";
          }
        }while(startTag!=undefined&& startTag.t_level<=lastElement.t_level);
      }
      xml=xml+"<"+element.name+" name=\""+element.name+"\" value=\""+element.value+"\">";
      lastElement=element;
    }
    xml=xml+"</"+lastElement.name+">";
    for(var i=stack.length-1;i>=0;i--){
      xml=xml+"</"+stack[i].name+">";
    }
    document.getElementById("xmlarea").value=xml;
}





回页首


模型的进化

由于把模型交给最终用户手里动态调整,模型本身就存在这进化的过程。例如当模型元素原先设定为长整型 (xs:long) 类型,后来发现该元素还需要可以容纳负数 xs:negativeInteger 的值,此时,模型结构的定义就需要做出相应的调整,这就是模型进化的一个情形。同样的,为了适应需求,模型结构可能发生别的变化,比如增加一些新元素,增加新的约束条件等。

对于向前兼容的模型进化,可以在原有的模型上进行修改,比如上述的类型变化,可以通过模型定制界面修改模型元素的类型为其共同的父类型 xs:integer,或者新增加模型元素。这样可以不影响既有的模型实例,又能适应进化后的模型。

关于模型元素的类型,可以参考 参考资源

对于向前不兼容的模型进化,例如删除某模型元素,增加约束条件,修改模型元素名称等,则需要对模型做特殊处理。可以将进化前的模型复制到新模型,赋予新的版本,然后基于新版本的模型进行修改,之后的模型实例都基于该新版本模型生成,从而达到模型进化的目的。





回页首


一些担忧和对策

模型定制减轻了开发者的负担,但是模型的不断进化给模型的管理带来了挑战。如果没有一定的约束,那么,日积月累,模型的数量将会越来越多,最后导致无法管理。

解决这类问题的办法有两种:

其一,将模型定制的权限限制在一个较小的范围,例如开放给管理员,由客户指派的管理员专门进行模型定制,对模型的修改需要通过相应的授权。这样就可以在制度上解决这个问题;

其二,对于模型元素,提供元素字典,定制任何模型都必须从该字典中选取元素。,这样,可以给最终用户更加粗粒度的定制方式,减轻了模型混乱的可能性。

另外,提供基本的模型给用户,让客户在这个模型基础上进行扩展,也是比较好的方式之一。

在性能上,由于模型本身的不确定性,系统无法在最初就在 XML 上创建相应的索引来提高查询效率。因此在让用户定义模型的时候,可以给用户选项,是否优化某模型元素的查询性能,从而确定在哪个元素上添加索引。

模型的安全性也是需要考虑的要素之一,在模型定制的过程中,应该尽量采用参数化形式操作,例如之前的模型元素增加,不采用直接 SQL 拼接的方式,而使用参数化的形式进行节点更新。从而避免恶意的语句危及数据库的安全。


清单 7. 采用参数化形式进行节点更新
                
update template set schema_info = xmlquery(
'declare namespace xsd="http://www.w3.org/2001/XMLSchema";
transform copy $new:=$i modify do insert 
$newpart
 as last into $new/xsd:schema/xsd:element/xsd:complexType/xsd:sequence
 return $new'
passing schema_info as "i")
 where XMLEXISTS('declare namespace 
 xsd="http://www.w3.org/2001/XMLSchema";$doc/xsd:schema/xsd:element[@name=$parentName]'
PASSING schema_info AS "doc", “parentName” as $parentName, ? as “newpart”);
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章