1. 程式人生 > >用Redis做Mybatis二級快取

用Redis做Mybatis二級快取

首先在pom.xml檔案裡面新增依賴

然後再在application.yml

檔案裡面一旦有這個配置,你伺服器啟動時就會與redis做連線,所以啟動伺服器時一定要先啟動redis

如果我們要對redis做使用者控制的話,不然還要對它配置使用者密碼之類的

接下來我們再來做個快取的實現,我們做個util包,在util包裡面寫個ApplicationContextHolder類來獲取ApplicationContext(應用上下文,spring的核心物件)。所以這個類要繼承ApplicationContextAware——當一個類實現了這個介面(ApplicationContextAware)之後,這個類就可以方便獲得ApplicationContext中的所有bean。換句話說,就是這個類可以直接獲取spring配置檔案中,所有有引用到的bean物件。(相當於是自己包裝了ApplicationContext的一個getBean方法)

package com.yy.hospital.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext ctx;

    @Override
    //向工具類注入applicationContext
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ctx = applicationContext;     //ctx就是注入的applicationContext
    }

    //外部呼叫ctx
    public static ApplicationContext getCtx(){
        return ctx;
    }


    //getbean有兩種方式拿:1)按型別拿  2)按名字拿

    //從應用上下文裡面獲得類例項(即bean容器裡面獲得類容器)
    //為什麼我們現在要採用這種麻煩的方法(以前直接用Autowired註解自動裝配進去了)--- 這與redis連線池有關
    //用Redis時,建了許多連線池,我們在redis裡面拿快取物件時,快取物件與每個連線都有一個RedisTemplate,你在注入時用自動注入,不同
    // RedisTemplate是同類型同名的,注入時你得到的是哪個連線使用的redisTemplate呢?所以你注入時分不清
    //所以我們重新封裝一個getBean的方法,按指定型別或名字來拿bean例項
    public static <T> T getBean(Class<T> tClass){
        return ctx.getBean(tClass);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name){
        return (T) ctx.getBean(name);
    }

}

ApplicationContextHolder是為接下來Mybatis的快取類做準備的。所以我們來定義RedisCache類,來作為Mybatis二級快取所使用的類。它要繼承Cache介面(對Mybatis來說,你要用自定義的類來實現二級快取,就要繼承Mybatis的Cache介面)

package com.yy.hospital.util;

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/*
    使用redis實現mybatis二級快取
 */
public class RedisCache implements Cache {
    //slf4j的日誌記錄器
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
    //快取物件唯一標識
    private final String id; //orm的框架都是按物件的方式快取,而每個物件都需要一個唯一標識.
    //用於事務性快取操作的讀寫鎖
    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //處理事務性快取中做的
    //操作資料快取的--跟著執行緒走的
    private  RedisTemplate redisTemplate;  //Redis的模板負責將快取物件寫到redis伺服器裡面去
    //快取物件的是失效時間,30分鐘
    private static final long EXPRIRE_TIME_IN_MINUT = 30;

    //構造方法---把物件唯一標識傳進來
    public RedisCache(String id){
        if(id == null){
            throw new IllegalArgumentException("快取物件id是不能為空的");
        }
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }


    //給模板物件RedisTemplate賦值,並傳出去
    private RedisTemplate getRedisTemplate(){
        if(redisTemplate == null){    //每個連線池的連線都要獲得RedisTemplate
            redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
        }
        return redisTemplate;
    }


    /*
        儲存快取物件的方法
     */
    @Override
    public void putObject(Object key, Object value) {
        try{
            RedisTemplate redisTemplate = getRedisTemplate();
            //使用redisTemplate得到值操作物件
            ValueOperations operation = redisTemplate.opsForValue();
            //使用值操作物件operation設定快取物件
            operation.set(key,value,EXPRIRE_TIME_IN_MINUT, TimeUnit.MINUTES);  //TimeUnit.MINUTES系統當前時間的分鐘數
            logger.debug("快取物件儲存成功");
        }catch (Throwable t){
            logger.error("快取物件儲存失敗"+t);
        }

    }

    /*
        獲取快取物件的方法
     */
    @Override
    public Object getObject(Object key) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            ValueOperations operations = redisTemplate.opsForValue();
            Object result = operations.get(key);
            logger.debug("獲取快取物件");
            return result;
        }catch (Throwable t){
            logger.error("快取物件獲取失敗"+t);
            return null;
        }
    }

    /*
        刪除快取物件
     */
    @Override
    public Object removeObject(Object key) {
        try{
            RedisTemplate redisTemplate = getRedisTemplate();
            redisTemplate.delete(key);
            logger.debug("刪除快取物件成功!");
        }catch (Throwable t){
            logger.error("刪除快取物件失敗!"+t);
        }
        return null;
    }

    /*
        清空快取物件
        當快取的物件更新了的化,就執行此方法
     */
    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        //回撥函式
        redisTemplate.execute((RedisCallback)collection->{
            collection.flushDb();
            return  null;
        });
        logger.debug("清空快取物件成功!");
    }

    //可選實現的方法
    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }



}

這樣,二級快取的工具類我們就寫好了,接著我們要把工具類配置到對映器介面上去

第一種策略是直接在mapper介面上加上註解@CacheNamespace(implementation = 。。。。)

第二種策略就是在mapper.xml檔案中加cache標籤

記住,對於同一個mapper介面,他不能同時用兩種策略(一個mapper裡面同時用了註解SQL和.xml檔案的SQL話,只能用其中一種策略)

最後,在啟動類加上註解@EnableCaching就ok了

最後,我們通過資料庫兩次重複查詢測試可以發現,redis裡面已經存在快取物件了(注意,快取都是給查詢用的)

有幾個注意點

1)在記憶體中儲存物件,物件要序列化

2)在.xml使用cache標籤,只針對xml裡的查詢語句有用,所以針對要用快取的查詢,應該放在同一個mapper介面或者.xml檔案中

3)使用了使用者Token的登入相關的方法,最好不要做快取操作

4)對快取物件進行增刪改操作,快取物件會被清除掉