springboot集成redis之字典缓存

什么是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能够以极快的速度处理大量的数据。

项目目录

代码实践

entity层
java 复制代码
package com.wyl.redis.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * @Description 
 * @Author wuyilong
 * @Date 2024-07-03
 */
@Data
@TableName("full_city")
@Entity
@Table(name="full_city")
public class FullCity extends Model<FullCity> {

    private static final long serialVersionUID = 1L;

    /**
     * 主键id
     */
    @TableId(value = "id", type = IdType.AUTO)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 名称
     */
    @TableField("name")
    private String name;

    /**
     * 行政编码
     */
    @TableField("code")
    private String code;

    /**
     * 全名称
     */
    @TableField("full_name")
    private String fullName;

    /**
     * 级别,1省,2市,3区,4街道
     */
    @TableField("level")
    private Integer level;

    /**
     * 创建时间
     */
    @TableField("create_time")
    private Date createTime;

    /**
     * 中心点
     */
    @TableField("center")
    private String center;

    /**
     * 是否被撤销,0否,1是
     */
    @TableField("is_revoke")
    private Integer isRevoke;

    /**
     * 父级编码
     */
    private String parentCode;


    @Override
    public Serializable pkVal() {
        return this.id;
    }

}
service层
java 复制代码
package com.wyl.redis.service.impl;

import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.map.MapUtil;
import com.wyl.redis.bean.DictionaryBean;
import com.wyl.redis.constant.DictionaryConst;
import com.wyl.redis.entity.FullCity;
import com.wyl.redis.service.DictionaryOperate;
import com.wyl.redis.service.FullCityService;
import com.wyl.redis.vo.FullCityVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author WuYiLong
 * @Date 2024/7/3 17:36
 */
@Slf4j
@Service
public class FullCityOperate implements DictionaryOperate {

    @Autowired
    private FullCityService fullCityService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List list(String key) {
        if(!redisTemplate.hasKey(key)) {
            List<FullCity> list = fullCityService.list();
            List<DictionaryBean> dictionaryBeans = list.stream().map(m -> {
                DictionaryBean dictionaryBean = new DictionaryBean();
                dictionaryBean.setCode(m.getCode());
                dictionaryBean.setName(m.getName());
                dictionaryBean.setLevel(m.getLevel());
                dictionaryBean.setParentCode(m.getParentCode());
                return dictionaryBean;
            }).collect(Collectors.toList());
            redisTemplate.opsForValue().set(key,dictionaryBeans);
            return dictionaryBeans;
        }
        List<DictionaryBean> list = (List<DictionaryBean>)redisTemplate.opsForValue().get(key);
        return list;
    }

    @Override
    public List<Tree<String>> tree(String key) {
        if(!redisTemplate.hasKey(key)) {
            List<FullCity> list = fullCityService.list();
            List<Tree<String>> build = TreeUtil.build(list, "0", (t1, t2) -> {
                t2.setId(t1.getCode());
                t2.setName(t1.getName());
                t2.setParentId(t1.getParentCode());
            });
            redisTemplate.opsForValue().set(key,build);
            return build;
        }
        List<Tree<String>> trees = (List<Tree<String>>)redisTemplate.opsForValue().get(key);
        return trees;
    }

    @Override
    public String codeNameMap(String key, String code) {
        if(!redisTemplate.opsForHash().hasKey(key,code)) {
            FullCityVo fullCityVo = fullCityService.getByCode(code);
            if(fullCityVo != null) {
                redisTemplate.opsForHash().putIfAbsent(key,fullCityVo.getCode(),fullCityVo.getName());
                return fullCityVo.getName();
            }
            return null;
        }
        String name = (String)redisTemplate.opsForHash().get(key, code);
        return name;
    }

    @Override
    public String nameCodeMap(String key, String name) {
        if(!redisTemplate.opsForHash().hasKey(key,name)) {
            FullCityVo fullCityVo = fullCityService.getByFullName(name);
            if(fullCityVo != null) {
                redisTemplate.opsForHash().putIfAbsent(key,fullCityVo.getFullName(),fullCityVo.getCode());
                return fullCityVo.getCode();
            }
            return null;
        }
        String code = (String)redisTemplate.opsForHash().get(key, name);
        return code;
    }

    @Override
    public String supportType() {
        return DictionaryConst.FULL_CITY;
    }
}
java 复制代码
package com.wyl.redis.service.impl;

import cn.hutool.core.lang.tree.Tree;
import com.wyl.redis.constant.DictionaryConst;
import com.wyl.redis.exception.BusinessException;
import com.wyl.redis.service.DictionaryOperate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @Description
 * @Author WuYiLong
 * @Date 2024/7/3 17:23
 */
@Slf4j
@Component
public class DictionaryService implements ApplicationContextAware {

    private Map<String,DictionaryOperate>  dictionaryMaps = new HashMap<>();

    @Autowired
    private RedisTemplate redisTemplate;

