软件编程
位置:首页>> 软件编程>> java编程>> MyBatis整合Redis实现二级缓存的示例代码

MyBatis整合Redis实现二级缓存的示例代码

作者:Leven  发布时间:2022-02-06 15:41:24 

标签:MyBatis,Redis,二级缓存

MyBatis框架提供了二级缓存接口,我们只需要实现它再开启配置就可以使用了。

特别注意,我们要解决缓存穿透、缓存穿透和缓存雪崩的问题,同时也要保证缓存性能。

具体实现说明,直接看代码注释吧!

1、开启配置

SpringBoot配置


mybatis:
configuration:
 cache-enabled: true

2、Redis配置以及服务接口

RedisConfig.java


package com.leven.mybatis.api.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* Redis缓存配置
* @author Leven
* @date 2019-09-07
*/
@Configuration
public class RedisConfig {

/**
  * 配置自定义redisTemplate
  * @return redisTemplate
  */
 @Bean
 public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
   RedisTemplate<String, Object> template = new RedisTemplate<>();
   template.setConnectionFactory(redisConnectionFactory);
   // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
   Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
   StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
   ObjectMapper mapper = new ObjectMapper();
   mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
   mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
   jackson2JsonRedisSerializer.setObjectMapper(mapper);
   template.setKeySerializer(stringRedisSerializer);
   template.setValueSerializer(jackson2JsonRedisSerializer);
   template.setHashKeySerializer(stringRedisSerializer);
   template.setHashValueSerializer(jackson2JsonRedisSerializer);
   template.afterPropertiesSet();
   return template;
 }
}

RedisService.java


