springboot集成redis之字典缓存

WuYiLong原创大约 7 分钟代码编程springbootredis

什么是redis的字典缓存?

Redis的缓存是Redis内部用于存储键值对数据结构的一种基础数据结构。在Redis中,所有的键值对都是通过字典这种数据结构来存储的。字典在Redis中扮演着核心角色,因为它不仅用于数据库中的键值对存储,还用于实现其他如哈希、集合等复杂数据结构。

以下是关于Redis字典缓存的一些关键点:

  1. 数据结构:Redis的字典使用哈希表作为底层实现,这样可以提供快速的查找、添加和删除操作。哈希表通常是一个数组,数组的每个元素是一个指向键值对结构的指针。
  2. 哈希冲突解决:当不同的键通过哈希函数映射到同一个位置时,Redis使用链表法来解决冲突。如果一个位置有多个键值对,它们会形成一个链表。
  3. rehash:随着键值对数量的增加或减少,为了维持哈希表的性能,Redis会进行rehash操作,即重新计算所有键的哈希值,并将它们重新分布到新的哈希表中。
  4. 渐进式rehash:为了避免rehash操作带来的性能问题,Redis使用渐进式rehash。它将rehash操作分散到对字典的每个添加、删除、查找和更新操作中,从而避免了一次性rehash可能导致的长时间延迟。
  5. 缓存作用:由于字典的高效访问特性,Redis可以快速读写数据,这使得Redis非常适合作为缓存系统使用。在字典中存储的数据可以直接从内存中访问,大大减少了数据读取的时间。
  6. 持久化:虽然字典是内存中的数据结构,但Redis支持将字典中的数据持久化到硬盘上,以保证在系统故障时数据不会丢失。
  7. 类型特定字典:Redis支持多种数据类型,如字符串、列表、集合、哈希、有序集合等,每种数据类型在内部都可能使用到字典结构来存储元数据或数据本身。

Redis的字典缓存是支撑其高性能的一个关键因素,它使得Redis能够以极快的速度处理大量的数据。

项目目录

image.png

image.png

代码实践

entity层

 1package com.wyl.redis.entity;
 2
 3import com.baomidou.mybatisplus.annotation.IdType;
 4import com.baomidou.mybatisplus.annotation.TableField;
 5import com.baomidou.mybatisplus.annotation.TableId;
 6import com.baomidou.mybatisplus.annotation.TableName;
 7import com.baomidou.mybatisplus.extension.activerecord.Model;
 8import lombok.Data;
 9
10import javax.persistence.*;
11import java.io.Serializable;
12import java.time.LocalDateTime;
13import java.util.Date;
14
15/**
16 * @Description 
17 * @Author wuyilong
18 * @Date 2024-07-03
19 */
20@Data
21@TableName("full_city")
22@Entity
23@Table(name="full_city")
24public class FullCity extends Model<FullCity> {
25
26    private static final long serialVersionUID = 1L;
27
28    /**
29     * 主键id
30     */
31    @TableId(value = "id", type = IdType.AUTO)
32    @Id
33    @GeneratedValue(strategy = GenerationType.IDENTITY)
34    private Long id;
35
36    /**
37     * 名称
38     */
39    @TableField("name")
40    private String name;
41
42    /**
43     * 行政编码
44     */
45    @TableField("code")
46    private String code;
47
48    /**
49     * 全名称
50     */
51    @TableField("full_name")
52    private String fullName;
53
54    /**
55     * 级别,1省,2市,3区,4街道
56     */
57    @TableField("level")
58    private Integer level;
59
60    /**
61     * 创建时间
62     */
63    @TableField("create_time")
64    private Date createTime;
65
66    /**
67     * 中心点
68     */
69    @TableField("center")
70    private String center;
71
72    /**
73     * 是否被撤销,0否,1是
74     */
75    @TableField("is_revoke")
76    private Integer isRevoke;
77
78    /**
79     * 父级编码
80     */
81    private String parentCode;
82
83
84    @Override
85    public Serializable pkVal() {
86        return this.id;
87    }
88
89}

