在之前的发布的博客https://blog.csdn.net/IceCaptain/article/details/79200388已经做过一次相关整合,这篇文章主要是记录一些注意点和非注解的缓存方式。
*注:这篇文章的所有配置都是基于上一篇文章,只是变更了实体类,删除了redisKey属性,并且将使用的service类直接继承了RedisService,其他的大致相同,在这里只演示RedisService的继承类RemindPushManager,重点展示思路。
redis作为缓存常用的无非两种形式,一种是注解形式,另一种则是非注解形式,虽然非注解形式相比起来更为麻烦,但是灵活性更高,可塑性更强。
一、注解缓存
在上一篇文章中记录的即是以注解的方式使用缓存,但存在两个问题:
*问题一:前文UserServiceImpl
类的save模块将数据手动插入了redis客户端,这是多余的,笔者之前对redis理解尚浅,不知道类似于下图的方式是spring存入的redis中,手动存入将使redis客户端中存在了key不同value相同的两份数据。
*问题二:前文虽然实现了注解缓存,但是实现的非常勉强,注解@CacheEvict(value=”userCache”, allEntries=true)中的allEntries=true在每一次执行完方法后都会清除掉所有缓存,虽然保证了findById和findAll(list)两个方法不会得到过期缓存,但是代价太过沉重,只适用于几乎没有什么修改变更的系统中,太过局限。
一种思路
下面提供一种方式,能协调findById和修改操作,但是放弃了findAll(list)的方式,暂时没有找到这个方法和其他方法在注解缓存中的平衡点。
在注解中增加了key属性,指定了spring存入redis中key的类型及数据,这样做在执行修改操作时,只会清除掉对应id的缓存。
package com.java.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.java.DO.RemindPushDO;
import com.java.common.CommonRestResult;
import com.java.dao.RemindPushDAO;
import com.java.enums.StatusEnum;
import com.java.form.RemindPushForm;
import com.java.servive.redis.RedisService;
import com.java.vo.RemindPushVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class RemindPushManager extends RedisService {
private static final String REMIND_KEY = "REMIND_KEY";
@Autowired
private RemindPushDAO remindPushDAO;
public List list(String status){
return remindPushDAO.getByStatus(status).stream().map(remindPushDO -> {
return getById(remindPushDO.getId());
}).collect(Collectors.toList());
}
@Cacheable(value="remindPushCache", key="#id")
public RemindPushVO getById(Long id){
RemindPushDO remindPushDO = remindPushDAO.findOne(id);
String str = JSON.toJSONString(remindPushDO);
RemindPushVO remindPushVO = JSON.parseObject(str, RemindPushVO.class);
return remindPushVO;
}
@CachePut(value="remindPushCache", key="#remindPushForm.id")
@Transactional
public RemindPushVO create(RemindPushForm remindPushForm){
String str = JSON.toJSONString(remindPushForm);
RemindPushDO remindPushDO = JSON.parseObject(str, RemindPushDO.class);
remindPushDO.setStatus(StatusEnum.NORMAL);
remindPushDO.setCreateTime(new Date());
remindPushDO.setUpdateTime(new Date());
Long id = remindPushDAO.save(remindPushDO).getId();
return getById(id);
}
@CacheEvict(value="remindPushCache", key="#remindPushForm.id")
@Transactional
public RemindPushVO modify(RemindPushForm remindPushForm){
RemindPushDO oldRemindPushDO = remindPushDAO.findOne(remindPushForm.getId());
if(oldRemindPushDO == null){
return new RemindPushVO();
}
String str = JSON.toJSONString(remindPushForm);
RemindPushDO newRemindPushDO = JSON.parseObject(str, RemindPushDO.class);
newRemindPushDO.setStatus(oldRemindPushDO.getStatus());
newRemindPushDO.setCreateTime(oldRemindPushDO.getCreateTime());
newRemindPushDO.setUpdateTime(new Date());
Long id = remindPushDAO.save(newRemindPushDO).getId();
return getById(id);
}
@CacheEvict(value="remindPushCache", key="#id")
@Transactional
public RemindPushVO delete(Long id){
RemindPushDO remindPushDO = remindPushDAO.findOne(id);
if(remindPushDO == null){
return new RemindPushVO();
}
remindPushDAO.delete(id);
return new RemindPushVO();
}
@Override
protected String getRedisKey() {
return REMIND_KEY;
}
}
*测试过程:
调用http://127.0.0.1:8080/api/admin/remind/getById/21接口后,可以看到将key的类型java.lang.Number一起存入了客户端,再次访问接口,控制台不再打印sql语句,并且执行速度大大提高。
接口调用时间:
再次调用http://127.0.0.1:8080/api/admin/remind/getById/20接口,出现了两条数据
调用http://127.0.0.1:8080/api/admin/remind/modify接口修改id=21的数据,可以看到id=21的数据被清除掉了。
二、非注解缓存
注解缓存相比较而言,会麻烦一点,要手动处理缓存的使用,但是很灵活,也很容易协调各个方法的使用。
一种思路
因为findById和findAll(list)两类方法始终规避不掉在redis中要以两种形式存储,那么,可以这样做。
先定义findById(id)方法,先判断key=id的数据是否存在,存在则直接取出,不存在从数据库取出后再存入redis中;
然后,再定义findAll(list)方法,将获取到的list数据通过findById(id)方法一条一条存入redis,并且将所有数据的id拼接成1,2,3…的形式作为key=”list”的值存入redis,调用方法时,先判断key=”list”的数据是否存在,存在则取出数据1,2,3…,再调用findById(id)方法从缓存中取出一条条数据,不存在则再重复上述过程。
并且在添加和删除操作中只需要增加或删除redis中key=id的数据后,再修改key=”list”的数据即可。
package com.java.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.java.DO.RemindPushDO;
import com.java.common.CommonRestResult;
import com.java.dao.RemindPushDAO;
import com.java.enums.StatusEnum;
import com.java.form.RemindPushForm;
import com.java.servive.redis.RedisService;
import com.java.vo.RemindPushVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class RemindPushManager extends RedisService {
private static final String REMIND_KEY = "REMIND_KEY";
@Autowired
private RemindPushDAO remindPushDAO;
public List list(String status){
List remindPushDOList = null;
String idStr = null;
if(StringUtils.isNotBlank(idStr = get("list"))){
List idList = Arrays.asList(idStr.split(","));
return idList.stream().map(id -> {
return getById(Long.parseLong(id));
}).collect(Collectors.toList());
}
List remindPushVOList = new ArrayList<>();
remindPushDOList = remindPushDAO.getByStatus(status);
StringBuilder sb = new StringBuilder();
for(RemindPushDO remindPushDO : remindPushDOList){
remindPushVOList.add(getById(remindPushDO.getId()));
sb.append(remindPushDO.getId() + ",");
}
if(sb.length() > 0){
sb.deleteCharAt(sb.length()-1);
}
put("list", sb.toString(), -1);
return remindPushVOList;
}
public RemindPushVO getById(Long id){
RemindPushDO remindPushDO = null;
String remindPushDOStr = null;
if(StringUtils.isNotBlank(remindPushDOStr = get(String.valueOf(id)))){
remindPushDO = JSON.parseObject(remindPushDOStr, RemindPushDO.class);
}else{
remindPushDO = remindPushDAO.findOne(id);
put(String.valueOf(id), JSON.toJSONString(remindPushDO), -1);
}
String str = JSON.toJSONString(remindPushDO);
RemindPushVO remindPushVO = JSON.parseObject(str, RemindPushVO.class);
return remindPushVO;
}
@Transactional
public RemindPushVO create(RemindPushForm remindPushForm){
String str = JSON.toJSONString(remindPushForm);
RemindPushDO remindPushDO = JSON.parseObject(str, RemindPushDO.class);
remindPushDO.setStatus(StatusEnum.NORMAL);
remindPushDO.setCreateTime(new Date());
remindPushDO.setUpdateTime(new Date());
Long id = remindPushDAO.save(remindPushDO).getId();
remindPushForm.setId(id);
put(String.valueOf(id), JSON.toJSONString(remindPushDO), -1);
String idStr = null;
if(StringUtils.isNotBlank(idStr = get("list"))){
String[] ids = idStr.split(",");
List idList = new ArrayList<>(Arrays.asList(idStr.split(",")));
if(!idList.contains(String.valueOf(id))){
idList.add(String.valueOf(id));
put("list", StringUtils.join(idList.toArray(), ","), -1);
}
}
return getById(id);
}
@Transactional
public RemindPushVO modify(RemindPushForm remindPushForm){
RemindPushDO oldRemindPushDO = remindPushDAO.findOne(remindPushForm.getId());
if(oldRemindPushDO == null){
return new RemindPushVO();
}
String str = JSON.toJSONString(remindPushForm);
RemindPushDO newRemindPushDO = JSON.parseObject(str, RemindPushDO.class);
newRemindPushDO.setStatus(oldRemindPushDO.getStatus());
newRemindPushDO.setCreateTime(oldRemindPushDO.getCreateTime());
newRemindPushDO.setUpdateTime(new Date());
Long id = remindPushDAO.save(newRemindPushDO).getId();
put(String.valueOf(id), JSON.toJSONString(newRemindPushDO), -1);
return getById(id);
}
@Transactional
public RemindPushVO delete(Long id){
RemindPushDO remindPushDO = remindPushDAO.findOne(id);
if(remindPushDO == null){
return new RemindPushVO();
}
remindPushDAO.delete(id);
remove(String.valueOf(id));
String idStr = null;
if(StringUtils.isNotBlank(idStr = get("list"))){
List idList = new ArrayList<>(Arrays.asList(idStr.split(",")));
if(idList.contains(String.valueOf(id))){
idList.remove(String.valueOf(id));
put("list", StringUtils.join(idList.toArray(), ","), -1);
}
}
return new RemindPushVO();
}
@Override
protected String getRedisKey() {
return REMIND_KEY;
}
}
*测试过程:
调用http://127.0.0.1:8080/api/admin/remind/list接口后,redis数据成功存入了findById和findAll(list)两种形式,再次调用接口后可以看到访问速度大大提高;之后再调用http://127.0.0.1:8080/api/admin/remind/getById/21接口将直接从缓存中取值;而调用http://127.0.0.1:8080/api/admin/remind/modify接口修改id=21数据的同时更新key=21的缓存,并不会影响到list缓存。
接口调用时间:
调用http://127.0.0.1:8080/api/admin/remind/delete/24接口后,redis数据变更,再次调用http://127.0.0.1:8080/api/admin/remind/list接口将依然从缓存中取值。
全文讲述了Spring Boot整合JPA+MySQL+Redis的两种形式,包括注解形式和非注解形式,可以看到非注解形式明显更为灵活和可控,在redis存入过程中key的形式也更容易指定,但是稍显麻烦,不过成功掌握了从缓存中取单条数据和取多条数据的平衡点;而注解形式,笔者暂时找不到更好的解决方案来处理这个问题,读者如有建议,欢迎指点。