Java枚举"已过时"-改用“数据字典”
xmh
•
2020年05月12日 am08:33
•
编程技术,
Java,
•
阅读 4840
Java枚举的一大缺点:增加一个枚举项,需要重新发版,不易扩展
数据字典:可以存储key=value形式的任何数据,变更不需要发版,易扩展
实现原理

字典的工作原理图
数据库表设计
CREATE TABLE `system_dict_node` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`pid` bigint(20) NOT NULL DEFAULT 0 COMMENT '上级ID',
`code` varchar(20) NOT NULL COMMENT '字典code 保存前统一转为大写/小写', --只能包含字典、数字、下划线
`title` varchar(20) NOT NULL DEFAULT '' COMMENT '字典值',
`leaf` tinyint(1) NOT NULL DEFAULT 0 COMMENT '末级 0-是 1-否',
`remark` varchar(255) DEFAULT NULL COMMENT '说明',
`status` tinyint(1) NULL DEFAULT NULL COMMENT '0-禁用 1-启用',
`order` int(5) NOT NULL COMMENT '显示顺序',
`time` int(10) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_pid`(`pid`) USING BTREE
) ENGINE = InnoDB COMMENT = '节点树';
CREATE TABLE `system_dict_value`
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`nid` bigint(20) NOT NULL COMMENT '节点ID',
`code` varchar(255) NOT NULL COMMENT '健',
`value` varchar(2048) NOT NULL COMMENT '值',
`remark` varchar(255) NULL DEFAULT NULL COMMENT '说明',
`status` tinyint(1) NULL DEFAULT NULL COMMENT '0-禁用 1-启用',
`order` int(5) NOT NULL COMMENT '索引 显示顺序 值越大越靠前',
`time` int(10) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_nid`(`nid`) USING BTREE
) ENGINE = InnoDB COMMENT = '节点键值对';
管理效果图

字典管理界面
说明:
节点支持多级
每个节点中可以有任意多个有顺序的“键值”对
数据变更后,点击“更新缓存”,则会把数据库中所有的 节点数据 和 键值数据 以json的形式存到 缓存(Redis中),并更新字典的最后变更时间到缓存(Redis)中
增加字典公共类
下面代码是SpringBoot实现,并封装到Starter中 (不明白什么是SpringBoot Starter可以参考我的其它文章)
@Configuration
@ConditionalOnProperty(prefix = "codeyyy.dict", name = "enable", matchIfMissing = true) //是否启用字典
@ConditionalOnClass({ EnableScheduling.class, RedisTemplate.class })
@EnableScheduling
@AutoConfigureAfter(RedisAutoConfiguration.class) //以来缓存(Redis)
public class DictConfiguration {
public DictConfiguration() {
log.info("======= SpringBootStarter ======= {}", "dict");
}
@Bean
public DictCache dictCache(RedisTemplate<String,String> redisTemplate) {
return new DictCache(redisTemplate);
}
}
public class DictCache {
private final String KEY_COMMON_DICT_NODE = "common:dict:node:data"; //保存的是节点数据列表
private final String KEY_COMMON_DICT_VALUE = "common:dict:value:data"; //保存的是键值对数据列表
private final String KEY_COMMON_DICT_TIME = "common:dict:time"; //保存的是字典最后变更的时间
protected static Map<String, Map<String, String>> cache = new HashMap(); //字典本地缓存(速度更快)
private String lastUpdateTime = "";
private RedisTemplate<String,String> systemRedisTemplate;
public DictCache(RedisTemplate<String,String> redisTemplate) {
this.systemRedisTemplate = redisTemplate;
reload();
}
//开放接口:通过节点 和 键 获取 值
public String getDictNodeValue(String dict, String code) {
if(cache.containsKey(dict)) {
return cache.get(dict).get(code);
}else{
return null;
}
}
//通过节点获取键值对列表(有序)
public Map<String, String> getDictNodeList(String dict) {
if(cache.containsKey(dict)) {
return cache.get(dict);
}else{
return new HashMap();
}
}
//定时监控字典数据是否变更
@Scheduled(fixedDelay = 1000)
public synchronized void reload() {
String value = systemRedisTemplate.opsForValue().get(KEY_COMMON_DICT_TIME);
if(value == null || lastUpdateTime.equals(value)) return; //字典数据是否变更
this.id = value;
Map<String, Map<String, String>> cache = new HashMap();
//临时的字典缓存
Map<String, JSONObject> dictCache = new HashMap();
String json = systemRedisTemplate.opsForValue().get(KEY_COMMON_DICT_VALUE);
if(json == null) {
log.warn("Dict Tree Data IS NULL.");
return;
}
JSONArray data = JSONArray.parseArray(json);
for(int i = 0; i < data.size(); i++) {
JSONObject item = data.getJSONObject(i);
dictCache.put(item.getString("id"), item);
}
//dict_node节点缓存
String did = null;
Map<String, String> codeCache = new HashMap();
json = systemRedisTemplate.opsForValue().get(KEY_COMMON_DICT_NODE);
if(json == null) {
log.warn("Dict Node Data IS NULL.");
return;
}
JSONArray nodeList = JSONArray.parseArray(json);
for(int i = 0; i < nodeList.size(); i++) {
JSONObject item = nodeList.getJSONObject(i);
String code = "";
did = item.getString("tid");
if(!codeCache.containsKey(did)) {
code = "";
JSONObject temp = null;
while(dictCache.containsKey(did)) {
temp = dictCache.get(did);
did = temp.getString("pid");
code = temp.getString("code") + "." + code;
}
if(code.length() > 0) {
code = code.substring(0, code.length() - 1);
}
codeCache.put(did, code);
}else{
code = codeCache.get(did);
}
if(!cache.containsKey(code)) {
cache.put(code, new LinkedHashMap());
}
cache.get(code).put(item.getString("code").trim(), item.getString("value").trim());
}
DictCache.cache = cache;
log.info("Reload Dict Data Success.");
}
}
项目中的使用
action/service中注入dictCache
@Autowired
private DictCache dictCache;
获取某一节点下的所有键值,如下:
Map<String,String> qqLoginSdkData = dictCache.getDictNodeList("SDK.QQ.LOGIN");
获取某一节点下 某一键 的值,如下:
String appid = dictCache.getDictNodeValue("SDK.QQ.LOGIN", "appid");
应用实例:
- 存放第三方的 appid和appkey等信息
- 存放 状态信息(0-禁用 1-启用)、打开方式(_blank-新窗口 _self-当前窗口...)、... 可以直接返回一个节点列表给前端,展示给用户,让其选择
- ...

实例效果图