若依字典原理---后端

若依字典原理---后端

数据库设计

在讲解若依字典原理之前先让我们了解一下两个数据库:

1. 字典类型表 (sys_dict_type)

sql 复制代码
CREATE TABLE sys_dict_type (
  dict_id     BIGINT PRIMARY KEY,    -- 字典主键
  dict_name   VARCHAR(100),          -- 字典名称
  dict_type   VARCHAR(100) UNIQUE,   -- 字典类型(唯一标识)
  status      CHAR(1) DEFAULT '0',   -- 状态(0正常 1停用)
  create_by   VARCHAR(64),           -- 创建者
  create_time DATETIME,              -- 创建时间
  update_by   VARCHAR(64),           -- 更新者
  update_time DATETIME,              -- 更新时间
  remark      VARCHAR(500)           -- 备注
);

2. 字典数据表 (sys_dict_data)

sql 复制代码
CREATE TABLE sys_dict_data (
  dict_code   BIGINT PRIMARY KEY,    -- 字典编码
  dict_sort   INT DEFAULT 0,         -- 字典排序
  dict_label  VARCHAR(100),          -- 字典标签
  dict_value  VARCHAR(100),          -- 字典键值
  dict_type   VARCHAR(100),          -- 字典类型(关联sys_dict_type)
  css_class   VARCHAR(100),          -- 样式属性
  list_class  VARCHAR(100),          -- 表格回显样式
  is_default  CHAR(1) DEFAULT 'N',   -- 是否默认(Y是 N否)
  status      CHAR(1) DEFAULT '0',   -- 状态(0正常 1停用)
  create_by   VARCHAR(64),           -- 创建者
  create_time DATETIME,              -- 创建时间
  update_by   VARCHAR(64),           -- 更新者
  update_time DATETIME,              -- 更新时间
  remark      VARCHAR(500)           -- 备注
);

完整的字典数据流

下图清晰地展示了若依字典数据从创建到使用的完整生命周期:
是 否 创建字典类型 sys_dict_type表 创建字典数据 sys_dict_data表 字典类型缓存Redis 字典数据缓存Redis 前端请求字典数据 缓存是否存在? 直接从Redis返回 查询数据库 回填Redis缓存 前端使用字典数据 字典数据变更 清除Redis缓存 下次请求重新加载

核心代码实现

1. 字典数据获取服务

java 复制代码
@Service
public class SysDictDataServiceImpl implements ISysDictDataService {
    
    @Autowired
    private RedisCache redisCache;
    
    /**
     * 根据字典类型查询字典数据
     */
    @Override
    public List<SysDictData> selectDictDataByType(String dictType) {
        // 1. 首先从Redis缓存获取
        List<SysDictData> dictDatas = redisCache.getCacheObject(getCacheKey(dictType));
        if (StringUtils.isNotNull(dictDatas)) {
            return dictDatas;
        }
        
        // 2. 缓存不存在,查询数据库
        dictDatas = dictDataMapper.selectDictDataByType(dictType);
        
        // 3. 将结果存入Redis缓存
        if (StringUtils.isNotNull(dictDatas)) {
            redisCache.setCacheObject(getCacheKey(dictType), dictDatas);
        }
        
        return dictDatas;
    }
    
    /**
     * 获取缓存Key
     */
    private String getCacheKey(String dictType) {
        return Constants.SYS_DICT_KEY + dictType;
    }
}

2. 字典类型服务

java 复制代码
@Service
public class SysDictTypeServiceImpl implements ISysDictTypeService {
    
    @Autowired
    private RedisCache redisCache;
    
    /**
     * 根据字典类型查询字典类型信息
     */
    @Override
    public SysDictType selectDictTypeByType(String dictType) {
        // 字典类型信息也进行缓存
        SysDictType dictType = redisCache.getCacheObject(getDictTypeCacheKey(dictType));
        if (StringUtils.isNotNull(dictType)) {
            return dictType;
        }
        
        dictType = dictTypeMapper.selectDictTypeByType(dictType);
        if (StringUtils.isNotNull(dictType)) {
            redisCache.setCacheObject(getDictTypeCacheKey(dictType.getDictType()), dictType);
        }
        
        return dictType;
    }
}

3. 缓存清除策略

