Redis实战(11)-哈希Hash典型应用场景实战之系统数据字典实时触发缓存存储 - Go语言中文社区

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的技术微信公众号,最新的技术文章、课程以及技术专栏将会第一时间在公众号发布哦!

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u013871100/article/details/104424785
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 17:52:17
  • 阅读 ( 1311 )
  • 分类:Redis

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