package com.leven.mybatis.core.service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* redis基础服务接口
* @author Leven
* @date 2019-09-07
*/
public interface RedisService {
// =============================common============================
 /**
  * 指定缓存失效时间
  * @param key 键
  * @param time 时间(秒)
  */
 void expire(String key, long time);

/**
  * 指定缓存失效时间
  * @param key 键
  * @param expireAt 失效时间点
  * @return 处理结果
  */
 void expireAt(String key, Date expireAt);

/**
  * 根据key 获取过期时间
  * @param key 键 不能为null
  * @return 时间(秒) 返回0代表为永久有效
  */
 Long getExpire(String key);

/**
  * 判断key是否存在
  * @param key 键
  * @return true 存在 false不存在
  */
 Boolean hasKey(String key);

/**
  * 删除缓存
  * @param key 可以传一个值 或多个
  */
 void delete(String... key);

/**
  * 删除缓存
  * @param keys 可以传一个值 或多个
  */
 void delete(Collection<String> keys);

// ============================String=============================

/**
  * 普通缓存获取
  * @param key 键
  * @return 值
  */
 Object get(String key);

/**
  * 普通缓存放入
  * @param key 键
  * @param value 值
  */
 void set(String key, Object value);

/**
  * 普通缓存放入并设置时间
  * @param key 键
  * @param value 值
  * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
  */
 void set(String key, Object value, long time);

/**
  * 普通缓存放入并设置时间
  * @param key 键
  * @param value 值
  * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
  */
 void set(String key, Object value, long time, TimeUnit timeUnit);

/**
  * 递增
  * @param key 键
  * @param value 要增加几(大于0)
  * @return 递增后结果
  */
 Long incr(String key, long value);

/**
  * 递减
  * @param key 键
  * @param value 要减少几(大于0)
  * @return 递减后结果
  */
 Long decr(String key, long value);

// ================================Map=================================
 /**
  * HashGet
  * @param key 键 不能为null
  * @param item 项 不能为null
  * @return 值
  */
 Object hashGet(String key, String item);

/**
  * 获取hashKey对应的所有键值
  * @param key 键
  * @return 对应的多个键值
  */
 Map<Object, Object> hashEntries(String key);

/**
  * HashSet
  * @param key 键
  * @param map 对应多个键值
  */
 void hashSet(String key, Map<String, Object> map);

/**
  * HashSet 并设置时间
  * @param key 键
  * @param map 对应多个键值
  * @param time 时间(秒)
  */
 void hashSet(String key, Map<String, Object> map, long time);

/**
  * 向一张hash表中放入数据,如果不存在将创建
  * @param key 键
  * @param item 项
  * @param value 值
  */
 void hashSet(String key, String item, Object value);

/**
  * 向一张hash表中放入数据,如果不存在将创建
  * @param key 键
  * @param item 项
  * @param value 值
  * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
  */
 void hashSet(String key, String item, Object value, long time);

/**
  * 删除hash表中的值
  * @param key 键 不能为null
  * @param item 项 可以使多个 不能为null
  */
 void hashDelete(String key, Object... item);

/**
  * 删除hash表中的值
  * @param key 键 不能为null
  * @param items 项 可以使多个 不能为null
  */
 void hashDelete(String key, Collection items);

/**
  * 判断hash表中是否有该项的值
  * @param key 键 不能为null
  * @param item 项 不能为null
  * @return true 存在 false不存在
  */
 Boolean hashHasKey(String key, String item);

/**
  * hash递增 如果不存在,就会创建一个 并把新增后的值返回
  * @param key 键
  * @param item 项
  * @param value 要增加几(大于0)
  * @return 递增后结果
  */
 Double hashIncr(String key, String item, double value);

/**
  * hash递减
  * @param key 键
  * @param item 项
  * @param value 要减少记(小于0)
  * @return 递减后结果
  */
 Double hashDecr(String key, String item, double value);

// ============================set=============================
 /**
  * 根据key获取Set中的所有值
  * @param key 键
  * @return set集合
  */
 Set<Object> setGet(String key);

/**
  * 根据value从一个set中查询,是否存在
  * @param key 键
  * @param value 值
  * @return true 存在 false不存在
  */
 Boolean setIsMember(String key, Object value);

/**
  * 将数据放入set缓存
  * @param key 键
  * @param values 值 可以是多个
  * @return 成功个数
  */
 Long setAdd(String key, Object... values);

/**
  * 将数据放入set缓存
  * @param key 键
  * @param values 值 可以是多个
  * @return 成功个数
  */
 Long setAdd(String key, Collection values);

/**
  * 将set数据放入缓存
  * @param key 键
  * @param time 时间(秒)
  * @param values 值 可以是多个
  * @return 成功个数
  */
 Long setAdd(String key, long time, Object... values);

/**
  * 获取set缓存的长度
  * @param key 键
  * @return set长度
  */
 Long setSize(String key);

/**
  * 移除值为value的
  * @param key 键
  * @param values 值 可以是多个
  * @return 移除的个数
  */
 Long setRemove(String key, Object... values);

// ===============================list=================================
 /**
  * 获取list缓存的内容
  * @param key 键
  * @param start 开始
  * @param end 结束 0 到 -1代表所有值
  * @return 缓存列表
  */
 List<Object> listRange(String key, long start, long end);

/**
  * 获取list缓存的长度
  * @param key 键
  * @return 长度
  */
 Long listSize(String key);

/**
  * 通过索引 获取list中的值
  * @param key 键
  * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
  * @return 值
  */
 Object listIndex(String key, long index);

/**
  * 将list放入缓存
  * @param key 键
  * @param value 值
  */
 void listRightPush(String key, Object value);

/**
  * 将list放入缓存
  * @param key 键
  * @param value 值
  * @param time 时间(秒)
  */
 void listRightPush(String key, Object value, long time);

/**
  * 将list放入缓存
  * @param key 键
  * @param value 值
  */
 void listRightPushAll(String key, List<Object> value);

/**
  * 将list放入缓存
  *
  * @param key 键
  * @param value 值
  * @param time 时间(秒)
  */
 void listRightPushAll(String key, List<Object> value, long time);

/**
  * 根据索引修改list中的某条数据
  * @param key 键
  * @param index 索引
  * @param value 值
  */
 void listSet(String key, long index, Object value);

/**
  * 移除N个值为value
  * @param key 键
  * @param count 移除多少个
  * @param value 值
  * @return 移除的个数
  */
 Long listRemove(String key, long count, Object value);
}

RedisServiceImpl.java


package com.leven.mybatis.core.service.impl;

