前言
在之前的發佈的博客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<String> {
private static final String REMIND_KEY = "REMIND_KEY";
@Autowired
private RemindPushDAO remindPushDAO;
public List<RemindPushVO> 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<String> {
private static final String REMIND_KEY = "REMIND_KEY";
@Autowired
private RemindPushDAO remindPushDAO;
public List<RemindPushVO> list(String status){
List<RemindPushDO> remindPushDOList = null;
String idStr = null;
if(StringUtils.isNotBlank(idStr = get("list"))){
List<String> idList = Arrays.asList(idStr.split(","));
return idList.stream().map(id -> {
return getById(Long.parseLong(id));
}).collect(Collectors.toList());
}
List<RemindPushVO> 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<String> 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<String> 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的形式也更容易指定,但是稍顯麻煩,不過成功掌握了從緩存中取單條數據和取多條數據的平衡點;而註解形式,筆者暫時找不到更好的解決方案來處理這個問題,讀者如有建議,歡迎指點。