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