import com.leven.commons.model.exception.SPIException;
import com.leven.mybatis.model.constant.Constant;
import com.leven.mybatis.core.service.RedisService;
import com.leven.mybatis.model.constant.ExceptionCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* redis基础服务接口实现
* @author Leven
* @date 2019-09-07
*/
@Slf4j
@Service
public class RedisServiceImpl implements RedisService {

/**
  *
  */
 private static final String PREFIX = Constant.APPLICATION;

@Autowired
 private RedisTemplate<String, Object> redisTemplate;

// =============================common============================
 /**
  * 指定缓存失效时间
  * @param key 键
  * @param time 时间(秒)
  */
 @Override
 public void expire(String key, long time) {
   redisTemplate.expire(getKey(key), time, TimeUnit.SECONDS);
 }

/**
  * 指定缓存失效时间
  * @param key 键
  * @param expireAt 失效时间点
  * @return 处理结果
  */
 @Override
 public void expireAt(String key, Date expireAt) {
   redisTemplate.expireAt(getKey(key), expireAt);
 }

/**
  * 根据key 获取过期时间
  * @param key 键 不能为null
  * @return 时间(秒) 返回0代表为永久有效
  */
 @Override
 public Long getExpire(String key) {
   return redisTemplate.getExpire(getKey(key), TimeUnit.SECONDS);
 }

/**
  * 判断key是否存在
  * @param key 键
  * @return true 存在 false不存在
  */
 @Override
 public Boolean hasKey(String key) {
   return redisTemplate.hasKey(getKey(key));
 }

/**
  * 删除缓存
  * @param keys 可以传一个值 或多个
  */
 @Override
 public void delete(String... keys) {
   if (keys != null && keys.length > 0) {
     if (keys.length == 1) {
       redisTemplate.delete(getKey(keys[0]));
     } else {
       List<String> keyList = new ArrayList<>(keys.length);
       for (String key : keys) {
         keyList.add(getKey(key));
       }
       redisTemplate.delete(keyList);
     }
   }
 }

/**
  * 删除缓存
  * @param keys 可以传一个值 或多个
  */
 @Override
 public void delete(Collection<String> keys) {
   if (keys != null && !keys.isEmpty()) {
     List<String> keyList = new ArrayList<>(keys.size());
     for (String key : keys) {
       keyList.add(getKey(key));
     }
     redisTemplate.delete(keyList);
   }
 }

// ============================String=============================
 /**
  * 普通缓存获取
  * @param key 键
  * @return 值
  */
 @Override
 public Object get(String key) {
   return key == null ? null : redisTemplate.opsForValue().get(getKey(key));
 }

/**
  * 普通缓存放入
  * @param key 键
  * @param value 值
  */
 @Override
 public void set(String key, Object value) {
   redisTemplate.opsForValue().set(getKey(key), value);
 }

/**
  * 普通缓存放入并设置时间
  * @param key 键
  * @param value 值
  * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
  */
 @Override
 public void set(String key, Object value, long time) {
   set(key, value, time, TimeUnit.SECONDS);
 }

/**
  * 普通缓存放入并设置时间
  * @param key 键
  * @param value 值
  * @param time 时间 time要大于0 如果time小于等于0 将设置无限期
  * @param timeUnit 时间单位
  */
 @Override
 public void set(String key, Object value, long time, TimeUnit timeUnit) {
   if (time > 0) {
     redisTemplate.opsForValue().set(getKey(key), value, time, timeUnit);
   } else {
     set(getKey(key), value);
   }
 }

/**
  * 递增
  * @param key 键
  * @param value 要增加几(大于0)
  * @return 递增后结果
  */
 @Override
 public Long incr(String key, long value) {
   if (value < 1) {
     throw new SPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递增因子必须大于0");
   }
   return redisTemplate.opsForValue().increment(getKey(key), value);
 }

/**
  * 递减
  * @param key 键
  * @param value 要减少几(大于0)
  * @return 递减后结果
  */
 @Override
 public Long decr(String key, long value) {
   if (value < 1) {
     throw new SPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递减因子必须大于0");
   }
   return redisTemplate.opsForValue().decrement(getKey(key), value);
 }

// ================================Map=================================
 /**
  * HashGet
  * @param key 键 不能为null
  * @param item 项 不能为null
  * @return 值
  */
 @Override
 public Object hashGet(String key, String item) {
   return redisTemplate.opsForHash().get(getKey(key), item);
 }

/**
  * 获取hashKey对应的所有键值
  * @param key 键
  * @return 对应的多个键值
  */
 @Override
 public Map<Object, Object> hashEntries(String key) {
   return redisTemplate.opsForHash().entries(getKey(key));
 }

/**
  * HashSet
  * @param key 键
  * @param map 对应多个键值
  */
 @Override
 public void hashSet(String key, Map<String, Object> map) {
   redisTemplate.opsForHash().putAll(getKey(key), map);
 }

/**
  * HashSet 并设置时间
  * @param key 键
  * @param map 对应多个键值
  * @param time 时间(秒)
  */
 @Override
 public void hashSet(String key, Map<String, Object> map, long time) {
   String k = getKey(key);
   redisTemplate.opsForHash().putAll(k, map);
   if (time > 0) {
     expire(k, time);
   }
 }

/**
  * 向一张hash表中放入数据,如果不存在将创建
  * @param key 键
  * @param item 项
  * @param value 值
  */
 @Override
 public void hashSet(String key, String item, Object value) {
   redisTemplate.opsForHash().putIfAbsent(getKey(key), item, value);
 }

/**
  * 向一张hash表中放入数据,如果不存在将创建
  * @param key 键
  * @param item 项
  * @param value 值
  * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
  */
 @Override
 public void hashSet(String key, String item, Object value, long time) {
   String k = getKey(key);
   redisTemplate.opsForHash().putIfAbsent(k, item, value);
   if (time > 0) {
     expire(k, time);
   }
 }

/**
  * 删除hash表中的值
  * @param key 键 不能为null
  * @param item 项 可以使多个 不能为null
  */
 @Override
 public void hashDelete(String key, Object... item) {
   redisTemplate.opsForHash().delete(getKey(key), item);
 }

/**
  * 删除hash表中的值
  * @param key 键 不能为null
  * @param items 项 可以使多个 不能为null
  */
 @Override
 public void hashDelete(String key, Collection items) {
   redisTemplate.opsForHash().delete(getKey(key), items.toArray());
 }

/**
  * 判断hash表中是否有该项的值
  * @param key 键 不能为null
  * @param item 项 不能为null
  * @return true 存在 false不存在
  */
 @Override
 public Boolean hashHasKey(String key, String item) {
   return redisTemplate.opsForHash().hasKey(getKey(key), item);
 }

/**
  * hash递增 如果不存在,就会创建一个 并把新增后的值返回
  * @param key 键
  * @param item 项
  * @param value 要增加几(大于0)
  * @return 递增后结果
  */
 @Override
 public Double hashIncr(String key, String item, double value) {
   if (value < 1) {
     throw new SPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递增因子必须大于0");
   }
   return redisTemplate.opsForHash().increment(getKey(key), item, value);
 }

/**
  * hash递减
  * @param key 键
  * @param item 项
  * @param value 要减少记(小于0)
  * @return 递减后结果
  */
 @Override
 public Double hashDecr(String key, String item, double value) {
   if (value < 1) {
     throw new SPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递减因子必须大于0");
   }
   return redisTemplate.opsForHash().increment(getKey(key), item, -value);
 }

// ============================set=============================
 /**
  * 根据key获取Set中的所有值
  * @param key 键
  * @return set集合
  */
 @Override
 public Set<Object> setGet(String key) {
   return redisTemplate.opsForSet().members(getKey(key));
 }

/**
  * 根据value从一个set中查询,是否存在
  * @param key 键
  * @param value 值
  * @return true 存在 false不存在
  */
 @Override
 public Boolean setIsMember(String key, Object value) {
   return redisTemplate.opsForSet().isMember(getKey(key), value);
 }

/**
  * 将数据放入set缓存
  * @param key 键
  * @param values 值 可以是多个
  * @return 成功个数
  */
 @Override
 public Long setAdd(String key, Object... values) {
   return redisTemplate.opsForSet().add(getKey(key), values);
 }

/**
  * 将数据放入set缓存
  * @param key 键
  * @param values 值 可以是多个
  * @return 成功个数
  */
 @Override
 public Long setAdd(String key, Collection values) {
   return redisTemplate.opsForSet().add(getKey(key), values.toArray());
 }

/**
  * 将set数据放入缓存
  * @param key 键
  * @param time 时间(秒)
  * @param values 值 可以是多个
  * @return 成功个数
  */
 @Override
 public Long setAdd(String key, long time, Object... values) {
   String k = getKey(key);
   Long count = redisTemplate.opsForSet().add(k, values);
   if (time > 0){
     expire(k, time);
   }
   return count;
 }

/**
  * 获取set缓存的长度
  * @param key 键
  * @return set长度
  */
 @Override
 public Long setSize(String key) {
   return redisTemplate.opsForSet().size(getKey(key));
 }

/**
  * 移除值为value的
  * @param key 键
  * @param values 值 可以是多个
  * @return 移除的个数
  */
 @Override
 public Long setRemove(String key, Object... values) {
   return redisTemplate.opsForSet().remove(getKey(key), values);
 }

// ===============================list=================================
 /**
  * 获取list缓存的内容
  * @param key 键
  * @param start 开始
  * @param end 结束 0 到 -1代表所有值
  * @return 缓存列表
  */
 @Override
 public List<Object> listRange(String key, long start, long end) {
   return redisTemplate.opsForList().range(getKey(key), start, end);
 }

/**
  * 获取list缓存的长度
  * @param key 键
  * @return 长度
  */
 @Override
 public Long listSize(String key) {
   return redisTemplate.opsForList().size(getKey(key));
 }

/**
  * 通过索引 获取list中的值
  * @param key 键
  * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
  * @return 值
  */
 @Override
 public Object listIndex(String key, long index) {
   return redisTemplate.opsForList().index(getKey(key), index);
 }

/**
  * 将list放入缓存
  * @param key 键
  * @param value 值
  */
 @Override
 public void listRightPush(String key, Object value) {
   redisTemplate.opsForList().rightPush(getKey(key), value);
 }

/**
  * 将list放入缓存
  * @param key 键
  * @param value 值
  * @param time 时间(秒)
  */
 @Override
 public void listRightPush(String key, Object value, long time) {
   String k = getKey(key);
   redisTemplate.opsForList().rightPush(k, value);
   if (time > 0){
     expire(k, time);
   }
 }

/**
  * 将list放入缓存
  * @param key 键
  * @param value 值
  */
 @Override
 public void listRightPushAll(String key, List<Object> value) {
   redisTemplate.opsForList().rightPushAll(getKey(key), value);
 }

/**
  * 将list放入缓存
  *
  * @param key 键
  * @param value 值
  * @param time 时间(秒)
  */
 @Override
 public void listRightPushAll(String key, List<Object> value, long time) {
   String k = getKey(key);
   redisTemplate.opsForList().rightPushAll(k, value);
   if (time > 0) {
     expire(k, time);
   }
 }

/**
  * 根据索引修改list中的某条数据
  * @param key 键
  * @param index 索引
  * @param value 值
  */
 @Override
 public void listSet(String key, long index, Object value) {
   redisTemplate.opsForList().set(getKey(key), index, value);
 }

/**
  * 移除N个值为value
  * @param key 键
  * @param count 移除多少个
  * @param value 值
  * @return 移除的个数
  */
 @Override
 public Long listRemove(String key, long count, Object value) {
   return redisTemplate.opsForList().remove(getKey(key), count, value);
 }

private String getKey(String key) {
   return PREFIX + ":" + key;
 }
}

