在平时的系统设计中,要充分考虑扩展和复用,后面维护过程中出现类似的场景的时候,能够有效的复用之前的。在快速响应业务的同时,也确保系统的稳定性。如何设计扩展性强的数据库结构呢,这里从日常工作中学习了一些经验,有自己团队内部实现的,也有其他团队的实践。
1、二进制位
在数据库中设计一个字段,暂且叫“options”,这个字段存储的是数值,可以理解为二进制的组合。例如一个用户既有A标签(标签可以是服务),又有B标签,这时候类似“有没有”或者“是否包含”的场景,非常适合这种。一个字段搞定多个布尔业务场景。
下图简单描述位数和业务以及具体存储的关系。
问题1:options允许直接设置值吗?
不允许,必须通过append或者remove来去掉一个特定位数的值。否则会导致问题,例如我用了第一位,第二个业务用了第二位,第二次直接设置了值,那就把原先的冲走了。
问题2:如何实现append或者remove的方法?
把options这个属性设置为私有,然后通过二进制的操作来添加或者去掉值。
问题3:如何比较options是否包含特定的位数?
首先,二进制哪一位表示那个业务场景,最好在定义常量,例如2的3次方表示用户含有A服务。然后通过工具类来判断用户是否支持或者包含次服务。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
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属性来完成。
上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
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格式的文档。
相关推荐
表T2 删掉 C2字段 alter table T2 drop column C2; 表T1 增加 g字段,类型为number(10) alter table t1 add g number(10);
⽤户登录系统数据库表设计 ⽤户登录系统数据库表设计 最近看了看公司后台⽤户登录系统的设计, ⽐较混乱, 主要还是因为URS和Oauth以及URS第三⽅这三个登录形式各不相同导致的。 下⾯着重介绍⼀下涉及到第三⽅登录中...
达梦数据库_SQL语言手册.pdf 数据库快照定义语句 数据库快照删除语句 第章数据查询语句和全文检索语句 单表查询 简单查询 带条件查询 集函数 情况表达式 连接查询 子查询 标量子查询 表子查询 派生表子...
数据库设计:⽤户登录系统数据库表设计 数据库设计:⽤户登录系统数据库表设计 ⽤户登录系统数据库表设计 ⽤户登录系统数据库表设计 最近看了看公司后台⽤户登录系统的设计, ⽐较混乱, 主要还是因为URS和Oauth以及...
(如表4) 表4 站点公汽关系表(BusLAP) 通过上述对德州市公交信息系统数据库的设计过程,已经为用户对数据可进行操作打 下了坚实基础,以便能更好的为公交公司和政府交通机关管理决策服务。 2.3公交系统空间数据库...
根据系统开发需求,每个公交站台最多可 以容纳8条公交线路同时通过,为了确保后期系统的良好扩展,本表设计为可以容纳20条 公交线路。公交站台线路的数据库格式是:线路1ID、线路2ID……,#表示线路结束。 3.2 公交...
表名 增长频度(条/年) 描述 考虑对数据库的维护、可扩展性的设计 数据库管理与维护说明 在设计数据库的时候,及时给出管理与维护本数据库的方法,有助于将来撰写出正确完备的用户手册。 软件数据库设计模板 1 2
学生成绩管理系统数据库设计 [提要] 一个好的数据库,不但可以提高数据查询效率,而且还可以保证数据的完整性和一致性 。所以,数据库在信息管理系统中有着非常重要的作用。本文介绍如何使用SQL Server 2005完成学生...
1487.2.5 将字段数据类型转换为Access数据类型 1497.2.6 使用链接表管理器加载项重新链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 通过导入Excel工作表创建一个表 1517....
看图中的红圈,先看gorupid字段相关联,这种关联方式在实际数据库中的表现如下 图: 如图中所示,管理组表中"超级管理员"的groupid为1,那么权限映射表中groupid为 1的权限也就是"超级管理员"所拥有的权限。...
数据库设计约定 ⼀、公共部分 ⼀、公共部分 1、存储引擎 、存储引擎 默认Innodb,⾮特殊要求⼀律使⽤此引擎 2、字符集 、字符集 Database Server 字符集统⼀默认UTF-8,table和column从server继承 ⼆、表设计约定 ⼆...
1457.2.4 处理外部文件中的图像 1487.2.5 将字段数据类型转换为Access数据 类型 1497.2.6 使用链接表管理器加载项重新 链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 ...
1457.2.4 处理外部文件中的图像 1487.2.5 将字段数据类型转换为Access数据 类型 1497.2.6 使用链接表管理器加载项重新 链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 ...
1487.2.5 将字段数据类型转换为Access数据类型 1497.2.6 使用链接表管理器加载项重新链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 通过导入Excel工作表创建一个表 1517....
1487.2.5 将字段数据类型转换为Access数据类型 1497.2.6 使用链接表管理器加载项重新链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 通过导入Excel工作表创建一个表 1517....
1487.2.5 将字段数据类型转换为Access数据类型 1497.2.6 使用链接表管理器加载项重新链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 通过导入Excel工作表创建一个表 1517....
1457.2.4 处理外部文件中的图像 1487.2.5 将字段数据类型转换为Access数据 类型 1497.2.6 使用链接表管理器加载项重新 链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 ...
三: 表中记录应该有一个唯一的标识符 在数据库表设计的时候,数据库管理员应该养成一个好习惯,用一 个ID号来 唯一的标识行记录,而不要通过名字、编号等字段来对纪录 进行区分。每个表都应该有一个ID列,任何两个...
1457.2.4 处理外部文件中的图像 1487.2.5 将字段数据类型转换为Access数据 类型 1497.2.6 使用链接表管理器加载项重新 链接表 1507.2.7 导入表和将数据库文件链接为表 1507.3 导入和链接电子数据表文件 1517.3.1 ...