java 复制代码
/**
 * 字典数据变更时的缓存清理
 */
@Override
public int updateDictData(SysDictData dictData) {
    // 更新数据库
    int rows = dictDataMapper.updateDictData(dictData);
    
    if (rows > 0) {
        // 清除对应的字典数据缓存
        redisCache.deleteObject(getCacheKey(dictData.getDictType()));
        
        // 同时清除字典类型缓存(如果类型信息有变更)
        redisCache.deleteObject(getDictTypeCacheKey(dictData.getDictType()));
    }
    
    return rows;
}

/**
 * 删除字典数据时的缓存清理
 */
@Override
public int deleteDictDataByIds(Long[] dictCodes) {
    // 先查询要删除的数据,获取字典类型
    for (Long dictCode : dictCodes) {
        SysDictData dictData = dictDataMapper.selectDictDataById(dictCode);
        if (dictData != null) {
            // 清除对应字典类型的缓存
            redisCache.deleteObject(getCacheKey(dictData.getDictType()));
        }
    }
    
    // 然后删除数据库记录
    return dictDataMapper.deleteDictDataByIds(dictCodes);
}

前端调用流程

1. 字典数据获取接口

java 复制代码
@RestController
@RequestMapping("/system/dict/data")
public class SysDictDataController {
    
    /**
     * 根据字典类型查询字典数据信息
     */
    @GetMapping(value = "/type/{dictType}")
    public AjaxResult dictType(@PathVariable String dictType) {
        // 这个接口就是前端组件调用的核心接口
        List<SysDictData> data = dictDataService.selectDictDataByType(dictType);
        return AjaxResult.success(data);
    }
}

2. 前端调用方式

javascript 复制代码
// 1. 组件中声明字典依赖
export default {
  dicts: ['sys_user_sex', 'sys_normal_disable'],
  created() {
    // 2. 字典数据自动加载到 this.dict.type
    console.log(this.dict.type.sys_user_sex);
    // 输出: [{ dictLabel: '男', dictValue: '0' }, { dictLabel: '女', dictValue: '1' }]
  }
}

缓存键设计

若依框架使用统一的缓存键格式:

java 复制代码
public class Constants {
    /**
     * 字典管理 cache key
     */
    public static final String SYS_DICT_KEY = "sys_dict:";
    
    /**
     * 字典类型 cache key  
     */
    public static final String SYS_DICT_TYPE_KEY = "sys_dict_type:";
}
  • 字典数据缓存键 : sys_dict:${dictType}

    例如: sys_dict:sys_user_sex

  • 字典类型缓存键 : sys_dict_type:${dictType}

    例如: sys_dict_type:sys_user_sex

设计优势

  1. 性能优化: 避免频繁查询数据库,提升响应速度
  2. 数据一致性: 通过缓存清除机制保证数据及时更新
  3. 系统解耦: 字典数据与业务逻辑分离,便于维护
  4. 通用性: 统一的字典管理,全系统共享使用

注意事项

  1. 缓存时效: 若依通常设置缓存永不过期,依靠更新时的清除机制
  2. 内存管理: 大量字典数据需要考虑Redis内存使用
  3. 集群环境: 在集群部署时,确保所有节点缓存同步

这种设计确保了字典数据的高效访问,同时通过合理的缓存策略保证了数据的实时性和一致性。

字典缓存详细机制

大家在使用的若依的时候会发现在redis中有sys:dict:* 数据,你很容易会想到他是字典缓存,但是当你去查询字典的 增删改查 的时候会发现只有一个接口使用了redis做缓存去返回值。

此方法如下:

java 复制代码
    /**
     * 根据字典类型查询字典数据信息
     */
    @GetMapping(value = "/type/{dictType}")
    public AjaxResult dictType(@PathVariable String dictType)
    {
        List<SysDictData> data = dictTypeService.selectDictDataByType(dictType);
        if (StringUtils.isNull(data))
        {
            data = new ArrayList<SysDictData>();
        }
        return success(data);
    }

再去深入了解会发现此接口便是我们组件调用字典返回值的接口,这时也可能会有一个疑问为什么若依不去将其他的接口做缓存处理?

若依框架的字典缓存设计,确实是有意识地区分了"高频常量型"数据和"低频管理型"数据。下面这个表格能帮你清晰理解这种设计思路的核心区别:

