Redis實戰(11)-哈希Hash典型應用場景實戰之系統數據字典實時觸發緩存存儲

概述:本系列博文所涉及的相關內容來源於debug親自錄製的實戰課程:緩存中間件Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰),感興趣的小夥伴可以點擊自行前往學習(畢竟以視頻的形式來掌握技術 會更快!) 文章所屬技術專欄:緩存中間件Redis技術入門與實戰

內容:在前文我們已經簡單介紹了Redis的數據類型~哈希Hash的底層存儲結構,很顯然,哈希Hash跟其他的數據結構還是有諸多不同之處的。其他的據結構幾乎都是:Key-Value的存儲,而Hash則是:Key – [Field-Value] 的存儲,也就是說其他數據結構的Value一般是確切的值,而Hash的Value是一系列的鍵值對,通常我們是這樣子稱呼Hash的存儲的:大Key爲實際的Key,小Key爲Field,而具體的取值爲Field對應的值。如下圖所示:

說實在的,它的作用還是很強大的,特別是在存儲“同種對象類型”的數據列表時哈希Hash更能體現其優勢,除此之外,其最大的、直觀上的作用便是“減少了緩存Key的數量”,而這主要還得得益於哈希Hash底層存儲數據時的存儲方式,如上圖所示!

接下來,我們便以實際項目開發中典型、常見的應用場景“系統數據字典實時觸發緩存存儲”爲案例一起來踐行哈希Hash的作用。

對於“數據字典模塊”,相信很多小夥伴都有所聽聞過,毫不誇張地講,幾乎每個項目都會有一個獨立的功能模塊,用於管理項目中各個業務模塊經常出現的“通用化、共性的、需要配置起來的東西”,這些通用化的東西我們可以稱之爲“數據字典”,對於這些東西我們一般會單獨開闢一個獨立的功能模塊,如“數據字典模塊”進行單獨維護管理!

對於上面這個解釋,可能有些小夥伴有點懵,下面我們舉個栗子吧,比如經常可以見到的數據字典:“性別Sex~其取值可以有:男=1;女=0;未知=2”;比如“支付狀態PayStatus~其取值可以有:1=未支付;2=已支付;3=已取消支付;4=已退款…”;再比如“訂單審覈狀態ReviewStatus~1=已保存/未審覈;2=已審覈;3=審覈成功;4=審覈失敗…”等等可以將其配置在“數據字典功能模塊”中將其維護起來,如下圖所示:

 

看到上面這張圖,有些機靈的小夥伴可能會立即聯想到哈希Hash的底層存儲結構(本文開篇的那張圖),會發現驚人的相似,就拿“性別Sex”這一數據字典爲例,它的取值爲“Female-女性”、“Male-男性”,這不就相當於哈希Hash的底層存儲結構嗎~Key=Sex,Field-Value對包含兩隊,分別是:Field=Female ~ Value=女性;Field=Male ~ Value=男性。

理解了這種數據關聯以及存儲之後,在後文的實戰中你就會發現代碼很容易理解,並且在實戰過後你或許會發出驚歎:“原來如此!”

除此之外,還有一種現象需要跟小夥伴分享分享,那就是“數據字典功能模塊”一旦配置好了某個“數據字典”之後,我們基本上會在好幾個月內都不會去重新修改它了,即有點“一勞永逸”的感覺!基於這個前提,我們可以將前端發起的請求實時訪問數據庫DB的“數據字典” 優化爲 基於緩存Redis的哈希Hash進行存儲與訪問,並且這種存儲是“實時”的,那我們就開始吧!

(1)同樣的道理,工欲善其事,必先利其器,我們首先需要建立一個數據庫表sys_config用於存儲管理員添加的數據字典,其DDL如下所示:

CREATE TABLE `sys_config` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(100) CHARACTER SET utf8mb4 NOT NULL COMMENT '字典類型',
  `name` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '字典名稱',
  `code` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '選項編碼',
  `value` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '選項取值',
  `order_by` int(11) DEFAULT '1' COMMENT '排序',
  `is_active` tinyint(4) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_type_code` (`type`,`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='字典配置表';

