`
iamzhongyong
  • 浏览: 805368 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

为数据库表设计可扩展的字段

 
阅读更多

       在平时的系统设计中,要充分考虑扩展和复用,后面维护过程中出现类似的场景的时候,能够有效的复用之前的。在快速响应业务的同时,也确保系统的稳定性。如何设计扩展性强的数据库结构呢,这里从日常工作中学习了一些经验,有自己团队内部实现的,也有其他团队的实践。

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(20);
    private static final Long B_SERVICE = (long)Math.pow(21);
    private static final Long C_SERVICE = (long)Math.pow(22);
    private static final Long D_SERVICE = (long)Math.pow(23);
    /**
     * 二进制中的属性名称,可以直接对应数据库中的字段
     */
    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格式的文档。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2
3
分享到:
评论
10 楼 ivan19861025 2015-09-07  
之前我们使用了第3种方案, 但是加了一个表, 用来维护扩展字段的元数据信息,
相关列可能如下:
字段名  字段文本 字段类型 字段描述

然后页面上可以根据这个信息来渲染动态的界面来录入信息或删除.
9 楼 di1984HIT 2014-02-16  
恩。扩展字段一般就是展示,不能作为查询
8 楼 iamzhongyong 2013-06-19  
lazy_ 写道
标题很好,但是图片烂掉了。

恩,最后面有链接下载。图片上传太费劲。可以下PDF看
7 楼 lazy_ 2013-06-19  
标题很好,但是图片烂掉了。
6 楼 iamzhongyong 2013-06-19  
liaolzy 写道
一个int 有32位,每一位处理不同的业务,这样可读性不好.呵呵.可维护也是问题啊...不知道是不是这意思

二进制表示,确实可读性不好。但是仅仅限于直接在数据库中查看数据,一般情况下会有页面来辅助数据的查询,这样的话可以在页面上做文章,查询出来的二进制再做一个转换。
5 楼 iamzhongyong 2013-06-19  
fflame 写道
查询的效率呢?

有个前提,就是这些扩展的东西,没有检索的需求。就是说根据主键或者其他查询条件把这些数据查询出来,附带的查询出来呵呵
4 楼 liaolzy 2013-06-19  
一个int 有32位,每一位处理不同的业务,这样可读性不好.呵呵.可维护也是问题啊...不知道是不是这意思
3 楼 fflame 2013-06-19  
查询的效率呢?
2 楼 niweiwei 2013-06-18  
楼主介绍的方法不错,在以后的项目中尝试下。。。
1 楼 jinnianshilongnian 2013-06-18  
iteye的图片上传功能着实不好用

在编辑器最下方 有个上传文件;上传图片后,有个添加到页面的按钮,点一下即可 还是挺方便的

相关推荐

    java如何获得数据库表中各字段的字段名

    ### Java如何获得数据库表中各字段的字段名 在Java编程中,经常需要与数据库进行交互,例如查询、更新或...此外,这种方法还能够帮助开发者更好地理解和适配不同数据库的结构变化,提高应用程序的灵活性和可扩展性。

    数据库建表增加或删除字段

    在Oracle数据库中,如果一个表中存在某个字段已经不再被使用或者该字段需要被替换,那么可以通过`ALTER TABLE`语句来删除该字段。这一操作会永久地从表中移除指定的列,因此在执行之前需要确保该字段的数据确实不再...

    设计数据库(表结构设计)

    如果发现字段过多而记录较少,或者有很多字段值为空,可能需要重新设计表结构,以减少字段,增加记录。 数据库的创建和基本操作主要包括创建数据库、打开数据库、关闭数据库和删除数据库。CREATE DATABASE命令用于...

    SQL数据库查看器 可创建字段 查看表

    这涉及到指定字段名、数据类型、长度、是否可为空、默认值等属性,有助于扩展数据库的结构。 5. 表管理:除了查看和创建字段,此工具还可能包含编辑表结构(如修改字段属性、删除字段或表)、导入/导出数据、运行...

    数据库表设计数据库表设计

    数据库表设计是构建高效、稳定、可扩展的数据库系统的核心环节。它涉及到多个关键知识点,包括数据模型、关系设计、规范化理论、索引优化、事务处理等。在本篇文章中,我们将深入探讨这些核心概念,并结合给定的...

    c06数据库表设计设计设计

    数据库表设计是构建高效、稳定、可扩展的信息系统的基础,尤其在C06阶段,这一环节显得尤为重要。本文将深入探讨数据库表设计的关键知识点,包括数据规范化、关系模型、索引优化、表关联以及设计原则。 首先,数据...

    数据库表设计的几点经验

    数据库表设计是构建高效、稳定、可扩展的信息系统的基础,其重要性不言而喻。以下将详细讨论在设计数据库表时的一些关键经验和原则。 首先,让我们关注使用GUID(全局唯一标识符)作为主键。主键是数据库表中识别每...

    基于RBAC权限管理数据库表设计

    总结来说,基于RBAC的权限管理数据库表设计涵盖了角色、权限、用户、角色-权限关联、用户-角色关联等多个核心组件,通过这些组件的组合,可以构建出一个强大且易于维护的权限管理体系。在实际应用中,还可以根据业务...

    SQL Server数据库设计表和字段的经验

    因此,建议为变动频繁的数据创建独立的表,并添加开始日期和结束日期字段,以便追踪数据历史。 在选择字段名时,应遵循有意义且易于理解的原则,避免使用过于简略或混淆的缩写。使用前缀命名可以帮助区分不同表格中...

    数据库字段命名规范

    数据库字段命名规范是数据库设计的重要环节,良好的命名规范可以提高数据库的可读性、可维护性和可扩展性。以下是数据库字段命名规范的详细介绍: 一、数据库表名命名规范 * 数据库表名在命名时,首字母大写,用...

    数据库表设计原则技巧

    ### 数据库表设计原则技巧详解 #### 一、原始单据与实体之间的关系 在数据库设计过程中,理解和处理原始单据与实体之间的关系至关重要。原始单据与实体之间的关系可以是一对一、一对多或者多对多的形式。通常情况...

    OA 协同办公系统 数据库设计说明书(附数据库关系表)

    表设计是指对数据库中的每个表的设计,包括表名、字段名、数据类型、约束等。表设计需要考虑到多方面的因素,包括系统的功能需求、数据的存储和管理、数据的安全性等。 3.2.1 TableName(表名的解释) TableName ...

    动态可扩展的数据库设计.docx

    动态可扩展的数据库设计是应对信息系统中数据结构不断变化和扩展需求的一种策略。数据库设计是构建数据库及其应用系统的关键技术,旨在有效地存储数据并满足用户的各类需求。在传统的数据库设计中,设计人员往往试图...

    多级目录的数据库设计

    多级目录数据库设计旨在提供一种灵活且可扩展的方式来存储和管理具有层级关系的数据,例如在论坛、文件系统或者电子商务网站的产品分类中。下面将详细讨论多级目录数据库设计的相关知识点。 首先,我们要理解多级...

    Delphi操作SQLSERVER数据库动态增加字段

    同时,考虑到数据库的可扩展性和维护性,应谨慎处理动态增加字段的操作,避免频繁修改数据库结构。 在实际开发过程中,可能还需要考虑事务处理,以确保数据库操作的原子性和一致性。例如,如果同一时间有多个操作,...

    数据库设计,讲解业务实体对象到数据库表的映射关系。

    通过对业务实体对象到数据库表的映射关系的深入探讨,我们可以发现,合理的设计能够极大地提高系统的可维护性和扩展性。UML作为一种强大的建模工具,在这个过程中发挥了不可或缺的作用。同时,采用合理的分层架构和...

    数据库设计原则优化数据库的设计

    字段名可使用MD001、MD002等,预留扩展空间,自定义字段则统一命名为UDF01至UDF99。同时,所有的表和字段应在数据字典中定义,以便于后续开发时生成数据绑定窗口和控件。 其次,数据字典的建立是不可忽视的步骤。在...

    排课系统数据库表详细设计

    综上所述,排课系统数据库表的设计需全面考虑教学活动的各个环节,从学生、教师、教室、课程到资源,每一项信息都需精心规划,以构建一个高效、稳定且易于扩展的排课系统。通过对上述表格的详细解读,我们可以更深入...

Global site tag (gtag.js) - Google Analytics