    public DictionaryOperate buildDictionaryOperate(String key) {
        DictionaryOperate dictionaryOperate = dictionaryMaps.get(key);
        if(dictionaryOperate == null) {
            throw new BusinessException("字典的key不存在");
        }
        return dictionaryOperate;
    }

    public List list(String key) {
        String listKey = DictionaryConst.DIC+key+DictionaryConst.LIST;
        if(key.contains(":")) {
            String[] split = key.split(":");
            key = split[0];
            listKey = DictionaryConst.DIC+key+DictionaryConst.LIST+":"+split[1];
        }
        List list = buildDictionaryOperate(key).list(listKey);
        return list;
    }

    public List<Tree<String>> tree(String key) {
        String listKey = DictionaryConst.DIC+key+DictionaryConst.TREE;
        if(key.contains(":")) {
            String[] split = key.split(":");
            key = split[0];
            listKey = DictionaryConst.DIC+key+DictionaryConst.TREE+":"+split[1];
        }
        List<Tree<String>> tree =buildDictionaryOperate(key).tree(listKey);
        return tree;
    }

    public String codeNameMap(String key, String code) {
        String name = buildDictionaryOperate(key).codeNameMap(DictionaryConst.DIC+key+":codeNameMap", code);
        return name;
    }

    public String nameCodeMap(String key, String name) {
        String code = buildDictionaryOperate(key).nameCodeMap(DictionaryConst.DIC+key+":nameCodeMap", name);
        return code;
    }

    public void refresh() {
        Set keys = redisTemplate.keys("dic*");
        keys.forEach(v->{
            redisTemplate.delete(v);
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, DictionaryOperate> dictionaryOperateMap = applicationContext.getBeansOfType(DictionaryOperate.class);
        dictionaryOperateMap.forEach((k,v)->{
            dictionaryMaps.put(v.supportType(),v);
        });
    }
}
controller层
java 复制代码
package com.wyl.redis.controller;

import com.wyl.common.bean.ResponseData;
import com.wyl.redis.service.impl.DictionaryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 * @Description
 * @Author WuYiLong
 * @Date 2024/7/8 10:21
 */
@Api(tags = "字典api")
@RestController
@RequestMapping(value = "dictionary")
public class DictionaryController {

    @Autowired
    private DictionaryService dictionaryService;

    @ApiOperation(value = "字典刷新")
    @GetMapping(value = "refresh")
    public ResponseData refresh() {
        dictionaryService.refresh();
        return ResponseData.success();
    }

    @ApiOperation(value = "字典列表")
    @GetMapping(value = "list")
    public ResponseData list(String key) {
        return ResponseData.successInstance(dictionaryService.list(key));
    }

    @ApiOperation(value = "字典树")
    @GetMapping(value = "tree")
    public ResponseData tree(String key) {
        return ResponseData.successInstance(dictionaryService.tree(key));
    }

    @ApiOperation(value = "根据code获取名称")
    @GetMapping(value = "codeNameMap")
    public ResponseData codeNameMap(String key, String code) {
        return ResponseData.successInstance(dictionaryService.codeNameMap(key,code));
    }

    @ApiOperation(value = "根据名称获取code")
    @GetMapping(value = "nameCodeMap")
    public ResponseData nameCodeMap(String key, String name) {
        return ResponseData.successInstance(dictionaryService.nameCodeMap(key, name));
    }
}

测试

根据code获取名称
字典列表
字典树
字典在redis客户端的存储

项目说明

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

java 复制代码
package com.wyl.redis.config;

import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.support.ScriptRunner;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.Map;

/**
 * @Description 公共初始化配置
 * @Author WuYiLong
 * @Date 2024/7/8 9:38
 */
@Slf4j
@ConditionalOnProperty(prefix = "init",value = "enabled",havingValue = "true")
@Component
public class InitConfig implements ApplicationRunner {

    @Autowired
    private DynamicDataSourceProperties dynamicDataSourceProperties;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("****************初始化数据库脚本开始*************");
        Map<String, DataSourceProperty> datasource = dynamicDataSourceProperties.getDatasource();
        DataSourceProperty master = datasource.get("master");
        DataSource build = DataSourceBuilder
                .create()
                .url(master.getUrl())
                .driverClassName(master.getDriverClassName())
                .password(master.getPassword())
                .type(master.getType())
                .username(master.getUsername())
                .build();

        ScriptRunner scriptRunner = new ScriptRunner(true, ";");
        scriptRunner.runScript(build,"classpath:/db/**");
        log.info("****************初始化数据库脚本结束*************");

    }
}

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

复制代码
init:
  enabled: false

项目地址

github

相关推荐
JH30736 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_12498707539 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_9 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_818732069 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
此生只爱蛋10 小时前
【Redis】主从复制
数据库·redis
汤姆yu13 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶13 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip14 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
惊讶的猫14 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
JavaGuide14 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot