文章

Redis数据类型实战

Redis数据类型实战

list有序列表

Redis的列表List中添加数据时,具有“先进先出”的特性,即所谓的“FIFO”(有点队列Queue的特性!),而且数据是紧凑、一个挨着一个存储的!

当我们在往缓存Redis的列表List添加数据时,可以采用“LPush 即从左边的方向添加”的方式往缓存Redis的List中添加,然后再采用“LPop 即从左边的方向弹出数据”或者“RPop 即从右边的方向弹出数据”的方式获取这一有序存储的列表数据!

上下架商品

电商平台~商家添加/下架商品时如何将其商品列表有序存储至缓存Redis的List中,每次获取商家当前的商品列表时可以直接从缓存中读取,减少每个商家在每次登陆之后都需要走数据库DB频繁查询 所带来的压力!

产品信息表

1
2
3
4
5
6
7
8
9
CREATE TABLE `product` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '商品名称',
  `user_id` int(11) NOT NULL COMMENT '所属商户id',
  `scan_total` int(255) DEFAULT NULL COMMENT '浏览量',
  `is_active` tinyint(255) DEFAULT '1' COMMENT '是否有效',
  PRIMARY KEY (`id`),
  KEY `indx_scan_total` (`scan_total`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商户商品表';

主逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Service
public class ListService {

    public static final Logger log= LoggerFactory.getLogger(ListService.class);

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //添加商品
    @Transactional(rollbackFor = Exception.class)
    public Integer addProduct(Product product) throws Exception{
        if (product!=null){
            product.setId(null);
	   //将该商品塞入数据库DB中
            productMapper.insertSelective(product);
            Integer id=product.getId();

            if (id>0){
			  //将该商品塞入缓存Redis中
                this.pushRedisService(product);
            }
            return id;
        }
        return -1;
    }

    //TODO:往缓存中塞信息-可以抽取到ListRedisService
    private void pushRedisService(final Product product) throws Exception{
        ListOperations<String,Product> listOperations=redisTemplate.opsForList();
        listOperations.leftPush(Constant.RedisListPrefix+product.getUserId(),product);
    }

    //获取历史发布的商品列表
    public List<Product> getHistoryProducts(final Integer userId) throws Exception{
        List<Product> list= Lists.newLinkedList();

        ListOperations<String,Product> listOperations=redisTemplate.opsForList();
        final String key=Constant.RedisListPrefix+userId;
        //TODO:倒序->userID=10010 ->Rabbitmq入门与实战,Redis入门与实战,SpringBoot项目实战
        list=listOperations.range(key,0,listOperations.size(key));
        log.info("--倒序:{}",list);

        //TODO:顺序->userID=10010 ->SpringBoot项目实战,Redis入门与实战,Rabbitmq入门与实战
        //Collections.reverse(list);
        //log.info("--顺序:{}",list);

        //TODO:弹出来移除的方式
        //Product entity=listOperations.rightPop(key);
        //while (entity!=null){
            //list.add(entity);
            //entity=listOperations.rightPop(key);
        //}
        return list;
    }
}

队列广播消息

电商应用~平台管理员在平台发布活动公告信息之后,除了将公告信息塞入数据库DB之外,同时以LPush的方式将其塞入缓存Redis的列表List中,并在接口的另一端开启定时检测的方式,随时检测缓存中指定的列表Redis是否有通告信息过来,如果有,则采取RPop的方式弹出该公告信息,并以邮件的形式发送给商户!

通告信息表

1
2
3
4
5
6
7
CREATE TABLE `notice` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '通告标题',
  `content` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '内容',
  `is_active` tinyint(4) DEFAULT '1',
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8 COMMENT='通告';

主逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    @Autowired
    private NoticeMapper noticeMapper;

    //创建通告
    @Transactional(rollbackFor = Exception.class)
    public void pushNotice(Notice notice) throws Exception{
        if (notice!=null){
            notice.setId(null);
            //TODO:将通告信息塞入数据库DB中
            noticeMapper.insertSelective(notice);
            final Integer id=notice.getId();

            if (id>0){
                //TODO:塞入List列表中(队列),准备被拉取异步通知至不同的商户的邮箱 - applicationEvent&Listener;Rabbitmq;jms
                ListOperations<String,Notice> listOperations=redisTemplate.opsForList();
                listOperations.leftPush(Constant.RedisListNoticeKey,notice);
            }
        }
    }

定时任务调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//Redis列表-队列的消费者监听器
@Component
@EnableScheduling
public class ListListenerScheduler {

    private static final Logger log= 
    LoggerFactory.getLogger(ListListenerScheduler.class);

    private static final String listenKey= Constant.RedisListNoticeKey;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private EmailService emailService;

    //TODO:近实时的定时任务检测
    //@Scheduled(cron = "0/10 * * * * ?")
    @Scheduled(cron = "0/59 * * * * ?")
    public void schedulerListenNotice(){
        log.info("----定时任务调度队列监听、检测通告消息,监听list中的数据");

        ListOperations<String,Notice> listOperations=redisTemplate.opsForList();
        Notice notice=listOperations.rightPop(listenKey);
        while (notice!=null){
            //TODO:发送给到所有的商户的邮箱
            this.noticeUser(notice);
            notice=listOperations.rightPop(listenKey);
        }
    }

    //TODO:发送通知给到不同的商户
    @Async("threadPoolTaskExecutor")
    private void noticeUser(Notice notice){
        if (notice!=null){
            //TODO:查询获取所有商户信息
            List<User> list=userMapper.selectList();

            //TODO:线程池/多线程触发群发邮件
            try {
                if (list!=null && !list.isEmpty()){
                    ExecutorService executorService=Executors.newFixedThreadPool(4);
                    List<NoticeThread> threads= Lists.newLinkedList();

                    list.forEach(user -> {
                        threads.add(new NoticeThread(user,notice,emailService));
                    });

                    executorService.invokeAll(threads);
                }
            }catch (Exception e){
                log.error("近实时的定时任务检测-发送通知给到不同的商户-法二-线程池/多线程触发-发生异常:",e.fillInStackTrace());
            }
        }
    }
}

set无序列表

Set “无序”、“唯一”,即集合Set中存储的元素是没有顺序且不重复的!

其底层设计亦具有“异曲同工”之妙,即采用哈希表来实现的,故而其相应的操作如添加、删除、查找的复杂度都是 O(1) 。

用户注册过滤重复提交信息

用户在前端提交信息时重复点击按钮多次,如果此时不采取相应的限制措施,那么很有可能会在数据库表中出现多条相同的数据条目

用户信息表user

1
2
3
4
5
6
7
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '姓名',
  `email` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '邮箱',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_email` (`email`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

主逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//集合set服务处理逻辑
@Service
public class SetService {
    private static final Logger log= LoggerFactory.getLogger(SetService.class);

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //TODO:用户注册
    @Transactional(rollbackFor = Exception.class)
    public Integer registerUser(User user) throws Exception{
        if (this.exist(user.getEmail())){
            throw new RuntimeException(StatusCode.UserEmailHasExist.getMsg());
        }
        int res=userMapper.insertSelective(user);
        if (res>0){
            SetOperations<String,String> setOperations=redisTemplate.opsForSet();
            setOperations.add(Constant.RedisSetKey,user.getEmail());
        }
        return user.getId();
    }

    //TODO:判断邮箱是否已存在于缓存中
    private Boolean exist(final String email) throws Exception{
        //TODO:写法二
        SetOperations<String,String> setOperations=redisTemplate.opsForSet();
        Long size=setOperations.size(Constant.RedisSetKey);
        if (size>0 &&  setOperations.isMember(Constant.RedisSetKey,email)){
            return true;
        }else{
            User user=userMapper.selectByEmail(email);
            if (user!=null){
                setOperations.add(Constant.RedisSetKey,user.getEmail());
                return true;
            }else{
                return false;
            }
        }
    }

在插入用户信息进入数据库之前,我们需要判断该用户是否存在于缓存集合Set中,如果已经存在,则告知前端该“用户邮箱”已经存在(在这里我们认为用户的邮箱是唯一的,当然啦,你可以调整为“用户名”唯一),如果缓存集合Set中不存在该邮箱,则插入数据库中,并在“插入数据库表成功” 之后,将该用户邮箱塞到缓存集合Set中去即可。

在“判断缓存Set中是否已经存在该邮箱”的逻辑中,是先判断缓存中是否存在,如果不存在,为了保险,我们会再去数据库查询邮箱是否真的不存在,如果真的是不存在,则将其“第一次”添加进缓存Set中(这样子可以在某种程度避免前端在重复点击提交按钮时,产生瞬时高并发的现象,从而降低并发安全的风险)!

如果在插入数据库时“掉链子”了,即发生异常了导致没有插进去,但是这个时候我们在“判断缓存集合Set中是否存在该邮箱时已经将该邮箱添加进缓存中一次了”,故而该邮箱将永远不能注册了(但是实际上该邮箱并没有真正插入到数据库中哦!)

改造后的主逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Transactional(rollbackFor = Exception.class)
    public Integer registerUser(User user) throws Exception{
        if (this.exist(user.getEmail())){
            throw new RuntimeException(StatusCode.UserEmailHasExist.getMsg());
        }

        int res=0;
        try{
            res=userMapper.insertSelective(user);
            if (res>0){
                redisTemplate.opsForSet().add(Constant.RedisSetKey,user.getEmail());
            }
        }catch (Exception e){
            throw e;
        }finally {
            //TODO:如果res不大于0,即代表插入到数据库发生了异常,
//TODO:这个时候得将缓存Set中该邮箱移除掉
            //TODO:因为在判断是否存在时 加入了一次,不移除掉的话,就永远注册不了该邮箱了
            if (res<=0){
                redisTemplate.opsForSet().remove(Constant.RedisSetKey,user.getEmail());
            }
        }

        return user.getId();
    }

试卷题目乱序

当我们向集合Set伸手要一个元素时,其底层会随机地给我们发一个元素!

业务流程

我们将主要做两件事情:

A、项目启动后从数据库DB中拉出所有的试卷题目列表,并将其塞入缓存Set集合中

B、开发一请求方法,用于从缓存中获取随机、无序且唯一的 N 道试题,并将其返回给当前成功登录考试系统的考生

试卷题目表

1
2
3
4
5
6
7
8
9
10
CREATE TABLE `problem` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(150) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '问题标题',
  `choice_a` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '选项A',
  `choice_b` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '选项B',
  `is_active` tinyint(4) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
  `order_by` tinyint(4) DEFAULT '0' COMMENT '排序',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_title` (`title`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='试卷题目表';

试卷题目服务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Service
public class ProblemService {
    private static final Logger log= LoggerFactory.getLogger(ProblemService.class);

    @Autowired
    private ProblemMapper problemMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //TODO:项目启动拉取出数据库中的题库,并塞入缓存Set集合中
    @PostConstruct
    public void init(){
        initDBToCache();
    }

    //TODO:拉取出数据库中的所有题目列表,并塞入缓存Set集合中
    private void initDBToCache(){
        try {
            //redisTemplate.delete(Arrays.asList(Constant.RedisSetProblemKey,Constant.RedisSetProblemsKey));
            SetOperations<String,Problem> setOperations=redisTemplate.opsForSet();
            Set<Problem> set=problemMapper.getAll();
            if (set!=null && !set.isEmpty()){
                set.forEach(problem -> setOperations.add(Constant.RedisSetProblemKey,problem));
                set.forEach(problem -> setOperations.add(Constant.RedisSetProblemsKey,problem));
            }
        }catch (Exception e){
            log.error("项目启动拉取出数据库中的题库,并塞入缓存Set集合中-发生异常:",e.fillInStackTrace());
        }
    }

    //TODO:从缓存中获取随机的、乱序的试题列表
    public Set<Problem> getRandomEntitys(Integer total){
        Set<Problem> problems=Sets.newHashSet();
        try {
            SetOperations<String,Problem> setOperations=redisTemplate.opsForSet();
            problems=setOperations.distinctRandomMembers(Constant.RedisSetProblemsKey,total);
        }catch (Exception e){
            log.error("从缓存中获取随机的、乱序的试题列表-发生异常:",e.fillInStackTrace());
        }
        return problems;
    }
}

SortedSet有序集合

有序集合SortedSet,这种数据结构延伸了集合Set的“元素唯一/不重复”的特性,但却有一点不同于集合Set,那就是SortedSet的成员元素具有“有序性”,而其“有序性”的实现是通过“添加成员时附带一个double类型的参数:分数”

游戏充值排行榜

用户充值模块流程

用户充值表

1
2
3
4
5
6
7
8
CREATE TABLE `phone_fare` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `phone` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '手机号码',
  `fare` decimal(10,2) DEFAULT NULL COMMENT '充值金额',
  `is_active` tinyint(4) DEFAULT '1' COMMENT '是否有效(1=是;0=否)',
  PRIMARY KEY (`id`),
  KEY `idx_phone` (`phone`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='手机充值记录';

业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    //TODO:新增/手机话费充值 记录 v2
    @Transactional(rollbackFor = Exception.class)
    public Integer addRecordV2(PhoneFare fare) throws Exception{
        log.info("----sorted set话费充值记录新增V2:{} ",fare);

        // 先入库,入库成功塞缓存
        int res=fareMapper.insertSelective(fare);
        if (res>0){
            FareDto dto=new FareDto(fare.getPhone());

            ZSetOperations<String,FareDto> zSetOperations=redisTemplate.opsForZSet();
            Double oldFare=zSetOperations.score(Constant.RedisSortedSetKey2,dto);
            if (oldFare!=null){
                //TODO:表示之前该手机号对应的用户充过值了,需要进行叠加
                zSetOperations.incrementScore(Constant.RedisSortedSetKey2,dto,fare.getFare().doubleValue());
            }else{
                //TODO:表示第一次充值
                zSetOperations.add(Constant.RedisSortedSetKey2,dto,fare.getFare().doubleValue());
            }
        }
        return fare.getId();
    }

获取充值排行榜

业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    //TODO:获取充值排行榜V2
    public List<PhoneFare> getSortFaresV2(){
        List<PhoneFare> list= Lists.newLinkedList();

        final String key=Constant.RedisSortedSetKey2;
        ZSetOperations<String,FareDto> zSetOperations=redisTemplate.opsForZSet();
        final Long size=zSetOperations.size(key);

        Set<ZSetOperations.TypedTuple<FareDto>> set=zSetOperations.reverseRangeWithScores(key,0L,size);
        if (set!=null && !set.isEmpty()){
            set.forEach(tuple -> {
                PhoneFare fare=new PhoneFare();
                // 金额
                fare.setFare(BigDecimal.valueOf(tuple.getScore()));
                //手机号
                fare.setPhone(tuple.getValue().getPhone());

                list.add(fare);
            });
        }
        return list;
    }

虽然我们基本上已经实现了该业务场景几乎所有的功能模块,但是我们却忽略了其他两种情况:

A.如果“充值排行榜”这一功能模块是增量式的需求,那么上线时如何去处理历史的用户充值记录呢?你总不能说我们的“充值排行榜”对于以往充值的用户记录不生效吧?

B.如果用户充值后插入数据库DB成功、但是插入缓存Cache失败(DB事务不回滚的前提),那毫无疑问,最终得出来的“充值排行榜”一定是不准确的(因为我们是直接从缓存Redis中获取的)!

能保证“最终一致性”的充值排行榜的解决方案,那就是万能的定时任务调度

它要完成的任务就是开启一个定时时钟,基于数据库DB中的“用户充值记录表”,借助数据库提供的Order By、Group By等查询得出目前为止所有有效用户的“充值排行榜”

定时任务调度类PhoneFareScheduler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//补偿机制:手机号码充值排行榜
@Component
public class PhoneFareScheduler {
    private static final Logger log= LoggerFactory.getLogger(PhoneFareScheduler.class);

    @Autowired
    private PhoneFareMapper phoneFareMapper;

    @Autowired
    private RedisTemplate redisTemplate;
    //时间频度设定为30min,当然啦,具体的设定要根据实际情况而定
    @Scheduled(cron = "0 0/30 * * * ?")
    public void sortFareScheduler(){
        log.info("--补偿性手机号码充值排行榜-定时任务");

        this.cacheSortResult();
    }

    @Async("threadPoolTaskExecutor")
    private void cacheSortResult(){
        try {
            ZSetOperations<String,FareDto> zSetOperations=redisTemplate.opsForZSet();

            List<PhoneFare> list=phoneFareMapper.getAllSortFares();
            if (list!=null && !list.isEmpty()){
                redisTemplate.delete(Constant.RedisSortedSetKey2);

                list.forEach(fare -> {
                    FareDto dto=new FareDto(fare.getPhone());
                    zSetOperations.add(Constant.RedisSortedSetKey2,dto,fare.getFare().doubleValue());
                });
            }
        }catch (Exception e){
            log.error("--补偿性手机号码充值排行榜-定时任务-发生异常:",e.fillInStackTrace());
        }
    }
}

其中,phoneFareMapper.getAllSortFares() 的作用就是前往数据库Mysql,通过Group By、Order By和SUM等查询得到排行榜,其完整的动态SQL如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<!--基于数据库的补偿排名机制-->
  <select id="getAllSortFares" resultType="com.boot.debug.redis.model.entity.PhoneFare">
    SELECT
        phone,
        SUM(fare) AS fare
    FROM
        phone_fare
    GROUP BY
        phone
    ORDER BY
        fare DESC
  </select>

除此之外,@Async(“threadPoolTaskExecutor”) 的作用便是采用“线程池-多线程的方式异步执行定时任务”,故而我们需要作一个全局的Config,用于配置线程池-多线程的相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//线程池-多线程配置
public class ThreadConfig {
    @Bean("threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setKeepAliveSeconds(10);
        executor.setQueueCapacity(8);

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

hash表

哈希Hash跟其他的数据结构还是有诸多不同之处的。其他的据结构几乎都是:Key-Value的存储,而Hash则是:Key – [Field-Value] 的存储,也就是说其他数据结构的Value一般是确切的值,而Hash的Value是一系列的键值对,通常我们是这样子称呼Hash的存储的:大Key为实际的Key,小Key为Field,而具体的取值为Field对应的值

它的作用还是很强大的,特别是在存储“同种对象类型”的数据列表时哈希Hash更能体现其优势,除此之外,其最大的、直观上的作用便是“减少了缓存Key的数量”

数据字典模块实时触发存储

经常可以见到的数据字典:“性别Sex~其取值可以有:男=1;女=0;未知=2”;比如“支付状态PayStatus~其取值可以有:1=未支付;2=已支付;3=已取消支付;4=已退款…”;再比如“订单审核状态ReviewStatus~1=已保存/未审核;2=已审核;3=审核成功;4=审核失败…”等等可以将其配置在“数据字典功能模块”中将其维护起来

数据库表sys_config

1
2
3
4
5
6
7
8
9
10
11
12
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='字典配置表';

业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//hash数据类型-service
@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);
    }
}

HashRedisService相应的方法逻辑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@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;
    }
}
本文由作者按照 CC BY 4.0 进行授权