# 1、问题描述
了解了缓存穿透问题后,我就想着使用 hash 结构存储对象。如果用户请求的 ID 不存在的时候,需要在 redis 缓存中缓存 NULL 值,这样显然是不可行的,因为使用通过 entities 返回的类型任然是 Map 类型,不是 null。

StringRedisTemplate 会创建一个空 Map,使用无法通过类似 string 结构的 != null 来判断数据是否在缓存中。
# 2、解决方案
我们可以在根据 ID 获取到的实体信息时不使用 putAll () 方法,我们直接使用 put(key,hashKey,value)方法,我们缓存的 hashKey 和 value 信息为空字符串,这样我们在请求打到缓存上的时候虽然 map 不是为空,但是我们结合 hutool 工具包来转换后的实体信息中的所有属性均为 NULL,这样我们就可以返回给前端实体不存在,转化后的实体属性如果不为 NULL,则说明实体信息是存在的。例如:
@Override | |
public Result queryById(Long id) { | |
String shopKey = CACHE_SHOP_KEY + id; | |
// 1、从 redis 缓存中获取商铺信息 | |
Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(shopKey); | |
// 2、判断缓存是否命中,解决缓存穿透的问题上,shopMap 可能为空的 Map,或者是空的 HashMap | |
if (!shopMap.isEmpty()) { | |
// 2.1 命中转化为 shop 对象 直接返回商铺信息 | |
Shop shop = BeanUtil.fillBeanWithMap(shopMap, new Shop(), false); | |
// 2.1.1 如果 shop 中的所有属性均为 null,那么代表没有这个店铺信息 | |
if (BeanUtil.isEmpty(shop)) { | |
return Result.fail("店铺不存在"); | |
} | |
// 2.1.2 否则返回转化后的商铺信息 | |
return Result.ok(shop); | |
} | |
// 2.2 未命中从数据库中查询商铺信息 | |
Shop shop = this.getById(id); | |
// 2.2.1 判断数据库中是否有当前商铺的信息 | |
if (ObjectUtil.isEmpty(shop)) { | |
// 2.2.1.2 数据库中无当前商铺信息,缓存空值到 redis 中 | |
stringRedisTemplate.opsForHash().put(shopKey, "", ""); | |
stringRedisTemplate.expire(shopKey, CACHE_NULL_TTL, TimeUnit.MINUTES); | |
return Result.fail("店铺不存在"); | |
} | |
// 2.2.1.1 数据库中存在当前商铺信息,缓存至 redis, 后返回 | |
Map<String, Object> sqlShopMap = BeanUtil.beanToMap(shop, new HashMap<>(), | |
CopyOptions.create() | |
.setIgnoreNullValue(true) | |
.setFieldValueEditor((filedName, filedValue) -> | |
filedValue == null ? "" : filedValue.toString() | |
)); | |
stringRedisTemplate.opsForHash().putAll(shopKey, sqlShopMap); | |
stringRedisTemplate.expire(shopKey, CACHE_SHOP_TTL, TimeUnit.MINUTES); | |
return Result.ok(shop); | |
} |
通过 hutool 的 BeanUtil.isEmpty (Object obj) 来判断实体的属性是否全部为空。