基于对象模型的代码模板技术与数据表对象模型
张博
2017.11.01
- 小众需求
存在这样一部份工作,单就技术而言并不难,随便找个人就能写,但是相似的工作很多,一个一个写很累人。典型的是对很多数据库表的处理,聪明的办法当然是自动化,根据数据字典分析,然后构造SQL语句,但是有时候这种办法不令人满意,比如不够高效,比如代码晦涩难懂,难以发现错误,难以修改——LocalCache最初正是用这种方法实现的,初次完成当然成就感巨大,然而后续修改却令人抓狂,因此催生了现在这个基于对象模型的代码模板技术。
- 这是什么
这是一个用代码模板生成代码的技术,代码模板类似asp或者jsp,通过嵌入的控制代码来生成目标文档。
对象模型是可以自由定义的(C++),代码模板也是任意的,目标文档可以是任何文本,意即,虽然对象模型和处理程序都是C++语言的,但可以用来生成任何文本:C++、Java、SQL甚至纯粹的名单。
不排除未来将对象模型存储在数据库的可能,不过目前,目标用户是程序员。
- 第一个示例
这个例子包含一个很简单的对象模型和一个很简单的模板。
对象模型:
table name T1
comment 这是一张表
columns
id long
name varchar 32
模板:
//${table.name} ${table.comment}
Struct ${table.name}
{
<%foreach col in column%>
${col.name} ${col.type};
<%end%>
}
输出文件:
//T1 这是一张表
Struct T1
{
Id long;
Name varchar 32;
}
注意其中的${}部分被替换为对象模型中的对应内容,<%foreach%>则控制对对象模型中的数组做了循环处理。
- 对象模型(需要用C++编写)
对象分为三类:
属性——仅仅包含一个值
数组——仅仅包含一个对象数组
对象——包含一组带有名字的对象
这是很常见的对象模型,通过对象包含对象可以构建一个无限大的世界。
比如对表格,作为一个对象可以包含这些内容:
Name 对象是一个属性
Comment 对象是一个属性
Columns 对象是一个列的数组
Indexs 对象是一个索引的数组
而列对象,可以包含这些内容:
Name 对象是一个属性
Comment 对象是一个属性
Type 列的类型可能是一个简单属性也可能是一个对象
- 代码模板(嵌入式脚本语言)
- 全局对象和栈
全局对象由对象模型构建而来,同时有一个栈用来处理局部对象,foreach和if块内部定义的对象为临时对象,块结束即销毁。
-
- ${}引用对象
${点分隔的对象名}语法引用一个对象并用对象的默认值替换自身所在的位置。
查找对象的顺序符合一般规律。(即先局部后全局)
属性对象的默认值就是属性的值。
对象的默认值是name属性的值。
数组对象不应该在这里出现。
如果对象是数组,可以用[下标]引用数组的一个对象,下标从0开始。
-
- <%foreach%>循环控制
<%foreach 变量名 in 对象>
<%end%>
对象必须是数组,对数组的每个对象执行一次
-
- <%if%>分支控制
<%if 对象 exist%>
<%else%>
<endif>
如果对象存在则执行……否则……
此判断只判断对象是否存在,有没有值无关紧要。一般在构建对象模型的时候可以将特殊的属性都生成出来,没必要在代码模板里面做复杂分析。
<%if 对象 equal 对象%>
判断两个对象的默认值是否相等,第二个对象可以使用双引号包裹的字符串。
不排除增加数值判断的可能。
-
- <%dim%>定义对象
<%dim 对象名=对象%>
定义对象,用来简化代码,可以使用双引号包裹的字符串。
-
- <%set%>设置对象
<%set 对象名=对象%>
设置对象,可以使用双引号包裹的字符串。
-
- 不常用语法
- Foreach的stepto
- 不常用语法
<%foreach 变量名 stepto 对象%>
<%foreach 变量名 stepto 对象 连接符%>
此循环的变量名对应的对象不是数组的一个元素,而是从第一个开始到这个的一个新数组,举例比较容易理解:
对象数组包含三个元素,那么三次循环的对象数组是:
1
1,2
1,2,3
这种语法可以用于对一个复合索引生成部分查找代码。
循环变量的name是每个元素的默认值的连接。
可选的连接符用来在生成循环变量的默认值时增加连接符。
-
-
- 后++
-
${对象++}可以在使用完对象后将对象的内容加一。某些场合需要递增对象时可用。
- 典型案例
LocalCache系统是第一个使用案例,实际上,这个系统就是LocalCache系统的一部分。
LocalCache系统包括若干组基于C++的共享内存结构和完整的JNI操作接口,每组数据包含若干个表,每个表包含若干个索引,每个索引包含若干个列,每个索引都可以做部分搜索。
包括若干个模板文件,代码不到1000行,生成的代码超过2万行。
- 数据表对象模型
- 对象模型
- 根对象
- 对象模型
名称 | 类型 | 说明 |
name | 属性 | 总的名称 |
sys | 属性 | JNI动态库名称和Java类名称 |
packaeg | 属性 | JNI Java包 |
firstPI | 属性 | 共享内存专用 |
PII | 属性 | 共享内存专用 |
tables | 数组 | 一组表对象 |
-
-
- 表对象
-
名称 | 类型 | 说明 |
name | 属性 | 名称 |
comment | 属性 | 说明 |
mutex | 属性 | 此属性存在则使用内置互斥 |
members | 数组 | 一组列 |
indexs | 数组 | 一组索引 |
functions | 数组 | 一组附加函数 |
lists | 数组 | 一组附加列表 |
-
-
- 附加列表对象
-
名称 | 类型 | 说明 |
name | 属性 | 名称 |
comment | 属性 | 说明 |
members | 数组 | 一组列(全部列,包含比较列和非比较列) |
key_members | 数组 | 一组比较列(即主键) |
functions | 数组 | 一组附加函数 |
-
-
- 附加函数对象
-
名称 | 类型 | 说明 |
name | 属性 | 名称 |
comment | 属性 | 说明 |
type | 属性 | 函数类型,由代码模板处理 |
no_index_col | 属性 | 若此属性存在则此函数不涉及索引列,仅用于主数据的附加函数,附加列表一律不允许修改比较列 |
members | 数组 | 一组列,由代码模板处理 |
-
-
- 索引对象
-
名称 | 类型 | 说明 |
name | 属性 | 名称,第一个索引将用作主键 |
members | 数组 | 一组列,按照顺序比较,允许部分查找 |
-
-
- 列对象
-
名称 | 类型 | 说明 |
name | 属性 | 名称 |
comment | 属性 | 说明 |
type | 属性 | C++数据类型,包括sstring和不同长度的整数 |
ParamType | 属性 | C++参数类型,用作函数参数时使用 |
JavaType | 属性 | Java数据类型 |
….. | 属性 | 其它一些代码中需要根据数据类型使用的属性,提前设置好,避免脚本中判断 |
-
- 使用对象模型
- 示例
- 使用对象模型
-
-
- 添加表
-
CCTModel_table createcode;
CCTModel_table::table * p;
p = createcode.AddNewTable("Table", "用于测试的表,不可删除", true);
函数:
table * AddNewTable(char const * name, char const * comment, bool mutex)
参数:
Name 表的名称,必须符合目标代码语言的规范,如果用于JNI则不能使用下划线
Comment 注释,能否多行取决于代码模板如何处理
Mutex 是否内置互斥,内置互斥为支持多进程和多线程的用于表、索引和附加列表的单一写优先读写锁,可以确保索引的一致性,如果有意见可以不使用内置互斥而自行处理互斥。
-
-
- 添加表的列
-
p->AddMember("A", "str", 8, "A字符串");
函数:
bool AddMember(char const * name, char const *type, long len, char const * comment)
参数:
Name 列名
Type 类型,str或int,根据长度不同实现为不同类型
Len 长度,对于str实现为sstring<len+1 >,对于int则分别实现为long int short char
Comment 注释
-
-
- 添加索引
-
p->AddIndex("IndexBA", "B,A");
函数:
bool AddIndex(char const * name, char const *members)
参数:
Name 索引名称
Members 索引的列,多个列用逗号分隔
说明:
必须先添加所有索引,然后才能添加附加函数,因为附加函数要根据索引情况采用不同的修改数据方法,不涉及索引的数据修改可以直接进行,涉及索引的必须同步修改索引。
-
-
- 添加附加函数
-
p->AddFunction("SetXYZ", "set", "X,Y,Z", "更新XYZ");
函数:
bool AddFunction(char const * name, char const * type, char const *members,char const * comment)
参数:
Name 函数名称,函数名称在实现中会根据需要扩展,附加表名或其它信息,比如JNI接口需要”Java_包名_类名_”开头。
Type 附加函数的类型,具体由代码模板解释,比如是累加还是修改。后续章节专门解释。
Memebers,函数操作的列,具体由代码模板解释。
Comment注释
-
-
- 附加列表
- 添加附加列表
- 附加列表
-
CCTModel_table::listdata * list;
list = p->AddList("L1", "第一个附加列表");
函数:
listdata * AddList(char const * name, char const * comment)
参数:
Name 名称
Comment 注释
-
-
-
- 添加附加列表的列
-
-
list->AddMember("L1a", "str", 8, false, "");
函数:
bool AddMember(char const * name, char const *type, long len, bool iskey, char const * comment)
参数:
isKey 是否是比较列,附加列表可以是允许重复的也可以是不允许重复的,有相应的函数操作,如果混合了不同类型的函数,可能得到错误的结果。
其它参数与表的添加列函数相同。
-
-
-
- 添加附加列表的附加函数
-
-
list->AddFunction("Add", "add", "X", "累加X");
函数:
bool AddFunction(char const * name, char const * type, char const *members, char const * comment)
参数与表的添加附加函数相同,但是,附加列表的附加函数的操作列不允许是比较列。
-
- 标准函数
系统管理类,如创建、删除。
主键操作,如查找、插入、修改、删除。
索引操作,包括遍历、部分查找,部分查找从第一个索引列递增到全部索引列。
-
- 附加函数
- set
- 附加函数
类型为set的附加函数修改主数据或附加列表数据。
-
-
- add
-
类型为add的附加函数累加主数据或附加列表数据。
(完)