MyBatis二级缓存自定义实现-Redis

package com.zjs.remote.config.redis;


import com.zjs.remote.config.ApplicationContextHolder;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.security.MessageDigest;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * MyBatis二级缓存Redis实现
 */

public class MybatisRedisCache implements Cache
{

    private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    /**
     * 统一缓存头
     */
    private static final String CACHE_NAME = "MyBatis:";
    /**
     * 读写锁:解决缓存击穿
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * 表空间ID:方便后面的缓存清理
     */
    private final String id;
    /**
     * redis服务接口:提供基本的读写和清理
     */
    //private static volatile RedisService redisService;

    //private RedisTemplate redisService; //(RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");

    //这里使用了redis缓存,使用springboot自动注入
    private RedisTemplate redisTemplate;


    /**
     * 信息摘要
     */
    private volatile MessageDigest messageDigest;

    /////////////////////// 解决缓存雪崩,具体范围根据业务需要设置合理值 //////////////////////////
    /**
     * 缓存最小有效期
     */
    private int minExpireMinutes = 60;
    /**
     * 缓存最大有效期
     */
    private int maxExpireMinutes = 120;

    /**
     * MyBatis给每个表空间初始化的时候要用到
     *
     * @param id 其实就是namespace的值
     */
    public MybatisRedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }

    /**
     * 获取ID
     *
     * @return 真实值
     */
    @Override
    public String getId() {
        return id;
    }

    /**
     * 创建缓存
     *
     * @param key   其实就是sql语句
     * @param value sql语句查询结果
     */
    @Override
    public void putObject(Object key, Object value) {
        try {
            String strKey = generateRedisKey(key);
            // 在redis额外维护CacheNamespace创建的key,clear的时候只清理当前CacheNamespace的数据
            getRedisTemplate().opsForHash().put(CACHE_NAME + id, strKey, "1");
            // 有效期 随机,防止雪崩
            int expireMinutes = RandomUtils.nextInt(minExpireMinutes, maxExpireMinutes);
            logger.info("将查询结果存储到cache.key:" + strKey + ",value:" + value);
            getRedisTemplate().opsForValue().set(strKey, value, expireMinutes, TimeUnit.SECONDS);

            logger.debug("Put cache to redis, id={}", strKey);
        } catch (Exception e) {
            logger.error("Redis put failed, key=" + key.toString(), e);
        }
    }

    /**
     * 读取缓存
     *
     * @param key 其实就是sql语句
     * @return 缓存结果
     */
    @Override
    public Object getObject(Object key) {
        try {
            String strKey = generateRedisKey(key);
            logger.debug("Get cache from redis, id={} key={}", id, strKey);
            return getRedisTemplate().opsForValue().get(strKey);
        } catch (Exception e) {
            logger.error("Redis get failed, fail over to db", e);
            return null;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 其实就是sql语句
     * @return 结果
     */
    @Override
    public Object removeObject(Object key) {
        try {
            String strKey = generateRedisKey(key);
            getRedisTemplate().delete(strKey);
            logger.debug("Remove cache from redis, id={}", id);
        } catch (Exception e) {
            logger.error("Redis remove failed", e);
        }
        return null;
    }

    /**
     * 缓存清理
     * 应该是根据表空间进行清理
     */
    @Override
    public void clear() {
        try {
            logger.debug("clear cache, id={}", id);
            String hsKey = CACHE_NAME + id;
            // 获取CacheNamespace所有缓存key
            Map<Object, Object> idMap = getRedisTemplate().opsForHash().entries(hsKey);
            if (!idMap.isEmpty()) {
                Set<Object> keySet = idMap.keySet();
                Set<String> keys = new HashSet<>(keySet.size());
                keySet.forEach(item -> keys.add(item.toString()));
                // 清空CacheNamespace下面所有缓存Key
                getRedisTemplate().delete(keys);
                // 清空CacheNamespace
                getRedisTemplate().delete(hsKey);
            }
        } catch (Exception e) {
            logger.error("clear cache failed", e);
        }
    }

    /**
     * 获取缓存大小,暂时没用上
     *
     * @return 长度
     */
    @Override
    public int getSize() {
        return 0;
    }

    /**
     * 获取读写锁:为了解决缓存击穿
     *  mybites 3.2 不生效 
     * @return*/
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    /**
     * 获取Redis服务接口
     * 使用双重检查保证线程安全
     *
     * @return 服务实例
     */
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            synchronized (RedisTemplate.class) {
                if (redisTemplate == null) {
                    redisTemplate = (RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");
                }
            }
        }
        return redisTemplate;
    }

    private String generateRedisKey(Object key) {
        String o = this.id + DigestUtils.sha256Hex(key.toString().getBytes());
        logger.info(o);
        return o;
    }

    public static byte[] append(byte[]... bas) {
        int iLen = 0;
        byte[][] var2 = bas;
        int var3 = bas.length;

        int var4;
        for (var4 = 0; var4 < var3; ++var4) {
            byte[] ba = var2[var4];
            if (ba != null && ba.length > 0) {
                iLen += ba.length;
            }
        }

        byte[] result = new byte[iLen];
        iLen = 0;
        byte[][] var8 = bas;
        var4 = bas.length;

        for (int var9 = 0; var9 < var4; ++var9) {
            byte[] ba = var8[var9];
            if (ba != null && ba.length > 0) {
                System.arraycopy(ba, 0, result, iLen, ba.length);
                iLen += ba.length;
            }
        }

        return result;
    }

    public int getMinExpireMinutes() {
        return minExpireMinutes;
    }

    public MybatisRedisCache setMinExpireMinutes(int minExpireMinutes) {
        this.minExpireMinutes = minExpireMinutes;
        return this;
    }

    public int getMaxExpireMinutes() {
        return maxExpireMinutes;
    }

    public MybatisRedisCache setMaxExpireMinutes(int maxExpireMinutes) {
        this.maxExpireMinutes = maxExpireMinutes;
        return this;
    }
}

 