3、实现MyBatis的Cache接口

MybatisRedisCache.java


package com.leven.mybatis.core.cache;

import com.leven.commons.core.util.ApplicationContextUtils;
import com.leven.commons.model.exception.SPIException;
import com.leven.mybatis.core.service.RedisService;
import com.leven.mybatis.model.constant.ExceptionCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.ibatis.cache.Cache;

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不能过长,会影响性能,这里使用SHA-256计算摘要当成key
* @author Leven
* @date 2019-09-07
*/
@Slf4j
public class MybatisRedisCache implements Cache {

/**
  * 统一字符集
  */
 private static final String CHARSET = "utf-8";
 /**
  * key摘要算法
  */
 private static final String ALGORITHM = "SHA-256";
 /**
  * 统一缓存头
  */
 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 volatile MessageDigest messageDigest;

/////////////////////// 解决缓存雪崩,具体范围根据业务需要设置合理值 //////////////////////////
 /**
  * 缓存最小有效期
  */
 private static final int MIN_EXPIRE_MINUTES = 60;
 /**
  * 缓存最大有效期
  */
 private static final int MAX_EXPIRE_MINUTES = 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 = getKey(key);
     // 有效期为1~2小时之间随机,防止雪崩
     int expireMinutes = RandomUtils.nextInt(MIN_EXPIRE_MINUTES, MAX_EXPIRE_MINUTES);
     getRedisService().set(strKey, value, expireMinutes, TimeUnit.MINUTES);
     log.debug("Put cache to redis, id={}", id);
   } catch (Exception e) {
     log.error("Redis put failed, id=" + id, e);
   }
 }

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

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