service层

  1package com.wyl.redis.service.impl;
  2
  3import cn.hutool.core.lang.tree.Tree;
  4import cn.hutool.core.lang.tree.TreeUtil;
  5import cn.hutool.core.map.MapUtil;
  6import com.wyl.redis.bean.DictionaryBean;
  7import com.wyl.redis.constant.DictionaryConst;
  8import com.wyl.redis.entity.FullCity;
  9import com.wyl.redis.service.DictionaryOperate;
 10import com.wyl.redis.service.FullCityService;
 11import com.wyl.redis.vo.FullCityVo;
 12import lombok.extern.slf4j.Slf4j;
 13import org.springframework.beans.factory.annotation.Autowired;
 14import org.springframework.data.redis.core.RedisTemplate;
 15import org.springframework.stereotype.Service;
 16
 17import java.util.HashMap;
 18import java.util.List;
 19import java.util.Map;
 20import java.util.stream.Collectors;
 21
 22/**
 23 * @Description
 24 * @Author WuYiLong
 25 * @Date 2024/7/3 17:36
 26 */
 27@Slf4j
 28@Service
 29public class FullCityOperate implements DictionaryOperate {
 30
 31    @Autowired
 32    private FullCityService fullCityService;
 33
 34    @Autowired
 35    private RedisTemplate redisTemplate;
 36
 37    @Override
 38    public List list(String key) {
 39        if(!redisTemplate.hasKey(key)) {
 40            List<FullCity> list = fullCityService.list();
 41            List<DictionaryBean> dictionaryBeans = list.stream().map(m -> {
 42                DictionaryBean dictionaryBean = new DictionaryBean();
 43                dictionaryBean.setCode(m.getCode());
 44                dictionaryBean.setName(m.getName());
 45                dictionaryBean.setLevel(m.getLevel());
 46                dictionaryBean.setParentCode(m.getParentCode());
 47                return dictionaryBean;
 48            }).collect(Collectors.toList());
 49            redisTemplate.opsForValue().set(key,dictionaryBeans);
 50            return dictionaryBeans;
 51        }
 52        List<DictionaryBean> list = (List<DictionaryBean>)redisTemplate.opsForValue().get(key);
 53        return list;
 54    }
 55
 56    @Override
 57    public List<Tree<String>> tree(String key) {
 58        if(!redisTemplate.hasKey(key)) {
 59            List<FullCity> list = fullCityService.list();
 60            List<Tree<String>> build = TreeUtil.build(list, "0", (t1, t2) -> {
 61                t2.setId(t1.getCode());
 62                t2.setName(t1.getName());
 63                t2.setParentId(t1.getParentCode());
 64            });
 65            redisTemplate.opsForValue().set(key,build);
 66            return build;
 67        }
 68        List<Tree<String>> trees = (List<Tree<String>>)redisTemplate.opsForValue().get(key);
 69        return trees;
 70    }
 71
 72    @Override
 73    public String codeNameMap(String key, String code) {
 74        if(!redisTemplate.opsForHash().hasKey(key,code)) {
 75            FullCityVo fullCityVo = fullCityService.getByCode(code);
 76            if(fullCityVo != null) {
 77                redisTemplate.opsForHash().putIfAbsent(key,fullCityVo.getCode(),fullCityVo.getName());
 78                return fullCityVo.getName();
 79            }
 80            return null;
 81        }
 82        String name = (String)redisTemplate.opsForHash().get(key, code);
 83        return name;
 84    }
 85
 86    @Override
 87    public String nameCodeMap(String key, String name) {
 88        if(!redisTemplate.opsForHash().hasKey(key,name)) {
 89            FullCityVo fullCityVo = fullCityService.getByFullName(name);
 90            if(fullCityVo != null) {
 91                redisTemplate.opsForHash().putIfAbsent(key,fullCityVo.getFullName(),fullCityVo.getCode());
 92                return fullCityVo.getCode();
 93            }
 94            return null;
 95        }
 96        String code = (String)redisTemplate.opsForHash().get(key, name);
 97        return code;
 98    }
 99
100    @Override
101    public String supportType() {
102        return DictionaryConst.FULL_CITY;
103    }
104}
 1package com.wyl.redis.service.impl;
 2
 3import cn.hutool.core.lang.tree.Tree;
 4import com.wyl.redis.constant.DictionaryConst;
 5import com.wyl.redis.exception.BusinessException;
 6import com.wyl.redis.service.DictionaryOperate;
 7import lombok.extern.slf4j.Slf4j;
 8import org.springframework.beans.BeansException;
 9import org.springframework.beans.factory.annotation.Autowired;
