缓存查询为空
在使用缓存过程中,我们都会面临没有查询到数据的情况,然后再把结果查询结果设置到缓存中,这样的场景有很多
常规实现
我们先用普通的方法来实现一下
- 缓存列表
private List<InfoCategory> queryAllCategory(String tid) {
String key = String.format(Constants.ALL_CATEGORY,tid);
String value = redisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(value)){
return JSON.parseArray(value,InfoCategory.class);
}else{
List<InfoCategory> list = new LambdaQueryChainWrapper<>(categoryMapper)
.eq(InfoCategory::getTid, tid)
.list();
if(!CollectionUtils.isEmpty(list)){
redisTemplate.opsForValue().set(key, JSON.toJSONString(list));
}
return list;
}
}
- 缓存普通对象
public CategoryEventListResponse queryEventName(String tid, String appletId, String eventName, String taskSerial) {
String key = String.format(Constants.EVENT_TASK_NAME,tid,appletId,eventName,taskSerial);
String value = redisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(value)){
return JSON.parseObject(value,CategoryEventListResponse.class);
}else{
CategoryEventListResponse response =doQueryEventName(tid, appletId, eventName, taskSerial);
if(Objects.isNull(response)){
response = new CategoryEventListResponse();
}
redisTemplate.opsForValue().set(key, JSON.toJSONString(response),2*60, TimeUnit.SECONDS);
return response;
}
}
缺点:很多相似的功能基本上都如同上面的代码实现,比较繁琐、累赘,代码复用性不强
优化方案
1、在获取缓存的时候,直接把一个回调方法传过去,在没有数据的情况下可以直接运行回调方法缓存值
2、需要封装的查询,回调方法全部统一封装,调用者甚至感觉不到存在回调方法
直接上最终版代码
封装redisTemplate
@Component
@Data
@Slf4j
public class CallBackRedisTemplate {
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
*
* @param key redis key
* @param callable callable function
* @param vClass return type class
* @param defaultValue when callable function get null,set defaultValue to avoid to call function again,storage like {} []
* @param timeUnit
* @param time
* @param <V>
* @return
*/
public <V> V get(String key, Callable<V> callable,Class<V> vClass,V defaultValue, TimeUnit timeUnit,Long time) {
String redisValue = redisTemplate.opsForValue().get(key);
if(StringUtils.isEmpty(redisValue)){
try {
V v = callable.call();
if(Objects.isNull(v)){
redisTemplate.opsForValue().set(key,JSON.toJSONString(defaultValue),time,timeUnit);
return defaultValue;
}else{
redisTemplate.opsForValue().set(key,JSON.toJSONString(v),time,timeUnit);
return v;
}
} catch (Exception e) {
e.printStackTrace();
log.error("callable error key={}",key,e);
return null;
}
}
return JSON.parseObject(redisValue,vClass);
}
/**
*
* @param key redis key
* @param callable callable function
* @param vClass return type class
* @param timeUnit
* @param time
* @param <V>
* @return
*/
public <V> V get(String key, Callable<V> callable,Class<V> vClass, TimeUnit timeUnit,Long time) {
String redisValue = redisTemplate.opsForValue().get(key);
if(StringUtils.isEmpty(redisValue)){
try {
V v = callable.call();
if (!Objects.isNull(v)) {
redisTemplate.opsForValue().set(key, JSON.toJSONString(v), time, timeUnit);
}
return v;
} catch (Exception e) {
e.printStackTrace();
log.error("callable error key={}",key,e);
return null;
}
}
return JSON.parseObject(redisValue,vClass);
}
/**
*
* @param key redis key
* @param callable callable function
* @param typeReference return reference type class
* @param defaultValue when callable function get null,set defaultValue to avoid to call function again,storage like {} []
* @param timeUnit
* @param time
* @param <V>
* @return
*/
public <V> V get(String key, Callable<V> callable,TypeReference<V> typeReference,V defaultValue, TimeUnit timeUnit,Long time) {
String redisValue = redisTemplate.opsForValue().get(key);
if(StringUtils.isEmpty(redisValue)){
try {
V v = callable.call();
if(Objects.isNull(v)){
redisTemplate.opsForValue().set(key,JSON.toJSONString(defaultValue),time,timeUnit);
return defaultValue;
}else{
redisTemplate.opsForValue().set(key,JSON.toJSONString(v),time,timeUnit);
return v;
}
} catch (Exception e) {
e.printStackTrace();
log.error("callable error key={}",key,e);
return null;
}
}
return JSON.parseObject(redisValue,typeReference);
}
/**
*
* @param key redis key
* @param callable callable function
* @param typeReference return reference type class
* @param timeUnit
* @param time
* @param <V>
* @return
*/
public <V> V get(String key, Callable<V> callable,TypeReference<V> typeReference, TimeUnit timeUnit,Long time) {
String redisValue = redisTemplate.opsForValue().get(key);
if(StringUtils.isEmpty(redisValue)){
try {
V v = callable.call();
if (!Objects.isNull(v)) {
redisTemplate.opsForValue().set(key, JSON.toJSONString(v), time, timeUnit);
}
return v;
} catch (Exception e) {
e.printStackTrace();
log.error("callable error key={}",key,e);
return null;
}
}
return JSON.parseObject(redisValue,typeReference);
}
}
- 反序列化处理
这里有一个点,就是回调方法返回的是一个泛型V,在fastJson反序列化的时候,需要确定V的calss类型,所以添加了参数 Class<V> vClass 。当返回值为列表形式的时候,我们在使用反序列化就需要使用到 TypeReference<V> typeReference
- 在回调过程中没有值,下次不希望再来调用回调方法,减少对数据库的压力
添加参数 V defaultValue ,给缓存系统直接设置到缓存服务中,避免对数据库或者服务造成压力
封装业务service
- EventBizCacheService
public interface EventBizCacheService {
/**
* cache all category
* @param tid
* @return
*/
List<InfoCategory> queryAllCategory(String tid);
/**
* cache all event name
* @param tid
* @return
*/
CategoryEventListResponse queryEventName(String tid, String appletId, String eventName, String taskSerial);
}
- impl
@Service
public class EventBizCacheServiceImpl implements EventBizCacheService {
@Autowired
private CallBackRedisTemplate callBackRedisTemplate;
@Autowired
private InfoCategoryMapper categoryMapper;
@Autowired
private InfoCategoryEventRelMapper relMapper;
@Autowired
private InfoTenantAppletEventMapper eventMapper;
@Autowired
private InfoEventTaskMapper taskMapper;
@Autowired
private CrowCategoryService crowCategoryService;
@Override
public List<InfoCategory> queryAllCategory(String tid) {
String key = String.format(Constants.ALL_CATEGORY,tid);
return callBackRedisTemplate.get(key,
() -> new LambdaQueryChainWrapper<>(categoryMapper).eq(InfoCategory::getTid, tid).list(),
new TypeReference<List<InfoCategory>>(){},
TimeUnit.DAYS,
360L);
}
@Override
public CategoryEventListResponse queryEventName(String tid, String appletId, String eventName, String taskSerial) {
String key = String.format(Constants.EVENT_TASK_NAME,tid,appletId,eventName,taskSerial);
return callBackRedisTemplate.get(key,
()->{
CategoryEventListResponse cResult = crowCategoryService.queryEventName(appletId, eventName, taskSerial);
if (cResult != null) {
return cResult;
}
return categoryMapper.queryEventName(appletId, eventName, taskSerial);
},
CategoryEventListResponse.class,
TimeUnit.DAYS,
360L);
}
}
这个业务类就是为了屏蔽callable方法,时调用者无需关系数据来源
总结
在业务开发过程中遇到代码实现差不多,只有部分业务功能不一样的时候,考虑通用方案来解决,把业务方法传递过去,让调用者通过lambda方法自己完成业务方法。
如果有更好的方法,还请大家不吝赐教。