在我们平时的编码工作中,经常会有一些流程步骤基本相同,只是中间有部分细节不同的业务流程,比如我们自己要实现一个基于redis缓存的模版方法,在redis中存在指定值时,则返回redis中缓存的数据;如果redis中不存在,则查询db,并把查询所得缓存起来,并返回查询所得。这个例子就是用模版模式再好不过了,直接上代码:
1.定义模版类
@Component
public abstract class RedisCacheTemplate {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 查询单个对象
*
* @param key redis缓存key
* @param targetClass 返回数据对象类型
* @return 缓存值
*/
@SuppressWarnings("unchecked")
public final <T> T findFromCache(String key, Class<T> targetClass) {
T target = BeanUtils.instantiateClass(targetClass);
// 1.判断redis中有无数据,有数据,直接返回
if (redisTemplate.hasKey(key)) {
Object result = redisTemplate.boundValueOps(key).get();
target = (T) result;
} else {
if (isSearchDB()) {// 钩子方法,判断是否要查询DB,由业务方实现
synchronized (RedisCacheTemplate.class) {// 加锁,防止并发查询或者操作DB
if (!redisTemplate.hasKey(key)) {
// 2.redis中无数据,则查找数据库
target = searchFromDB();
// 3.将数据库查询出的数据,放在redis中
if (timeOut() == -1L) {// 由业务方判断缓存是否需要超时时间
// 其实这里还有一个可以优化的地方,就是数据库没有查询到,
// 我们可以在缓存中保存一个特殊的值,这样,如果数据中不存在满足条件的数据,也不会每次查询DB了
redisTemplate.boundValueOps(key).set(target);
} else {
redisTemplate.boundValueOps(key).set(target, timeOut(), timeOutTimeUnit());
}
}
}
} else {
// 缓存没有命中,并不查询DB,直接返回null
return null;
}
}
// 返回查询数据
return target;
}
/**
* 钩子方式,供用户实现,判断是否要查找数据库,默认不查DB
*
* @return false
*/
protected boolean isSearchDB() {
return false;
};
/**
* 钩子方法,设置过期时间
*
* @return 默认-1L:不过期
*/
protected long timeOut() {
return -1L;
};
/**
* 设置过期时间单位
*
* @return 默认TimeUnit.HOURS
*/
protected TimeUnit timeOutTimeUnit() {
return TimeUnit.HOURS;
};
/**
* 查看DB逻辑,供用户实现
*
* @return DB查找结果
*/
protected abstract <T> T searchFromDB();
}
通常,因为模版方式需要一些钩子方法,或者其他需要业务方自己实现的具体方法,我们一般将模版类定义为抽象类,提供供业务实现的抽象方法,或者定义一个protect的方法,返回默认值,必要时由业务方重写该方法。
2.业务方定制具体细节
@Component
public class ServiceBootRedisCacheTemplate extends RedisCacheTemplate {
@Autowired
private TaskService taskService;
//业务方确认是否需要查询DB
@Override
protected boolean isSearchDB() {
return true;
}
//若需要查询DB,则实现查询DB的逻辑
@SuppressWarnings("unchecked")
@Override
protected TaskResponseDTO searchFromDB() {
TaskResponseDTO result = taskService.findOneById(1L);
return result;
}
}
3.使用我们的缓存模版
@RequestMapping("/test")
public String test() {
TaskResponseDTO res = cacheTempalte.findFromCache("lichangwu", TaskResponseDTO.class);
System.out.println(res);
System.out.println(userService.get(1L));
return "0000";
}
一个简单的模版模式就实现了,是不是很简单!
补充2点:
1.如果查询DB需要参数,也很简单,提供的抽象方法带参数即可。也可以在我们具体实现的ServiceBootRedisCacheTemplate 中定义需要的参数变量,提供set方法,也可实现该功能
2.我们第一次查询缓存,如果没有命中的话,我们就会查询数据库,如果查询数据库也没有满足条件的数据,即db查询返回null,如果不设置默认值在缓冲中,则每次这样的查询都会走DB,我们的缓存模版也就没有意义了。最好的做法是,如果DB查询不到,我们在缓存中设置一个指定的默认值,业务方根据返回的值做相应的业务逻辑。