zoukankan      html  css  js  c++  java
  • 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;
    }
    }

  • 相关阅读:
    AngularJS--过滤器
    AngularJS--自定义指令和模板
    AngularJS多模块开发
    百度优先收录HTTPS网站?你的网站https还在等什么
    什么是HTTPS
    必须要懂得的密码技术
    如何处理服务器SSL收到了一个弱临时Diffie-Hellman 密钥?
    可以将代码签名证书安装在多台电脑上吗?
    学习第一天
    前端综合知识小集
  • 原文地址:https://www.cnblogs.com/atliwen/p/14979976.html
Copyright © 2011-2022 走看看