关于数据字典(数据编码)的使用

聊一下“数据字典”

数据字典的含义非常广泛, 通俗地讲, 是指"关于数据项的数据", 一个项目中, 所有数据的来源, 去向, 存储等各个环节, 对数据的说明都可以是"数据字典"的范畴. 
我在这里想讲的"数据字典",是狭义上, 特指数据编码,举个栗子,就是如性别这个数据项,在数据库和代码中,用 '1' 表示男性, '2' 表示女性。
也有人叫这是数据字典,名字高大上一些。

不好的栗子

也许有人认为这个很简单,但其实,我碰到过很多人都不能恰当使用它,从而导致程序性能上的低下或代码隐患,前人编程后人掉坑的情况。
类似以下的,我认为都是不好的栗子:
如:
  表 user(id, name, gender, ....)
  表 dict_gender(id, value, label, ...)
  
在mybatis 的xml 文件sql语句中,

select a.name, b.label from user a 

left join dict_gender b on a.gender = b.value;

更有甚者
select gender_label, count(*) from 
(
select a.id, b.label as gender_label as  from user a left 
join dict_gender b on a.gender = b.value
);
诸如之类的代码。 它们将 数据值与需要在用户视图界面的显示标签的转换,放在数据库层面来处理,一个user表有几个编码字段,就left join 几个码表,导致程序的效率低下,性能极低,如果你在写这样的代码,建议阅读此文。如你是大牛,也帮我看看,提个意见,不胜感激!

还有就是在java代码中, 类似 if (myUser.getGender().equals("1")){...} 这样的代码,
"1", "2" 指的分别是什么呢? 给阅读者造成不必要的困绕。

我的解决方法

我认为,界面显示层的问题就应该直接交给前端浏览器处理,后端给前端的数据直接用原值。 
后端代码,应该用有意义常量或者枚举值对"1", "2"这样的编码作一个封装。
下面是我的解决方法,分享出来,

建表语句


CREATE TABLE `mandy_dict` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL COMMENT '程序中用的名字,数据字段注释:后的名字',
  `label` varchar(255) NOT NULL COMMENT '显示用的名字',
  `mandy_const` varchar(255) DEFAULT NULL COMMENT '常量类名',
  `remark` varchar(2000) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='字典表';

CREATE TABLE `mandy_dict_item` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `dict_id` bigint(20) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL COMMENT '常量名称',
  `value` varchar(255) DEFAULT NULL COMMENT '数据表中存放的值',
  `label` varchar(255) DEFAULT NULL COMMENT '前端显示的值',
  `remark` varchar(2000) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COMMENT='字典明细表';

数据样例

mandy_dict
1    gender    性别    Gender    性别
2    security_level    安全级别    SecurityLevel    安全级别参数配置

mandy_dict_item
1    1    MALE    1    男    性别-男
2    1    FEMALE    2    女    性别-女
3    2    TOP_SECRET    1    绝密级    TOP_SECRET:绝密级
4    2    SECRET    2    机密级    SECRET:机密级
5    2    CONFIDENTIAL    3    秘密级    CONFIDENTIAL:秘密级
6    2    UNCLASSIFIED    4    普通级    UNCLASSIFIED:普通级
7    2    PUBLIC    5    已公开    PUBLIC:已公开

生成Java代码的SQL语句

定义好所有的字典数据项后,执行如下语句
select * from 
(
select -1 as line_nbr, 'package cn.mandy.sysadmin.constant;\n\npublic interface MandyConstant {' as content
union all
select id * 100000 as line_nbr, concat('/**\n* name:', name, '\n* label:', '\n* remark:', remark, '\n*/\n   final class ', mandy_const, '{') as content from mandy_dict
union all
select a.id * 100000 + b.id as line_nbr, concat('   public static final String ', b.name, ' = "', b.value, '"; // label:', b.label, ' remark:', b.remark) as content from mandy_dict a, mandy_dict_item b
where a.id = b.dict_id
union all
select id * 100000 + 10000 as line_nbr, concat('} \n') as content from mandy_dict
union all
select 999999 as line_nbr, '}' as content
) all_line order by line_nbr,

生成的JAVA代码

将输出直接拷贝到一个程序文件中,是一个接口。 用SQL自动生成代码,既可提高效率,也可以避免手写出错,提高正确率,给项目管理也带来好处。

