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

相关推荐
许苑向上1 小时前
Spring Boot 自动装配底层源码实现详解
java·spring boot·后端
星月昭铭2 小时前
Spring AI调用Embedding模型返回HTTP 400:Invalid HTTP request received分析处理
人工智能·spring boot·python·spring·ai·embedding
超级小忍4 小时前
深入浅出:在 Spring Boot 中构建实时应用 - 全面掌握 WebSocket
spring boot·后端·websocket
lang201509285 小时前
Apache Ignite 与 Spring Boot 集成
spring boot·后端·apache·ignite
小醉你真好5 小时前
Spring Boot 数据源配置中为什么可以不用写 driver-class-name
spring boot·后端·源代码管理
lixzest6 小时前
Redis实现数据传输简介
数据库·redis·缓存
lang201509286 小时前
如何使用 Apache Ignite 作为 Spring 框架的缓存(Spring Cache)后端
spring·缓存·apache·ignite
Linux技术支持工程师6 小时前
二十八、【Linux系统域名解析】DNS安装、子域授权、缓存DNS、分离解析、多域名解析
linux·运维·服务器·缓存·centos
小信丶7 小时前
Spring Boot 简单接口角色授权检查实现
java·spring boot·后端
星月昭铭8 小时前
Spring AI集成Elasticsearch向量检索时filter过滤失效问题排查与解决方案
人工智能·spring boot·spring·elasticsearch·ai