特性维度 带缓存的字典接口 (如 getDicts) 普通查询接口 (如字典列表分页查询)
核心场景 前端组件渲染、数据回显等高频读取场景 后台系统的字典管理功能(增删改查)
数据特性 稳定字典数据 (如 "用户状态","性别"等,定义后很少变更) 任何字典数据,包括其当前状态、分页列表等
性能要求 ,要求快速响应,避免重复数据库查询 相对较低,管理操作频率远低于前端数据展示
缓存机制 查询时优先读取Redis缓存,无缓存则查库并回填 通常直接查询数据库
数据一致性 通过字典数据变更时清除对应缓存来保证 实时查询,天然保持一致性

为何这样设计

若依框架这样设计字典缓存,主要基于以下几点考虑:

  1. 性能与资源平衡

    框架将珍贵的缓存资源 (如Redis内存)用在刀刃上,即那些最常用、最影响用户体验的字典数据。这避免了为所有字典查询都建立和维护缓存带来的额外开销。

  2. 保证管理操作的实时性

    在后台管理字典时,必然希望任何增、删、改操作都能立刻看到准确结果 。如果这些管理类查询也走缓存,就需要在数据变更时维护更多缓存,增加数据不一致的风险(比如改了字典值但缓存更新不及时)。

  3. 简化缓存策略

    只为最核心的接口配置缓存,使得缓存的失效和更新策略变得简单明确:主要关注字典数据本身的变更即可。

深入了解缓存机制

若依框架中字典缓存的核心运作方式,尤其是针对那个带缓存的接口,可以概括为以下几点:

  • 缓存键与类型 :缓存通常以字典类型dict_type)为关键维度。例如,接口根据传递过来的字典类型首先从Redis的字典缓存中查询。
  • 数据更新与缓存清除 :当你在后台修改字典数据时,若依框架会清除 对应字典类型的Redis缓存。这样下次通过 getDicts 接口请求时,缓存未命中,就会从数据库查询最新数据并重新写入缓存,从而保证前端能获取到最新数据。

注意事项

了解上述机制后,在实际开发和维护中需要注意:

  • 新增字典数据不显示 :向已有字典类型新增数据后,前端若未显示,通常是因为旧的字典数据仍在缓存中。此时需要清除Redis中对应的字典缓存(例如通过系统提供的缓存监控功能或手动清除),触发下次查询时重新加载。
  • 空字典缓存问题:早期的若依版本可能存在一个情况:如果仅为字典类型创建了类型但未添加具体数据,可能导致缓存了空集合,之后即使添加了数据,由于空缓存的存在,接口仍返回空。这一问题在后续版本中已修复。

总结

若依框架选择只为特定字典接口(如 getDicts)使用缓存,是经过权衡的:优先保障高频、稳定数据访问的性能,同时确保管理操作的实时性和数据一致性,并简化缓存管理。这种设计在多数情况下是合理且高效的。

希望这些解释能帮助你更好地理解若依字典缓存的设计,并指导你的开发和问题排查。

相关推荐
笨蛋不要掉眼泪23 天前
SpringBoot项目Excel模板下载功能详解
java·spring boot·后端·spring·excel·ruoyi
潇I洒1 个月前
若依4.8.1打包war后在Tomcat无法运行,404报错的一个解决方法
java·tomcat·ruoyi·若依·404
焯7592 个月前
若依微服务遇到的配置问题
java·mybatis·ruoyi
LKAI.2 个月前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi
pengzhuofan2 个月前
项目一系列-第4章 在线接口文档 & 代码模板改造
低代码·ruoyi
Olrookie2 个月前
若依前后端分离版学习笔记(七)—— Mybatis,分页,数据源的配置及使用
数据库·笔记·学习·mybatis·ruoyi
Olrookie2 个月前
若依前后端分离版学习笔记(五)——Spring Boot简介与Spring Security
笔记·后端·学习·spring·ruoyi
草履虫建模3 个月前
前后端分离项目中的接口设计与调用流程——以高仙机器人集成为例
java·前端·spring boot·机器人·intellij-idea·ruoyi·js
弗锐土豆3 个月前
一个基于若依(ruoyi-vue3)的小项目部署记录
前端·vue.js·部署·springcloud·ruoyi·若依