10import org.springframework.context.ApplicationContext;
11import org.springframework.context.ApplicationContextAware;
12import org.springframework.data.redis.core.RedisTemplate;
13import org.springframework.stereotype.Component;
14
15import java.util.*;
16
17/**
18 * @Description
19 * @Author WuYiLong
20 * @Date 2024/7/3 17:23
21 */
22@Slf4j
23@Component
24public class DictionaryService implements ApplicationContextAware {
25
26    private Map<String,DictionaryOperate>  dictionaryMaps = new HashMap<>();
27
28    @Autowired
29    private RedisTemplate redisTemplate;
30
31    public DictionaryOperate buildDictionaryOperate(String key) {
32        DictionaryOperate dictionaryOperate = dictionaryMaps.get(key);
33        if(dictionaryOperate == null) {
34            throw new BusinessException("字典的key不存在");
35        }
36        return dictionaryOperate;
37    }
38
39    public List list(String key) {
40        String listKey = DictionaryConst.DIC+key+DictionaryConst.LIST;
41        if(key.contains(":")) {
42            String[] split = key.split(":");
43            key = split[0];
44            listKey = DictionaryConst.DIC+key+DictionaryConst.LIST+":"+split[1];
45        }
46        List list = buildDictionaryOperate(key).list(listKey);
47        return list;
48    }
49
50    public List<Tree<String>> tree(String key) {
51        String listKey = DictionaryConst.DIC+key+DictionaryConst.TREE;
52        if(key.contains(":")) {
53            String[] split = key.split(":");
54            key = split[0];
55            listKey = DictionaryConst.DIC+key+DictionaryConst.TREE+":"+split[1];
56        }
57        List<Tree<String>> tree =buildDictionaryOperate(key).tree(listKey);
58        return tree;
59    }
60
61    public String codeNameMap(String key, String code) {
62        String name = buildDictionaryOperate(key).codeNameMap(DictionaryConst.DIC+key+":codeNameMap", code);
63        return name;
64    }
65
66    public String nameCodeMap(String key, String name) {
67        String code = buildDictionaryOperate(key).nameCodeMap(DictionaryConst.DIC+key+":nameCodeMap", name);
68        return code;
69    }
70
71    public void refresh() {
72        Set keys = redisTemplate.keys("dic*");
73        keys.forEach(v->{
74            redisTemplate.delete(v);
75        });
76    }
77
78    @Override
79    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
80        Map<String, DictionaryOperate> dictionaryOperateMap = applicationContext.getBeansOfType(DictionaryOperate.class);
81        dictionaryOperateMap.forEach((k,v)->{
82            dictionaryMaps.put(v.supportType(),v);
83        });
84    }
85}

controller层

 1package com.wyl.redis.controller;
 2
 3import com.wyl.common.bean.ResponseData;
 4import com.wyl.redis.service.impl.DictionaryService;
 5import io.swagger.annotations.Api;
 6import io.swagger.annotations.ApiOperation;
 7import org.springframework.beans.factory.annotation.Autowired;
 8import org.springframework.web.bind.annotation.GetMapping;
 9import org.springframework.web.bind.annotation.RequestMapping;