为什么会记录一下?

  网络上目前的都有坑。 这是基于我找到的最好的实例,再次修改的。 

 

package com.zjs.remote.config.redis;


import com.zjs.remote.config.ApplicationContextHolder;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.security.MessageDigest;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* MyBatis二级缓存Redis实现
* 重点处理以下几个问题
* 1、缓存穿透:存储空值解决,MyBatis框架实现
* 2、缓存击穿:使用互斥锁,我们自己实现
* 3、缓存雪崩:缓存有效期设置为一个随机范围,我们自己实现
* 4、读写性能:redis key不能过长
*/

public class MybatisRedisCache implements Cache
{

private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

/**
* 统一缓存头
*/
private static final String CACHE_NAME = "MyBatis:";
/**
* 读写锁:解决缓存击穿
*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 表空间ID:方便后面的缓存清理
*/
private final String id;
/**
* redis服务接口:提供基本的读写和清理
*/
//private static volatile RedisService redisService;

//private RedisTemplate redisService; //(RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");

//这里使用了redis缓存,使用springboot自动注入
private RedisTemplate redisTemplate;


/**
* 信息摘要
*/
private volatile MessageDigest messageDigest;

/////////////////////// 解决缓存雪崩,具体范围根据业务需要设置合理值 //////////////////////////
/**
* 缓存最小有效期
*/
private int minExpireMinutes = 60;
/**
* 缓存最大有效期
*/
private int maxExpireMinutes = 120;

/**
* MyBatis给每个表空间初始化的时候要用到
*
* @param id 其实就是namespace的值
*/
public MybatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}

/**
* 获取ID
*
* @return 真实值
*/
@Override
public String getId() {
return id;
}