採用Mybatis的逆向工程生成該數據庫表的實體類Entity、Mapper操作接口及其對應的用於操作動態SQL的Mapper.xml,在這裏我們只貼出SysConfigMapper接口中一個相當重要的方法吧:  

//查詢目前數據字典表中所有可用的-已激活的數據字典列表
List<SysConfig> selectActiveConfigs();

其對應的動態SQL實現如下所示:  

  <select id="selectActiveConfigs" resultType="com.boot.debug.redis.model.entity.SysConfig">
    SELECT <include refid="Base_Column_List"/>
    FROM sys_config
    WHERE is_active = 1
    ORDER BY type, order_by ASC
  </select>

(2)緊接着,我們建立一個HashController,用於“新增數據字典”、“獲取緩存中所有的數據字典”以及“獲取特定編碼的數據字典取值列表”,其完整的源代碼如下所示:  

/**數據類型Hash散列-減少key存儲、類似於map-可以通過鍵取得其 “值” (可以對象列表...)
 * @Author:debug (SteadyJack) **/
@RestController
@RequestMapping("hash")
public class HashController extends AbstractController {

    @Autowired
    private HashService hashService;
    //新增數據字典
    @RequestMapping(value = "put",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public BaseResponse put(@RequestBody @Validated SysConfig config, BindingResult result){
        String checkRes= ValidatorUtil.checkResult(result);
        if (StrUtil.isNotBlank(checkRes)){
            return new BaseResponse(StatusCode.Fail.getCode(),checkRes);
        }
        BaseResponse response=new BaseResponse(StatusCode.Success);
        try {
            hashService.addSysConfig(config);

        }catch (Exception e){
            response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }
    //獲取緩存中所有的數據字典
    @RequestMapping(value = "get",method = RequestMethod.GET)
    public BaseResponse get(){
        BaseResponse response=new BaseResponse(StatusCode.Success);
        try {
            response.setData(hashService.getAll());

        }catch (Exception e){
            response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }
    //獲取緩存中某個特定編碼下數據字典的取值列表
    @RequestMapping(value = "get/type",method = RequestMethod.GET)
    public BaseResponse getType(@RequestParam String type){
        BaseResponse response=new BaseResponse(StatusCode.Success);
        try {
            response.setData(hashService.getByType(type));

        }catch (Exception e){
            response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }
}

(3)其中,hashService下那幾個方法的實現邏輯即爲真正要做的事情,其完整源代碼如下所示:  

/**hash數據類型-service
 * @Author:debug (SteadyJack)
 * @Link: weixin-> debug0868 qq-> 1948831260
 * @Date: 2019/10/31 21:07
 **/
@Service
public class HashService {
    private static final Logger log= LoggerFactory.getLogger(HashService.class);

    @Autowired
    private SysConfigMapper sysConfigMapper;

    @Autowired
    private HashRedisService hashRedisService;

    //TODO:添加數據字典及其對應的選項(field-value)
    @Transactional(rollbackFor = Exception.class)
    public Integer addSysConfig(SysConfig config) throws Exception{
        int res=sysConfigMapper.insertSelective(config);
        if (res>0){
            //TODO:實時觸發數據字典的hash存儲
            hashRedisService.cacheConfigMap();
        }
        return config.getId();
    }

    //TODO:取出緩存中所有的數據字典列表
    public Map<String,List<SysConfig>> getAll() throws Exception{
        return hashRedisService.getAllCacheConfig();
    }

    //TODO:取出緩存中特定的數據字典列表
    public List<SysConfig> getByType(final String type) throws Exception{
        return hashRedisService.getCacheConfigByType(type);
    }
}

(4)而hashService中實現數據字典的實時存取又是交給了HashRedisService相應的方法邏輯進行處理,其對應的完整源代碼如下所示:  

/**hash緩存服務 @Author:debug (SteadyJack)  weixin-> debug0868 qq-> 1948831260**/
@Service
public class HashRedisService {
    private static final Logger log= LoggerFactory.getLogger(HashRedisService.class);
    @Autowired
    private SysConfigMapper sysConfigMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    //TODO:實時獲取所有有效的數據字典列表-轉化爲map-存入hash緩存中
    @Async
    public void cacheConfigMap(){
        try {
            List<SysConfig> configs=sysConfigMapper.selectActiveConfigs();
            if (configs!=null && !configs.isEmpty()){
                Map<String,List<SysConfig>> dataMap= Maps.newHashMap();

                //TODO:所有的數據字典列表遍歷 -> 轉化爲 hash存儲的map
                configs.forEach(config -> {
                    List<SysConfig> list=dataMap.get(config.getType());
                    if (list==null || list.isEmpty()){
                        list= Lists.newLinkedList();
                    }
                    list.add(config);
                    dataMap.put(config.getType(),list);
                });
                //TODO:存儲到緩存hash中
                HashOperations<String,String,List<SysConfig>> hashOperations=redisTemplate.opsForHash();
                hashOperations.putAll(Constant.RedisHashKeyConfig,dataMap);
            }
        }catch (Exception e){
            log.error("實時獲取所有有效的數據字典列表-轉化爲map-存入hash緩存中-發生異常:",e.fillInStackTrace());
        }
    }

    //TODO:從緩存hash中獲取所有的數據字典配置map
    public Map<String,List<SysConfig>> getAllCacheConfig(){
        Map<String,List<SysConfig>> map=Maps.newHashMap();
        try {
            HashOperations<String,String,List<SysConfig>> hashOperations=redisTemplate.opsForHash();
            map=hashOperations.entries(Constant.RedisHashKeyConfig);
        }catch (Exception e){
            log.error("從緩存hash中獲取所有的數據字典配置map-發生異常:",e.fillInStackTrace());
        }
        return map;
    }

    //TODO:從緩存hash中獲取特定的數據字典列表
    public List<SysConfig> getCacheConfigByType(final String type){
        List<SysConfig> list=Lists.newLinkedList();
        try {
            HashOperations<String,String,List<SysConfig>> hashOperations=redisTemplate.opsForHash();
            list=hashOperations.get(Constant.RedisHashKeyConfig,type);
        }catch (Exception e){
            log.error("從緩存hash中獲取特定的數據字典列表-發生異常:",e.fillInStackTrace());
        }
        return list;
    }
}

至此,我們已經完成了哈希Hash典型應用場景“系統數據字典的實時存取”的代碼實戰了,相應的代碼的含義我們也在代碼中做了相應的註釋!如果有疑問的地方,各位小夥伴可以加Debug的聯繫方式進行交流(代碼中就有我的交流聯繫方式哦!),下面我們基於Postman進行一波測試吧!

A.首先是往數據庫中已有的某個數據字典添加某些具體的取值列表(Field-Value),如下幾張圖所示:

 

 

B.最後是往數據庫中添加一個全新的數據字典及其對應的取值列表(Field-Value),如下幾張圖所示:

 

好了,本篇文章我們就介紹到這裏了,建議各位小夥伴一定要照着文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談者”!

對Redis相關技術棧以及實際應用場景實戰感興趣的小夥伴可以前往debug搭建的技術社區的課程中心進行學習觀看:程序員實戰基地 !其他相關的技術,感興趣的小夥伴可以關注底部debug的技術公衆號,一起學習、共同成長!

補充:

1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:https://gitee.com/steadyjack/SpringBootRedis

2、目前debug已將本文所涉及的內容整理錄製成視頻教程,感興趣的小夥伴可以前往觀看學習:https://edu.csdn.net/course/detail/26619

3、關注一下debug的技術微信公衆號,最新的技術文章、課程以及技術專欄將會第一時間在公衆號發佈哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章