1 產生數據字典表的原因
現在有這么一張表,其中的大部分字段都是固定值,比如訂單分類、類型、動作、狀態、業務類型
如果都以字符串的形式存入數據庫中,就存在數據冗余的情況,而且如果希望修改某一字段的字符串(比如從“來自網絡的訂單”改為“網絡訂單”),那就不是修改一條,而是需要修改許多條
|
因此通常以數字的格式存入表中,此時原表的數據如圖
|
而每個字段的數字與字符串的對應關系單獨建一張表進行管理,示意圖如下
|
但是如果字段很多,比如上面有5個字段,那么進行轉義時就要連接6張表進行查詢,而這並不是為了業務進行連表,只是單純需要轉義,這就非常麻煩了
每個字段分別建一個表也很繁瑣,不方便管理,此時就可以考慮把這些字段全部統合在一張表里——數據字典表
數據字典表 能起到減少代碼量以及靈活修改固定值的作用
2 數據字典表建表代碼
2.1 一張表
如圖,就是數據字典表,每個字段根據type進行區分,同一type的數據再根據type_value進行區分,並與type_text構成對應關系用於轉義
|
數據字典表建表語句一覽
|
CREATE TABLE `sales_dictionary` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` int(11) DEFAULT NULL COMMENT '類型 1.訂單動作 2 訂單狀態 3 業務類型 4 訂單分類 5 訂單類型 6 配送方式 7 支付方式',
`type_value` int(11) DEFAULT NULL COMMENT '值',
`type_text` varchar(255) DEFAULT NULL COMMENT '文本',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
2.2 兩張表
上面是簡單的建表,將類型、key、value統合在一張表,方便直接查詢使用,但很明顯,時間久了誰記得type中的數字對應什么,如果直接改用字符串,又會陷入“改一個字符串就要修改多條記錄”的情況
因此實際上應該拆為兩張表。
兩張表的建表方式以及查詢方式具體見SpringBoot+Redis實現數據字典_蹊源的奇思妙想的博客-CSDN博客_springboot緩存數據字典
下面是摘錄:
|
|
3 查詢數據庫的數據字典表並緩存到集合中
3.1 步驟一 建一個與字典對應的實體類
如圖,為單張數據字典表對應的實體類,姑且可以理解為PO,如果是兩張數據字典表,那么就是兩表聯查的PO
|
3.2 步驟二 寫一個mapper 查詢數據字典表
3.2.1 單張數據字典表
在后端寫一個mapper,查詢數據庫的字典表
|
|
3.2.2 兩張數據字典表
相比起一張表,兩張表管理更為清晰,只是在后端mapper查詢數據庫表時,需要兩張表進行聯查
參考SpringBoot+Redis實現數據字典_蹊源的奇思妙想的博客-CSDN博客_springboot緩存數據字典
|
3.3 步驟三 編寫詞典全局緩存方法
3.3.1 存放位置
直接復制下面的代碼,粘貼到IDEA中
|
3.3.2 代碼
注意根據步驟一和二 修改一下聲明的mapper接口以及集合中的元素類型
@Component
public class InitContext {
/**
* 詞典全局緩存對象
* key : type 表示哪個類型
* value中的hashMap中的key : 表示value value表示text文本
*/
public static final Map<Integer,LinkedHashMap<Integer,String>> dic = new HashMap<>();
//ProjectApplication是當前springboot模塊的啟動類,Logger類是一個日志對象 作用是在緩存好詞典后,在控制台提示一下
Logger logger = LoggerFactory.getLogger(ProjectApplication.class);
@Resource
private SalesDictionaryMapper salesDictionaryMapper;
/**
* 容器創建自動執行
*/
@PostConstruct
public void init(){
initDictionary();
}
/**
* 初始化詞典
*/
public void initDictionary(){
List<SalesDictionary> all = salesDictionaryMapper.findAll();
all.forEach(sysDictionary -> {
LinkedHashMap<Integer, String> kv = dic.get(sysDictionary.getType());
if(kv == null){
kv = new LinkedHashMap<>();
}
kv.put(sysDictionary.getTypeValue(),sysDictionary.getTypeText());
dic.put(sysDictionary.getType(),kv);
});
//緩存完畢,logger對象在控制台打印下面的語句進行說明,可以不要
logger.debug("詞典初始化完成");
}
}
3.3.3 幾個知識點
3.3.3.1 Logger,是一個日志對象
java日志LoggerFactory.getLogger最全講解使用方法_滑稽的鼠標的博客-CSDN博客_loggerfactory.getlogger
下圖划線代碼就是在springboot的啟動類下創建一個日志對象,其實不是必須的
|
這個對象要做的就是在springboot啟動完畢並且詞典緩存后,在控制台提示一下,這不是必須要做的
|
3.3.3.2 @PostConstruct 這個注解注釋的方法會在整個類被注入spring容器后自動執行
springboot 啟動時加載數據(字典)到內存 - 簡書 (jianshu.com)
主要看文章中對 “@PostConstruct”的介紹 以及 存在“BiMap”這種東西,能把map的value也唯一化,不過我這里沒有用
下面是部分摘錄
@PostConstruct
spring中Constructor、@Autowired、@PostConstruct的順序(網上解釋比較清楚的版本)
要將對象p注入到對象a,那么首先就必須得生成對象p與對象a,才能執行注入。所以,如果一個類A中有個成員變量p被@Autowired注解,那么@Autowired注入是發生在A的構造方法執行完之后的。
如果想在生成對象時候完成某些初始化操作,而偏偏這些初始化操作又依賴於依賴注入,那么就無法在構造函數中實現。為此,可以使用@PostConstruct注解一個方法來完成初始化,@PostConstruct注解的方法將會在依賴注入完成后被自動調用。
Constructor >> @Autowired >> @PostConstruct
3.3.3.3 字典的數據類型 Map<Integer,LinkedHashMap<Integer,String>> dic
這是一個嵌套map,外層map的key對應type,value是一個LinkedHashMap,LinkedHashMap的key對應type_value,value對應type_text
實際使用時,可以根據自己的字典表格式變化,比如只建立一張數據字典表的情況下,type不用數字,而是用字符串,那么此時外層map的key就要用String
4 在VUE+springboot項目中使用數據字典表
4.1 緩存完畢的字典表樣式
數據字典表存到項目的map后,數據結構如圖
|
4.2 使用方式 直接使用
緩存數據字典表后,可以直接在controller類、serviceimpl類或者transfer類中使用“dic",IDEA會提示需要導入,直接導入即可
|
|
4.3 應用場景一 PO轉VO時進行轉義
在PO轉VO時,在transfer類中使用,第一個get要人為指定,第二個get直接將PO的屬性值傳入,這樣VO就能獲得對應的字符串
|
可以把數據庫的字典表的注釋放在實體類上 方便查看
|
4.4 應用場景二 令前端下拉列表動態生成
4.4.1 效果圖
前端基於VUE+ElementUI,要實現的效果如圖
|
4.4.2 后端
之前考慮的做法是每個字段單獨建表,后端分別查詢,然后傳給前端,一旦字段多了,那也太麻煩了
現在有了數據字典表,直接把數據字典表傳給前端即可
|
封裝類如圖,返回成功的方法的status和message已經用枚舉類預設好
|
postman請求結果一覽
|
4.4.3 前端
4.4.3.1 請求方法
前端基於VUE+ElementUI
前端向后端的controller發送請求,其中instance是自定義封裝好的類
|
4.4.3.2 methods
methods的代碼如下,獲取到數據后,存到VUE的屬性下
|
4.4.3.3 前端下拉列表代碼
ElementUI實現的下拉列表代碼
|
4.4.4 前端的知識點說明
后端沒啥要說的,倒是前端讓我吃盡了苦頭,下面是我遇到的問題和解決辦法
4.4.4.1 console.log顯示object
ajax請求后的返回結果顯示[object Object]的原因_IT_townlet的博客-CSDN博客
console.log時不要加字符串
|
4.4.4.2 后端返回的數據是Promise對象
打印后端傳進來的數據時返回數據是Promise對象_Olliverzhang的博客-CSDN博客
要給方法加同步鎖,即async和await
|
4.4.4.3 json取值方式 json對象[]
JSON取值(key是中文或者數字)方式詳解_Care_about的博客-CSDN博客
json的取值方式有好幾種,對於后端傳遞的這種嵌套map,需要手動指定一下外層map的key
|
4.4.4.4 ElementUI的下拉列表動態生成的方式
筆記: SpringBoot + VUE實現數據字典展示功能_CJG753951的博客-CSDN博客
下圖是文章摘錄,其中的取值方式啟發了我
|
動態生成的下拉列表,可以在遍歷json對象時,將獲得的元素拆開使用
|
可以對比寫死的情況,如圖
|
4.5 應用場景三 前端VUE表格列的動態轉義
筆記: SpringBoot + VUE實現數據字典展示功能_CJG753951的博客-CSDN博客
Vue 利用后端的數據字典和Map對象實現表格列字段動態轉義的處理方案 - 阿拉伯1999 - 博客園 (cnblogs.com)
這個場景與應用場景一相悖,畢竟如果PO轉VO時就把數字轉為字符串了,前端就沒必要轉了,我通常會依據應用場景一去實現轉義,所以這個我就不研究了,僅放在這里
5 數據字典表緩存到本地內存還是redis中
5.1 結論 依據公司技術選型而定
在實際使用時,需要在整個項目啟動時就執行查詢語句,把數據字典表整體保存到一個map中,這個map再存到redis或者本地內存中,然后在項目的各個類中隨調隨用
而到底是直接用static把map存到內存中還是直接把map存到redis中,就看以后的技術選型,從速度上看前者肯定更快,因為用的就是本地的內存
5.2 本文緩存到本地
本篇文章是將字典表存在本地內存中,所以使用了static修飾方法
|
5.3 緩存到redis的方式
如果要探究存到redis中,參見SpringBoot+Redis實現數據字典_蹊源的奇思妙想的博客-CSDN博客_springboot緩存數據字典
文章的部分知識點的個人理解如下
5.3.1 springUtils工具類
文章中的RedisDistUtil類用於獲取被spring管理的service接口對象,然后調用這個對象的方法實現轉義
|
為了能從spring容器中取出service接口對象,借助了springUtils工具類,這是一個自定義的工具類,用於從spring容器中取出對象
①本篇文章的數據字典表被定義為static 靜態的對象,被存在內存中,所以可以直接使用,不需要springUtils這種類
②如果不使用這個工具類,而是在當前類聲明要使用的對象並利用@Autowired等注解注入spring容器,那么還得先把最外層這個類(RedisDistUtil)注入spring容器,然而注入spring容器的類不是越多越好
③顯然,這個工具類要求springboot項目啟動,否則spring容器中是空的,方法getBean取不到東西就報空指針異常
|
5.3.2 RedisTemplate
文章中提到spring封裝了一個對象叫RedisTemplate 基於這個對象提供的方法 我們能操控redis儲存的數據
|
6 數據字典表和常量類對比
我沒有使用數據字典表時,直接在項目建了一個常量類,如圖
|
借助這個常量類也能實現轉義以及管理,但真的太麻煩了
比如轉義,代碼量可見一斑,而且每當需要新增一條“訂單動作”,我就需要到各個transform中為switch-case增加一條
|
再就是管理時,每新增一條,我就得准備一個常量名,寫起來特麻煩
|
現在固定值寫在一張數據庫表中 相比常量類就方便維護了
並且前端下拉菜單不再需要寫死 直接去讀字典表類的map即可