为数据库表设计可扩展的字段
来源:互联网 发布:自动化编程用什么语言 编辑:程序博客网 时间:2024/06/10 14:57
在平时的系统设计中,要充分考虑扩展和复用,后面维护过程中出现类似的场景的时候,能够有效的复用之前的。在快速响应业务的同时,也确保系统的稳定性。如何设计扩展性强的数据库结构呢,这里从日常工作中学习了一些经验,有自己团队内部实现的,也有其他团队的实践。
1、二进制位
在数据库中设计一个字段,暂且叫“options”,这个字段存储的是数值,可以理解为二进制的组合。例如一个用户既有A标签(标签可以是服务),又有B标签,这时候类似“有没有”或者“是否包含”的场景,非常适合这种。一个字段搞定多个布尔业务场景。
下图简单描述位数和业务以及具体存储的关系。
问题1:options允许直接设置值吗?
不允许,必须通过append或者remove来去掉一个特定位数的值。否则会导致问题,例如我用了第一位,第二个业务用了第二位,第二次直接设置了值,那就把原先的冲走了。
问题2:如何实现append或者remove的方法?
把options这个属性设置为私有,然后通过二进制的操作来添加或者去掉值。
问题3:如何比较options是否包含特定的位数?
首先,二进制哪一位表示那个业务场景,最好在定义常量,例如2的3次方表示用户含有A服务。然后通过工具类来判断用户是否支持或者包含次服务。
代码如下:
package
com.taobao.logistics.domain.dataobject;
public
class
OptionsTest {
private
static
final
Long A_SERVICE = (
long
)Math.pow(
2
,
0
);
private
static
final
Long B_SERVICE = (
long
)Math.pow(
2
,
1
);
private
static
final
Long C_SERVICE = (
long
)Math.pow(
2
,
2
);
private
static
final
Long D_SERVICE = (
long
)Math.pow(
2
,
3
);
/**
* 二进制中的属性名称,可以直接对应数据库中的字段
*/
private
Long options;
/**
* 添加一个特定的位数,targetProperty是这个位的数值,例如2的3次方等
* @param targetProperty
*/
public
void
appendOptions(
long
targetProperty) {
if
(
null
== options) {
options =
new
Long(
0
);
}
this
.options |= targetProperty;
}
/**
* 在options中移除特定的位数,判断是否包含
* @param targetProperty
*/
public
void
removeOptions(
long
targetProperty) {
if
(
null
== options) {
options =
new
Long(
0
);
}
if
(
this
.containOptions(targetProperty)){
this
.options &= (
this
.options - targetProperty);
}
}
public
boolean
containOptions(
long
targetProperty) {
if
(
null
== options) {
return
false
;
}
return
((
this
.options & targetProperty) == targetProperty);
}
public
static
void
main(String[] args) {
OptionsTest op =
new
OptionsTest();
op.appendOptions(A_SERVICE);
op.appendOptions(B_SERVICE);
op.appendOptions(C_SERVICE);
op.appendOptions(D_SERVICE);
System.out.println(
"options的值:"
+op.options);
op.removeOptions(A_SERVICE);
op.removeOptions(C_SERVICE);
System.out.println(
"移除第0位和第2位后的,options的值:"
+op.options);
System.out.println(
"是否包含测试,第3位:"
+op.containOptions(D_SERVICE));
}
}
2、feature或者attribute字段来存储KV接口数据,以此来进行扩展
在设计表字段的时候,有些新增的字段我们是无法预料的,新增加的字段,如果没有检索需求,是可以通过key-value的形式来在一个数据库字段中进行扩展的,这样业务上面增加了一个新字段,只需要简单定义一下key即可。其余的数据库表变更就不用做了,方便快捷。
例如下图,key和value通过“:”来做分割,不同的KV之间通过“;”来做分割,然后通过代码来做DB中数据的保存和隔离。
问题1:外部在调用的时候,能否自定义key?
这个建议最好不要外部直接自定义,如果A团队维护的feature字段,B团队能够在A团队完全不知情的情况下写入一个新的key,我觉得是有点危险的。比较给力的做法是,B团队如果需要在feature中增加一个key,那向A团队申请,A团队在配置或者常量中增加这个key(只有配置过的常量才能写入),这样就能达到扩展并且相对安全的目的了。
问题2:value中如果包含分隔符怎么办?
在插入value的时候,最好是做一个校验,判断value是否包含feature中定义的分隔符,如果包含,可以转义或者替换一下。否则会造成在解析分割的时候出现混乱的情况。
问题3:feature的内容超过数据库的长度怎么办?
一般情况下,feature字段最好预留长一点,这样保证插入相对多的数据。另外可以在数据库中申请多个feature,例如feature1、feature2,这样保持足够扩展,但是这并不是长远之计,后面会有基于数据库中新表的扩展。
问题4:feature中的数据如何解析?
这个其实在上面的图中就能理解了,解析字符串,然后转换为java中的Map数据结构,之后外部调用,统统依赖心的map属性来完成。
上代码:
package
com.taobao.logistics.domain.dataobject;
import
java.util.ArrayList;
import
java.util.HashMap;
import
java.util.List;
import
java.util.Map;
import
com.alibaba.common.lang.ArrayUtil;
import
com.alibaba.common.lang.StringUtil;
public
class
FeatureTest {
private
static
final
String K_V_SPLIT =
":"
;
private
static
final
String KV_KV_SPLIT =
";"
;
private
static
final
String KEY_NAME =
"name"
;
private
static
final
String KEY_AGE =
"age"
;
private
static
final
String KEY_SEX =
"sex"
;
/**
* 把定义的key放在List中,用于做校验
*/
private
static
final
List<String> KEY_ALLOW =
new
ArrayList<String>();
static
{
KEY_ALLOW.add(KEY_AGE);
KEY_ALLOW.add(KEY_NAME);
KEY_ALLOW.add(KEY_SEX);
}
/**
* 原始的feature内容,对应数据库中的表字段
*/
private
String feature;
/**
* 解析之后的KV对应关系,存储在Map中,方便对象操作
*/
private
Map<String,String> featureMap;
public
static
void
main(String[] args) {
FeatureTest ft =
new
FeatureTest();
ft.addFeature(KEY_NAME,
"iamzhongyong"
);
ft.addFeature(KEY_AGE,
"11"
);
ft.addFeature(KEY_SEX,
"0"
);
ft.addFeature(
"funk"
,
"zhongyong"
);
System.out.println(
"目前Feature中的内容:"
+ft.feature);
ft.removeFeature(KEY_SEX);
System.out.println(
"移除Sex之后的内容:"
+ft.feature);
System.out.println(
"输出feature中的name:"
+ft.getFeature(KEY_NAME));
}
/**
* 校验传入的key是否合法
*/
public
boolean
checkKeyIsAllow(String key){
return
KEY_ALLOW.contains(key);
}
/**
* 根据特定的key获取value值
* @param key
* @return
*/
public
String getFeature(String key){
initFeatureMap();
String value = featureMap.get(key);
return
value==
null
?
null
: value;
}
/**
* 移除一个keu对应的value内容
* @param key
* @return
*/
public
boolean
removeFeature(String key){
initFeatureMap();
boolean
flag =
false
;
if
(featureMap.containsKey(key)){
featureMap.remove(key);
resetFeature();
flag =
true
;
}
else
{
flag =
false
;
}
return
flag;
}
/**
* 移除所有的feature内容
*/
public
void
removeAllFeature() {
this
.featureMap =
null
;
this
.feature =
null
;
}
private
void
resetFeature(){
StringBuffer sb =
new
StringBuffer();
for
(String key : featureMap.keySet()) {
String aValue = featureMap.get(key);
sb.append(key);
sb.append(
":"
);
sb.append(aValue);
sb.append(
";"
);
}
this
.feature = sb.toString();
}
private
void
initFeatureMap() {
if
(
null
== featureMap) {
featureMap =
this
.getFeatureMap(feature);
}
}
private
Map<String, String> getFeatureMap(String features) {
Map<String, String> featureMap =
new
HashMap<String, String>();
if
(StringUtil.isNotBlank(features)) {
String[] featureArray = StringUtil.split(features, KV_KV_SPLIT);
if
(ArrayUtil.isNotEmpty(featureArray)) {
for
(String feature : featureArray) {
if
(StringUtil.isNotBlank(feature)) {
String[] aKeyAndValue =
new
String[
2
];
int
index = feature.indexOf(K_V_SPLIT);
if
(index >
0
) {
aKeyAndValue[
0
] = feature.substring(
0
, index);
aKeyAndValue[
1
] = feature.substring(index +
1
);
if
(ArrayUtil.isNotEmpty(aKeyAndValue)) {
String key = aKeyAndValue[
0
];
String value = aKeyAndValue[
1
];
if
(StringUtil.isNotBlank(key)&& StringUtil.isNotBlank(value)) {
featureMap.put(key, value);
}
}
}
}
}
}
}
return
featureMap;
}
/**
* 添加一个KV的数据到feature中去
* @param name
* @param value
*/
public
void
addFeature(String name, String value){
if
(StringUtil.isNotBlank(name) && StringUtil.isNotBlank(value) && checkKeyIsAllow(name)) {
initFeatureMap();
featureMap.put(name, value);
resetFeature();
}
}
}
3、构建扩展表,灵活支持KV类扩展
刚才的feature中的扩展,有个弊端,就是feature不能无限的扩展,有没有办法能够相对灵活的扩展,当然有了呵呵。设计一个扩展表,这个扩展表来表示一个扩展的key和value的值。这样增加key的时候,就能相关比较灵活了。
数据库表字段设计如下:
其中a表是业务主表,a_ext是业务的扩展表,biz_id记录了a表中的业务主键ID,kv_type_id来定义扩展的key的信息,可以理解类似feature中的key,另外biz_value值是扩展字段对应的值。
通过一个例子说明:
基于上述三个点,我觉得在系统扩展性方面能相对比较好,这样能够相对比较灵活的添加新东西。
发现有些图片不能正常展示,iteye的图片上传功能着实不好用。我在附件中添加了PDF格式的文档。