10import org.springframework.web.bind.annotation.RequestParam;
11import org.springframework.web.bind.annotation.RestController;
12
13/**
14 *
15 * @Description
16 * @Author WuYiLong
17 * @Date 2024/7/8 10:21
18 */
19@Api(tags = "字典api")
20@RestController
21@RequestMapping(value = "dictionary")
22public class DictionaryController {
23
24    @Autowired
25    private DictionaryService dictionaryService;
26
27    @ApiOperation(value = "字典刷新")
28    @GetMapping(value = "refresh")
29    public ResponseData refresh() {
30        dictionaryService.refresh();
31        return ResponseData.success();
32    }
33
34    @ApiOperation(value = "字典列表")
35    @GetMapping(value = "list")
36    public ResponseData list(String key) {
37        return ResponseData.successInstance(dictionaryService.list(key));
38    }
39
40    @ApiOperation(value = "字典树")
41    @GetMapping(value = "tree")
42    public ResponseData tree(String key) {
43        return ResponseData.successInstance(dictionaryService.tree(key));
44    }
45
46    @ApiOperation(value = "根据code获取名称")
47    @GetMapping(value = "codeNameMap")
48    public ResponseData codeNameMap(String key, String code) {
49        return ResponseData.successInstance(dictionaryService.codeNameMap(key,code));
50    }
51
52    @ApiOperation(value = "根据名称获取code")
53    @GetMapping(value = "nameCodeMap")
54    public ResponseData nameCodeMap(String key, String name) {
55        return ResponseData.successInstance(dictionaryService.nameCodeMap(key, name));
56    }
57}

测试

根据code获取名称

image.png

image.png

字典列表

image.png

image.png

字典树

image.png

image.png

字典在redis客户端的存储

image.png

image.png

项目说明

只需要配置好本地的数据库,连接上自己本地的redis,启动项目,就会自动初始化数据库脚本到本地数据库。

 1package com.wyl.redis.config;
 2
 3import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
 4import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
 5import com.baomidou.dynamic.datasource.support.ScriptRunner;
 6import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
 7import lombok.extern.slf4j.Slf4j;
 8import org.springframework.beans.factory.annotation.Autowired;
 9import org.springframework.boot.ApplicationArguments;
10import org.springframework.boot.ApplicationRunner;
11import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
12import org.springframework.boot.context.properties.ConfigurationProperties;
13import org.springframework.boot.jdbc.DataSourceBuilder;
14import org.springframework.stereotype.Component;
15
16import javax.sql.DataSource;
17import java.util.Map;
18
19/**
20 * @Description 公共初始化配置
21 * @Author WuYiLong
22 * @Date 2024/7/8 9:38
23 */
24@Slf4j
25@ConditionalOnProperty(prefix = "init",value = "enabled",havingValue = "true")
26@Component
27public class InitConfig implements ApplicationRunner {
28
29    @Autowired
30    private DynamicDataSourceProperties dynamicDataSourceProperties;
31    @Override
32    public void run(ApplicationArguments args) throws Exception {
33        log.info("****************初始化数据库脚本开始*************");
34        Map<String, DataSourceProperty> datasource = dynamicDataSourceProperties.getDatasource();
35        DataSourceProperty master = datasource.get("master");
36        DataSource build = DataSourceBuilder
37                .create()
38                .url(master.getUrl())
39                .driverClassName(master.getDriverClassName())
40                .password(master.getPassword())
41                .type(master.getType())
42                .username(master.getUsername())
43                .build();
44
45        ScriptRunner scriptRunner = new ScriptRunner(true, ";");
46        scriptRunner.runScript(build,"classpath:/db/**");
47        log.info("****************初始化数据库脚本结束*************");
48
49    }
50}

在配置文件那里配置,设置init.enabled=true

1init:
2  enabled: false

项目地址

githubopen in new window

上次编辑于:
贡献者: wuyilong