/**
  * 缓存清理
  * 网上好多博客这里用了flushDb甚至是flushAll,感觉好坑鸭!
  * 应该是根据表空间进行清理
  */
 @Override
 public void clear() {
   try {
     log.debug("clear cache, id={}", id);
     String hsKey = CACHE_NAME + id;
     // 获取CacheNamespace所有缓存key
     Map<Object, Object> idMap = getRedisService().hashEntries(hsKey);
     if (!idMap.isEmpty()) {
       Set<Object> keySet = idMap.keySet();
       Set<String> keys = new HashSet<>(keySet.size());
       keySet.forEach(item -> keys.add(item.toString()));
       // 清空CacheNamespace所有缓存
       getRedisService().delete(keys);
       // 清空CacheNamespace
       getRedisService().delete(hsKey);
     }
   } catch (Exception e) {
     log.error("clear cache failed", e);
   }
 }

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

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

/**
  * 计算出key的摘要
  * @param cacheKey CacheKey
  * @return 字符串key
  */
 private String getKey(Object cacheKey) {
   String cacheKeyStr = cacheKey.toString();
   log.debug("count hash key, cache key origin string:{}", cacheKeyStr);
   String strKey = byte2hex(getSHADigest(cacheKeyStr));
   log.debug("hash key:{}", strKey);
   String key = CACHE_NAME + strKey;
   // 在redis额外维护CacheNamespace创建的key,clear的时候只清理当前CacheNamespace的数据
   getRedisService().hashSet(CACHE_NAME + id, key, "1");
   return key;
 }

