7.14 动态XBean

支持动态XBean的Zdb数据库,允许增删,修改XBean字段的定义,无须在原先的Zdb数据库上执行数据格式转换,减少维护代价。详见《运行管理》数据转换一节。


  • 7.14.1 基本原理

    1. 动态XBean由静态字段和动态字段组成,静态字段的调整需要执行数据格式转换,动态字段的调整则不需要。

    2. 不论是静态字段,还是动态字段,在编程访问的方式上没有任何区别。

    3. 动态XBean将所有动态字段组织为一个Map,这个Map定义为动态数据,在编码上进行了扩展。编码规则为,首先按定义顺序编码所有静态字段,然后编码动态数据数量,随后编码动态数据条目。其中,动态数据条目的key为的serial,value为动态字段单独编码后获得的字节数组。

    4. serial是实现动态XBean的关键,serial在XBean上进行分配,任何一次修改调整必须递增serial。serial反映了XBean的修改历史。

    5. XBean中读出了当前运行代码不知道的serial,意味着该serial对应的字段需要删除,这样的serial对应的数据在解码过程中即被忽略。

    6. 新增动态字段,必然分配了所有已存储XBean都不知道的最新的serial,解码过程获得的XBean动态数据不可能更新这一字段的值,所以用户看到的就是构造XBean时初始化的默认值(除非字段描述中定义了script,详见后面的讨论)。

    7. 同一动态字段,如果需要修改定义,那么最新的定义具有最新的serial,之前的定义必须保留,这些定义反映了当前数据中的历史。设计上,允许提供脚本描述,定义历史serial向新serial的转换,解码过程中如果解码出历史serial,则调用转换过程,将字段转换为最新的版本,更新XBean对应字段,提供给用户访问,编码存储XBean时最新版本被写入数据库。


  • 7.14.2 动态XBean的描述

    • 描述样式


      <?xml version="1.0" encoding="utf-8"?>
      <zdb dynamic="true">
      	<xbean name="MyXbean" nextserial="1">
      		<variable name="var0" type="int" />
      		<variable name="var1" dynamic="true"> 
      			<variable serial="0" type="int"/> 
      		</variable>
      	</xbean>
      	<table name="mytable" key="long" value="MyXbean" autoIncrement="true" />
      </zdb>
      

      1. zdb元素dynamic属性决定了该zdb中允许动态XBean。一旦这样定义zdb,该zdb中所有的XBean都是动态的,不论是否定义了动态字段。

      2. xbean元素的nextserial属性,决定了下一次修订动态字段定义时应该使用的serial,这样的serial被使用之后,需要将nextserial加一。该属性用于代码生成时的检查,避免错误分配serial,导致运行时出现错误。nextserial缺省为0,没有定义动态字段XBean允许缺省。

      3. 字段var0为静态字段。

      4. 字段var1为动态字段,当前的serial为0,类型为int。


    • 增加动态字段

      例如,增加一个string类型的字段var2,添加一个描述片段即可


      		<variable name="var2" dynamic="true"> 
      			<variable serial="1" type="string"/> 
      		</variable>
      

      这里使用了xbean属性nextserial指定的serial 1,所以应该将nextserial修改为2。


    • 删除动态字段

      例如,删除var2,删除上述描述片段即可,xbean属性的nextserial不修改。


    • 修改动态字段

      例如,修改动态字段var1,新类型为double,值为之前的值乘以3.14。


      		<variable name="var1" dynamic="true"> 
      			<variable serial="0" type="int"/> 
      			<variable serial="2" type="double" script="$0 * 3.14"/> 
      		</variable>
      

      这里定义了var1的更新类型,serial为2,记得将xbean的nextserial更新为3。

      动态字段定义中,具有最大serial的元素描述的信息为该字段的当前描述信息,其它元素提供了历史描述信息。

      script属性可以填写一个表达式,这里的$0表示需要依赖serial="0"的历史值,动态字段的解码基本过程为:

      1. 如果最新版本serial对应的数据已经存在,直接解码即可。

      2. 如果不存在,根据script描述的依赖关系递归解析依赖值,计算结果作为当前动态字段的值。

      3. 递归解析过程中任何一个依赖值不存在,则使用字段构造时初始化的默认值。


      从当前的例子来看,如果读取的XBean动态数据中,存在serial="2"的值,直接解码,初始化var1,否则检查serial="0"的值,如果存在,则按int类型解码该值,乘以3.14,获得的结果初始化var1,如果serial="0"的值不存在,使用var1构造时的默认值,即0.0。


      历史描述信息没有被XBean定义范围内的任何script引用,意味着历史值可能被丢弃,生成代码时将产生警告。在这里,如果serial="2"对应的script没有定义,则会警告serial=”0”没有使用。


      script的设计原则为:

      1. 为了灵活性,允许使用$引用XBean中的任何字段,不限于当前动态字段的历史描述。

      2. $number 的形式引用了历史动态字段,其中number必须小于自己的serial,并且存在于当前XBean定义中,否则生成过程中报错。

      3. $word的形式引用了静态字段,word必须为有效的静态字段名,否则生成过程中报错。

      4. 生成代码只检查$引用的合法性,并不执行语法检查,所以生成之后应该检查生成代码是否有错,如果有错,修订script描述,重新生成。

      5. script只允许填写一个合法表达式, 如果需要执行复杂操作, 可以首先定义一个生成的XBean可见的静态函数, 执行转换。 例如script="mypkg.MyApp.transform($0)"

      6. 特别注意,一个动态字段最小serial元素中定义的script表达式计算结果并不能理解为该字段初始值。该值仅仅是解码XBean时使用的默认值,并不是构造XBean的默认值,这一点需要特别注意,通常最小serial元素中最好不要定义script属性,这样就能保证解码默认值与构造默认值一致。

      7. 修改动态字段,设计script的时候,正确区分解码默认值与构造默认值对于insert新记录时,如何正确填写相应字段的值非常重要。


  • 7.14.3 数据格式转换

    使用动态XBean可以减少修改Zdb描述带来的数据格式转换代价。至少有两种情况需要转换,其一,非动态XBean系统向动态XBean系统的转换;其二,Zdb描述修订太多次,开发人员感觉难以维护。

    数据格式转换的操作与《运行管理》数据转换一节,完全相同,应该首先熟悉操作过程。

    • 静态转换到动态


      <?xml version="1.0" encoding="utf-8"?>
      <zdb>
      	<xbean name="MyXbean">
      		<variable name="var0" type="int" />
      	</xbean>
      	<table name="mytable" key="long" value="MyXbean" autoIncrement="true" />
      </zdb>
      

      假设原本的Zdb这样的描述, 已经运行一段时间了。现在需要修订为前面例子定义的形式, 加入动态字段var1。修订Zdb描述之后生成代码, 运行程序。则会报告错误 "convert needed to cast to dynamic".


      java -cp ../limax/bin/limax.jar;bin limax.zdb.tool.DBTool -e "convert zdb zdbcov"
      

      当前开发环境下,参考上面的命令执行,生成转换代码。

      编译代码之后,再执行一次上述命令,(注意创建zdbcov目录),数据被转换到zdbcov中。

      备份zdb目录,zdbcov重命名为zdb,程序即可正常执行。

      特别注意,动态不可重新转换回静态。


    • 维护性转换

      Zdb描述中动态字段修订太多次,开发人员感觉难以维护,可以考虑生成代码执行维护性转换,维护性转换的实质就是将所有XBean读取存储一次,更新到最新版本,清除所有历史数据。继而可以清理Zdb描述,删除所有动态字段的历史描述,只保留serial最大的元素。


      java -cp ../limax/bin/limax.jar;bin limax.zdb.tool.DBTool -e "convert zdb zdbcov"
      

      执行维护性转换同样需要运行这样的命令,结果是为所有以XBean为value的table生成转换代码,编译代码后重复运行上述命令执行转换。


      这里需要注意,即便XBean没有描述任何动态字段,也会生成转换代码,因为转换工具不能确定动态字段从来都不存在,还是仅仅在当前的描述中被删除了。所以,开发人员可以根据实际情况清理相应的生成代码,基本原则有两条,其一,确实从来没有定义动态字段,其二,动态字段一直只有一条定义,从来没有修改过,能够确信数据库中没有历史信息。最简单的策略是根本不清理,转换时间长一些而已。


      如果静态字段的定义被修改了,运行程序自然会报告需要转换,这种情况参考《运行管理》数据转换一节即可。


  • 7.14.4 动态还是静态的讨论

    • 基本比较

      动态 静态
      性能 编解码过程较复杂,性能较差
      修改描述的影响 影响小,动态字段的修订,无需执行数据转换 影响大,每次修改都需要转换
      安全性 低,动态字段的修订必须非常小心,数据库上执行错误的代码将会造成不可挽回的影响。 高,既然每次修改都需要转换,转换的源可以作为备份,一旦出现问题容易挽回。

    • 修改描述的讨论

      1. 数据库设计必须仔细斟酌,不到万不得已不应该修改。

      2. 动态方式不是随意修改设计的理由,通常动态系统都会教坏设计者。

      3. 通常的修改都是需求变化带来的,讨论好需求,预期一些可能的变化,事先准备,是比较好的方式。


    • 推荐的方式

      1. 项目开始投入使用,推荐使用静态方式。

      2. 维护时,使用转换工具转换数据库,备份原始库,是非常安全的方式。

      3. 随着记录数量的增加,转换时间快要超出容忍限度的时候,可以考虑转换为动态方式,降低之后的转换频率。

      4. 一旦使用动态方式需要特别小心,应该在备份的数据上,执行严格的测试,再应用于实际系统,避免造成运营事故。


上一页 下一页