package cn.mandy.sysadmin.constant;

public interface MandyConstant {

/**
* name:security_level
* label:
* remark:安全级别参数配置
*/
   final class SecurityLevel{
   public static final String TOP_SECRET = "1"; // label:绝密级 remark:TOP_SECRET:绝密级
   public static final String SECRET = "2"; // label:机密级 remark:SECRET:机密级
   public static final String CONFIDENTIAL = "3"; // label:秘密级 remark:CONFIDENTIAL:秘密级
   public static final String UNCLASSIFIED = "4"; // label:普通级 remark:UNCLASSIFIED:普通级
   public static final String PUBLIC = "5"; // label:已公开 remark:PUBLIC:已公开

/**
* name:gender
* label:
* remark:性别
*/
   final class Gender{
   public static final String MALE = "1"; // label:男 remark:性别-男
   public static final String FEMALE = "2"; // label:女 remark:性别-女

小小的说明一下

然后在程序中,就可以像这样愉快地使用了。
if (MandyConstant.Gender.MALE.equals(myUser.getGender()){...}

(小技巧: 用 MandyConstant.Gender.MALE.equals(myUser.getGender()) 而不是  myUser.getGender().equals(MandyConstant.Gender.MALE) 可以避免NPE )

用MALE而不是 "1", 程序的可读性是不是大大增强了,而且,想要查看程序中关于这个数据项的处理逻辑,还可以用 MandyConstant.Gender 或者 MandyConstant.Gender.MALE
来进行查找,对读源码查BUG带来极大的便利。

在mandy_dict表中, mandy_const的值对应着类名, 若 mandy_const 为空, 则不生成常量类。通常来说,会对程序流程产生影响,需要写成常量的数据项(如各种状态值),
它们的枚举值数据一般不会太多,而枚举值多的数据项(如民族,行政区域等)通常不影响程序流程。用 mandy_const 空或非空就可以将这两类数据进行区分。

以上是后端的处理方式。

前端的处理


    前端界面展示, 通常只是作一个 value 到 label 的转换,还有用到 select,  list 等的时候,需要用到所有的枚举条目。
可以如下处理(以下是vue中的 js代码, 不复杂,相信聪明如您一定看得懂)。
//关键sql
//        select d.name as dict_key, di.value as dict_value, di.label as dict_label from mandy_dict d, mandy_dict_item di
//        where di.dict_id = d.id
// list 是从后端执行以上sql返回的数据,sysDict 存放的是一个 map(mandy_dict.name, map(mandy_dict_item.value, mandy_dict_item.dict_label))
SYS_DICT_INIT: (stat, list) => {
            for (var i = 0, len = list.length; i < len; i++) {
                var dest = null;
                if (!stat.sysDict.has(list[i].dictKey)) {
                    dest = new Map();
                    stat.sysDict.set(list[i].dictKey, dest);
                } else {
                    dest = stat.sysDict.get(list[i].dictKey);
                }
                dest.set(list[i].dictValue, list[i].dictLabel);
            }
        },

import store from "../store";
export function getLabel(dictKey, dictValue) {
    let myMap = new Map();
    myMap = store.getters.sysDict;
    if (!myMap.has(dictKey)) {
        return "未定义";
    }
    let keyMap = new Map();
    keyMap = myMap.get(dictKey);
    if (!keyMap.has(dictValue)) {
        return "未定义";
    }
    return keyMap.get(dictValue);
}

export function getEntries(dictKey) {
    if (!store.getters.sysDict.has(dictKey)) {
        return [];
    }
    let ret = [];
    for (var [value, label] of store.getters.sysDict.get(dictKey)) {
        let rec = {
            value: value,
            label: label
        };
        ret.push(rec);
    }
    return ret;
}

<template> HTML 段就可以类似这样使用了 {{getDictLabel("gender", row.gender)}}
像这样,不是说把活交给还前端人员,把皮球踢给前端,更大的意义在于,在客户端浏览器上去做这些value, label转换可以极大的降低数据库的压力。

写在后面

    数据库做join 类似操作是非常消耗资源的,阿里的java开发手册(1.4.0 索引规约, 第二条)中,明确地提出超过三个表禁止join, 要让join做更有意义的事,join业务数据表,而不是码表。不了解这个开发手册的可以去百度了解一下这个开发规范,这个手册是有非常高价值的.

好,分享到这里,谢谢!

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值