各式结构化数据的动态接入存储查询,这一需求相信有很多人都遇到过,随着实现技术路线选择的不同,遇到的问题出入大了,其解决办法也是大相径庭。数据存储在哪儿,是关系型数据库,还是NoSQL数据库,是MySQL还是Oracle,怎么建立索引,建立什么类型的索引,都是大学问。下面,我要把我对这一解决办法的思考总结一下,有成熟的也有不成熟的,希望大家一起共同探讨。
关键词:
结构化数据,
动态,
接入, 存储, 查询
首先,我们得定义一下在本文中什么是结构化数据,这里的结构化数据主要是指扁平化的、可以由基础数据类型组合成的数据,例如:
{"data":{"name":"wang anqi","age":28,"money":1653.35,"XX_date":"2013-12-3"}
},它们是可以很容易
被
存入关系型数据库的,我们要讨论的就是这种数据。对应的,
非结构化数据这里是指那些需要存储在文件系统中的,不是扁平化的数据。
那么,什么又是“各式结构化数据”呢,在本文中?这是一个数据集合,有可能集合中的每一条数据结构都是不尽相同的,例如:
{"data":{"name":"wang anqi","age":28,"money":1653.35,"XX_date":"2013-12-3"}
},和
{"angel":{"address":"清凉山公园","user":289770363}
}同时存在于一个数据集合中,它们结构不同,简单地说:第一条数据有四个属性,第二条数据只有两个属性,属性名称和类型都不一样。“各式”
包括了不定数量的
属性
,不定的
属性名称、
不定的
数据
类型。
解释清楚名词了,再解释一下动词:“动态接入”。在普遍情境下,你只会遇到将固定数据结构的数据存储入库,这里的入库主要还是指MySQL一类的关系型数据库。那么你可以选择使用Hibernate等ORM工具或不使用,来进行数据的存储读取,在使用ORM工具的情况下,要首先定义好数据的数据结构,写死在xml里或是java代码里。
一般情况下,你是不会遇到这样的需求的:对于不能事先确定数据结构的数据,我要把它们存储到关系型数据库中,并提供“合法性检验”、“更新”、“查询”等基本数据操作。要说的是,如果要把它们存储到HBase这种NoSQL数据库中,那是再好不过的了,配合着HBase与Solr的集成(详见之前的博客:
大数据架构-使用HBase和Solr将存储与索引放在不同的机器上
),搜索也不是件难事,唯一可能出现的难点在于:Solr对于Schema中filedName的配置,因为结构是动态的,所以fildName也是动态的,这其实也是很好处理的,有位微软的同学已经跟我咨询过这个问题了;事实上,这样的例子是很常见的。
但是往往,事与愿违,很有可能存在着其它的约束条件制约着你:必须使用关系型数据库,那么一整套解决办法是需要设计的。因为当你使用Hibernate时,你不能再把一个数据结构写死在代码里,因为它不是固定的,你该如何入库,该如何查询数据,这都是问题。
要处理好“各式结构化数据动态接入管理
”,应该分成以下几步:一、定义数据;二、动态管理;三、数据接入;四、数据查询。
一、定义数据
假如相关业务单位提供的有这么一类数据,这里用一条数据举例来说明:产品A 产品序列号为:A8815001 生产日期为:2013/12/09 13:33:33 供应商为:阿里巴巴 产品质量为:509g 是否合格为:是。我们可以从中大致看出各字段的数据类型,通过面向对象的方法定义出这么一个产品A类也很容易。现在我们要把这条数据存储到关系型数据库中,还是需要一引起先前处理的。
定义数据是基础,无论你的数据结构再怎么不固定,它也应该有个名字,它的属性也应该是由基础数据类型构成的。基础数据类型就像是构成世界万物的基本元素Ka、Ca、H、O等(请原谅我的灵感来自于《绝命毒师》),它们肯定是要事先定义好的,我们数据库中能够接入的数据必须由这些组成,如:
String
、
Integer
、
Long
、
BigDecimal
、
Boolean
、
Calendar
、
Date
、
Time
、
Blob
等,
Time
是以什么样的格式存在的,18:09:32还是18:9:32.3?
Date
是以
什么样的格式存在的,2000-10-8 18:09:32还是2000/10/8 18:9:32.3?
因此基础数据类型,需要作为一项很重要的业务约定,同各个“干系人”共享。就是说,在这里,我定义的基础数据类型,并不是我一个人说的算了的,而是要同相关业务单位开会沟通,这些基础数据类型是否可以囊括它们提供的数据结构的所有内容。毕竟,要接入的数据类型是由他们提供的,他们事先也需要把各种数据类型定义好,比如上面的产品A的结构,都要以文档
《产品A数据结构定义》
的形式记录下来。
在这一步需要产生一个文档,《基础数据定义文档》。
光是有基础数据类型的概念还远远不够,可能在产品A的数据结构文档中,还定义了某一属性值是否可为空,它的取值范围是多少,以及其它业务相关的配置等等,那么就需要有一张数据定义表,来记录对数据的要求。举个例子,我们要接入管理的数据有:产品A,根据文档《产品A数据结构定义》和
《基础数据定义文档》和《业务需求文档》
,它的数据结构可以在数据库中被定义成这样:
类型名称(DATA_TYPE) |
属性中文名称(ATTR_CNAME) |
属性名称(ATTR_NAME) |
属性类型(ATTR_TYPE) |
是否可空(IS_NULLABLE) |
属性长度(ATTR_LENGTH) |
是否可查(IS_SEARCHABLE) |
属性约束(ATTR_RES) |
...... |
产品A |
产品序列号 |
PRO_SEQ |
String |
F |
32 |
T |
^[\s\S]{0,32}$ |
|
产品A |
生产日期 |
PRO_DATE |
Date |
F |
19 |
T |
^\d{4}-\d{2}-\d{2}( )+\d{2}(:\d{2}){2}$ |
|
产品A |
供应商 |
SUPPLY |
String |
T |
60 |
T |
^[\s\S]{0,60}$ |
|
产品A |
产品质量 |
WEIGHT |
Integer |
F |
5 |
T |
^\d{1,5}(\.\d*)?$ |
|
产品A |
是否合格 |
IS_QUALIFIED |
Boolean |
T |
1 |
F |
^\d{1,1}(\.\d*)?$ |
|
...... |
|
|
|
|
|
|
|
|
说明一下,这是的“是否可查”表示的是是否可以针对
此属性
作为查询条件,进行数据搜索,这是业务相关的;这里的“属性约束”一列,它采用“正则表达式”的方式,在验证数据正确性时,将起到很大的作用。验证数据正确性这部分逻辑,放在数据接入中做。
这样一来,我们就能把对“产品A”这种数据类型的具体的数据要求放在数据库中存储起来,我们估且把这些数据要求集合而成的表命名为:属性表(TBL_ATTRIBUTE),当然这是可以通过使用
Hibernate的方式来进行增删改的操作的。别忘了,一个数据类型的增删改,还需要改一张表:数据类型管理表(TBL_DATATYPE),它存储了所有的被动态接入管理的数据类型,其表结构及其可能存储的数据内容如下所示:
类型名称(DATA_TYPE) |
表名(TABLE) |
创建时间(CREATE_TIME) |
修改时间(MODIFY_TIME) |
...... |
产品A |
TBL_PRO_A |
2014/10/8 8:43:23 |
2014/10/8 15:2:12 |
|
产品B |
TBL_PRO_B |
2014/10/8 10:16:56 |
2014/10/8 16:37:7 |
|
...... |
|
|
|
|
二、动态管理
定义完了要接入的数据类型,我们需要的是有这么一个函数来完成:addDataType(
DataType dt, List<Attribute> attrList)的功能,只要我们定义好了DataType数据类型、
List<Attribute>属性列表,那么我们就要能够动态地把这种数据类型加入“王安琪豪华数据”中进行管理。数据类型的管理容易,通过
数据类型管理表(TBL_DATATYPE)和
属性表(TBL_ATTRIBUTE),将传入的
DataType dt 和 List<Attribute> attrList 入库即可,主要的难点还是在于动态的把此种数据类型的存储表建立起来,因为每定义一种数据类型就要创建一张表。在这一步,我们需要产生或更新三张表:
数据类型管理表(TBL_DATATYPE)、
属性表(TBL_ATTRIBUTE)和产品A表(TBL_PRO_A)。
对于举例的“产品A”,它生成的存储表中的内容应该是这样的:
PRO_SEQ |
PRO_DATE |
SUPPLY |
WEIGHT |
IS_QUALIFIED |
A0001234567 |
2011-06-09 12:29:19 |
杭州诺基亚西门子科技有限公司 |
398 |
1 |
Angel89Wang |
2013-04-05 08:10:23 |
江苏金陵科技有限公司 |
125 |
|
...... |
|
|
|
|
在填入这些数据之前,我们应该把这张表建立起来,建立的依据就是
数据类型管理表(TBL_DATATYPE)和
属性表(TBL_ATTRIBUTE)中对“
产品A
”的定义,要创建的表名为:
TBL_PRO_A,各个列名为:PRO_SEQ(32
位
字符串型)、PRO_DATE(时间型)、SUPPLY(60
位
字符串型)、WEIGHT(5位整数型示)、IS_QUALIFIED(布尔型)。
在这里,我使用的是Hibernate所支持的tableName.hbm.xml来动态生成表的,当然在此之前,需要先把
tableName.hbm.xml文件按照定义的格式生成好。然后再调用
SchemaUpdate schemaUpdate = new SchemaUpdate(config);
schemaUpdate.execute(true, true);来生成表。
其实动态管理这一步,就不只是动态建表这么简单,上面说的比较狭隘,从广义上理解,它应该被分解为下面几个操作:1、动态创建;2、动态修改;3、动态删除。
动态创建就是通过
addDataType(
DataType dt, List<Attribute> attrList)来实现的
,
那么我们还需要:
removeDataType(String dataTypeName)来实现动态删除,
updateDataType(
DataType dt, List<Attribute> attrList)来实现动态修改。
动态创建就是刚刚说的那一部分,通过在
数据类型管理表(TBL_DATATYPE)、
属性表(TBL_ATTRIBUTE)中添加一个数据类型,并动态生成一张存储表
。动态修改就比较麻烦了,
数据类型管理表(TBL_DATATYPE)、
属性表(TBL_ATTRIBUTE)中内容容易修改,可是
如果数据存储表中存有数据,应该怎么处理。我采用了和一般数据库通用的处理方式,若表中有数据,则不给动态修改,必须通过删除再创建的方式来达到效果。动态删除,我采用的处理方式是,先删除此种数据类型定义,也即在
数据类型管理表(TBL_DATATYPE)和
属性表(TBL_ATTRIBUTE)把相应数据删除,然后再删除实际存储表中的数据。当然,具体的处理方法,还是要跟相关业务单位协商,了解他们的需求,如果他们就是不要删除数据存储表中的数据呢。
动态建表
是这里比较容易出问题的地方,因为如果我们新建一种数据类型:“typeA”,然后再把这个数据类型删除,此时就要处理好它在Hibernate上下文中的位置,也要从Hibernate上下文中删除,据我所知,
Hibernate对这个还没有支持,也可能是我才疏学浅,欢迎各位提供解决办法。
存在问题
1、数据字典问题
数据字典是数据管理系统中经常使用的,这主要是用来保证灵活性的,它基本是由key-value键值对来实现的,在实际数据存储中存储key,但在显示时取出value用来显示。它的好处为:更改value不改key,可以做到显示灵活配置,比如TB在系统中原先显示为“淘宝”,后来想要它显示为“TaoBao”,那么只要在数据字典项中更改value值即可。
那么问题就来了,假如数据类型A中某属性
应用了某数据字典,key-value1,它要存储的是key,那么我们就还要对这一数据字典进行管理。在我们的系统中也要存在这份数据字典,并且指明此属性对应的数据字典是哪张表,哪一列为key,哪一列为value。这当然是可以实现的,稍微麻烦一些。
再假如数据类型A中某属性应用了某数据字典,key-value1,数据类型B中某属性应用了key-value2,它们的数据字典还可能相同的地方,比如value值都是相同的,为了节约存储空间,我们把这两个数据字典整合进一张表中key1-key2-value,处理方法如上,实现可以,但麻烦。数据字典问题不止是存在于定义数据和动态管理这两个步骤中,下面的数据接入、数据查询遇到的问题将更加棘手。
处理办法:
a、唯一化key。
b、你的建议。
2、动态建表问题
上面说了,在删除数据类型时,要
在Hibernate上下文中
处理好
它,但是没有找到好的解决办法。欢迎不吝赐教。
处理办法:
a、悬而未决,你的建议。
上面讲了
一、定义数据,二、动态管理,
还有:
三、数据接入,
四、数据查询 两个步骤,将在以后的文章中详细说明。