/**
  * 获取信息摘要
  * @param data 待计算字符串
  * @return 字节数组
  */
 private byte[] getSHADigest(String data) {
   try {
     if (messageDigest == null) {
       synchronized (MessageDigest.class) {
         if (messageDigest == null) {
           messageDigest = MessageDigest.getInstance(ALGORITHM);
         }
       }
     }
     return messageDigest.digest(data.getBytes(CHARSET));
   } catch (Exception e) {
     log.error("SHA-256 digest error: ", e);
     throw new SPIException(ExceptionCode.RUNTIME_UNITE_EXP,"SHA-256 digest error, id=" + id + ".");
   }
 }

/**
  * 字节数组转16进制字符串
  * @param bytes 待转换数组
  * @return 16进制字符串
  */
 private String byte2hex(byte[] bytes) {
   StringBuilder sign = new StringBuilder();
   for (byte aByte : bytes) {
     String hex = Integer.toHexString(aByte & 0xFF);
     if (hex.length() == 1) {
       sign.append("0");
     }
     sign.append(hex.toUpperCase());
   }
   return sign.toString();
 }

/**
  * 获取Redis服务接口
  * 使用双重检查保证线程安全
  * @return 服务实例
  */
 private RedisService getRedisService() {
   if (redisService == null) {
     synchronized (RedisService.class) {
       if (redisService == null) {
         redisService = ApplicationContextUtils.getBeanByClass(RedisService.class);
       }
     }
   }
   return redisService;
 }
}

来源:https://segmentfault.com/a/1190000023667657

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com