Spring Boot整合JPA+MySQL+Redis(二)

前言

在之前的發佈的博客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在每一次執行完方法後都會清除掉所有緩存,雖然保證了findByIdfindAll(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的數據被清除掉了。
這裏寫圖片描述

二、非註解緩存
註解緩存相比較而言,會麻煩一點,要手動處理緩存的使用,但是很靈活,也很容易協調各個方法的使用。

一種思路
因爲findByIdfindAll(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數據成功存入了findByIdfindAll(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的形式也更容易指定,但是稍顯麻煩,不過成功掌握了從緩存中取單條數據和取多條數據的平衡點;而註解形式,筆者暫時找不到更好的解決方案來處理這個問題,讀者如有建議,歡迎指點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章