/**
* 创建缓存
*
* @param key 其实就是sql语句
* @param value sql语句查询结果
*/
@Override
public void putObject(Object key, Object value) {
try {
String strKey = generateRedisKey(key);
// redis额外维护CacheNamespace创建的keyclear的时候只清理当前CacheNamespace的数据
getRedisTemplate().opsForHash().put(CACHE_NAME + id, strKey, "1");
// 有效期 随机,防止雪崩
int expireMinutes = RandomUtils.nextInt(minExpireMinutes, maxExpireMinutes);
logger.info("将查询结果存储到cache.key:" + strKey + ",value:" + value);
getRedisTemplate().opsForValue().set(strKey, value, expireMinutes, TimeUnit.SECONDS);

logger.debug("Put cache to redis, id={}", strKey);
} catch (Exception e) {
logger.error("Redis put failed, key=" + key.toString(), e);
}
}

/**
* 读取缓存
*
* @param key 其实就是sql语句
* @return 缓存结果
*/
@Override
public Object getObject(Object key) {
try {
String strKey = generateRedisKey(key);
logger.debug("Get cache from redis, id={} key={}", id, strKey);
return getRedisTemplate().opsForValue().get(strKey);
} catch (Exception e) {
logger.error("Redis get failed, fail over to db", e);
return null;
}
}

/**
* 删除缓存
*
* @param key 其实就是sql语句
* @return 结果
*/
@Override
public Object removeObject(Object key) {
try {
String strKey = generateRedisKey(key);
getRedisTemplate().delete(strKey);
logger.debug("Remove cache from redis, id={}", id);
} catch (Exception e) {
logger.error("Redis remove failed", e);
}
return null;
}

/**
* 缓存清理
* 应该是根据表空间进行清理
*/
@Override
public void clear() {
try {
logger.debug("clear cache, id={}", id);
String hsKey = CACHE_NAME + id;
// 获取CacheNamespace所有缓存key
Map<Object, Object> idMap = getRedisTemplate().opsForHash().entries(hsKey);
if (!idMap.isEmpty()) {
Set<Object> keySet = idMap.keySet();
Set<String> keys = new HashSet<>(keySet.size());
keySet.forEach(item -> keys.add(item.toString()));
// 清空CacheNamespace下面所有缓存Key
getRedisTemplate().delete(keys);
// 清空CacheNamespace
getRedisTemplate().delete(hsKey);
}
} catch (Exception e) {
logger.error("clear cache failed", e);
}
}

/**
* 获取缓存大小,暂时没用上
*
* @return 长度
*/
@Override
public int getSize() {
return 0;
}

/**
* 获取读写锁:为了解决缓存击穿
*
* @return
*/
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}

/**
* 获取Redis服务接口
* 使用双重检查保证线程安全
*
* @return 服务实例
*/
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
synchronized (RedisTemplate.class) {
if (redisTemplate == null) {
redisTemplate = (RedisTemplate) ApplicationContextHolder.getBean("redisTemplate");
}
}
}
return redisTemplate;
}

private String generateRedisKey(Object key) {
String o = this.id + DigestUtils.sha256Hex(key.toString().getBytes());
logger.info(o);
return o;
}

public static byte[] append(byte[]... bas) {
int iLen = 0;
byte[][] var2 = bas;
int var3 = bas.length;

int var4;
for (var4 = 0; var4 < var3; ++var4) {
byte[] ba = var2[var4];
if (ba != null && ba.length > 0) {
iLen += ba.length;
}
}

byte[] result = new byte[iLen];
iLen = 0;
byte[][] var8 = bas;
var4 = bas.length;

for (int var9 = 0; var9 < var4; ++var9) {
byte[] ba = var8[var9];
if (ba != null && ba.length > 0) {
System.arraycopy(ba, 0, result, iLen, ba.length);
iLen += ba.length;
}
}

return result;
}

public int getMinExpireMinutes() {
return minExpireMinutes;
}

public MybatisRedisCache setMinExpireMinutes(int minExpireMinutes) {
this.minExpireMinutes = minExpireMinutes;
return this;
}

public int getMaxExpireMinutes() {
return maxExpireMinutes;
}

public MybatisRedisCache setMaxExpireMinutes(int maxExpireMinutes) {
this.maxExpireMinutes = maxExpireMinutes;
return this;
}
}

posted @ 2021-07-07 09:16  atliwen  阅读(391)  评论(1编辑  收藏  举报