若依字典原理---后端
数据库设计
在讲解若依字典原理之前先让我们了解一下两个数据库:
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
设计优势
- 性能优化: 避免频繁查询数据库,提升响应速度
- 数据一致性: 通过缓存清除机制保证数据及时更新
- 系统解耦: 字典数据与业务逻辑分离,便于维护
- 通用性: 统一的字典管理,全系统共享使用
注意事项
- 缓存时效: 若依通常设置缓存永不过期,依靠更新时的清除机制
- 内存管理: 大量字典数据需要考虑Redis内存使用
- 集群环境: 在集群部署时,确保所有节点缓存同步
这种设计确保了字典数据的高效访问,同时通过合理的缓存策略保证了数据的实时性和一致性。
字典缓存详细机制
大家在使用的若依的时候会发现在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缓存,无缓存则查库并回填 | 通常直接查询数据库 |
数据一致性 | 通过字典数据变更时清除对应缓存来保证 | 实时查询,天然保持一致性 |
为何这样设计
若依框架这样设计字典缓存,主要基于以下几点考虑:
-
性能与资源平衡
框架将珍贵的缓存资源 (如Redis内存)用在刀刃上,即那些最常用、最影响用户体验的字典数据。这避免了为所有字典查询都建立和维护缓存带来的额外开销。
-
保证管理操作的实时性
在后台管理字典时,必然希望任何增、删、改操作都能立刻看到准确结果 。如果这些管理类查询也走缓存,就需要在数据变更时维护更多缓存,增加数据不一致的风险(比如改了字典值但缓存更新不及时)。
-
简化缓存策略
只为最核心的接口配置缓存,使得缓存的失效和更新策略变得简单明确:主要关注字典数据本身的变更即可。
深入了解缓存机制
若依框架中字典缓存的核心运作方式,尤其是针对那个带缓存的接口,可以概括为以下几点:
- 缓存键与类型 :缓存通常以字典类型 (
dict_type
)为关键维度。例如,接口根据传递过来的字典类型首先从Redis的字典缓存中查询。 - 数据更新与缓存清除 :当你在后台修改字典数据时,若依框架会清除 对应字典类型的Redis缓存。这样下次通过
getDicts
接口请求时,缓存未命中,就会从数据库查询最新数据并重新写入缓存,从而保证前端能获取到最新数据。
注意事项
了解上述机制后,在实际开发和维护中需要注意:
- 新增字典数据不显示 :向已有字典类型新增数据后,前端若未显示,通常是因为旧的字典数据仍在缓存中。此时需要清除Redis中对应的字典缓存(例如通过系统提供的缓存监控功能或手动清除),触发下次查询时重新加载。
- 空字典缓存问题:早期的若依版本可能存在一个情况:如果仅为字典类型创建了类型但未添加具体数据,可能导致缓存了空集合,之后即使添加了数据,由于空缓存的存在,接口仍返回空。这一问题在后续版本中已修复。
总结
若依框架选择只为特定字典接口(如 getDicts
)使用缓存,是经过权衡的:优先保障高频、稳定数据访问的性能,同时确保管理操作的实时性和数据一致性,并简化缓存管理。这种设计在多数情况下是合理且高效的。
希望这些解释能帮助你更好地理解若依字典缓存的设计,并指导你的开发和问题排查。