spring-data-redis支持以注解的方式集成redis缓存,2.x版本以后支持自定义异常处理,要求Spring5.0.7以上,jedis2.9以上, jdk1.8。
我使用的版本是Spring版本5.0.8, jedis版本2.9.0, spring-data-redis版本2.0.9。
第一步。先在POM中引入相关的依赖包
redis.clients
jedis
2.9.0
org.springframework.data
spring-data-redis
2.0.9.RELEASE
org.springframework.data
spring-data-keyvalue
2.0.9.RELEASE
第二步。redis.xml文件中配置,这里推荐使用jedisConnectionFactory连接池
redisTemplate的keySerializer和valueSerializer可以根据实际需要选择,本例使用的是SpringRedisSerializer(字符串)和JdkSerializationRedisSerializer(对象),适用于
redisKeyGenerator: 统一命名redis缓存的key。可以根据方法名,参数,组合成唯一的key。
public class RedisKeyGenerator implements KeyGenerator {
private static final Logger logger = LoggerFactory.getLogger(RedisKeyGenerator.class);
private static String NO_PARAM_KEY = "no_params";
@Override
public Object generate(Object target, Method method, Object... params) {
String sp = ":";
StringBuilder strBuilder = new StringBuilder(30);
// 类名
/* strBuilder.append(target.getClass().getSimpleName());
strBuilder.append(sp);*/
// 方法名
strBuilder.append(method.getName());
strBuilder.append(sp);
if (params.length > 0) {
int i =1;
// 参数值
for (Object object : params) {
if (isSimpleValueType(object.getClass())) {
strBuilder.append(object);
if(i++ < params.length) {
strBuilder.append(sp);
}
} else {
strBuilder.append(JSONObject.fromObject(object).toString());
if(i++ < params.length) {
strBuilder.append(sp);
}
}
}
} else {
strBuilder.append(NO_PARAM_KEY);
}
logger.info("=============newKey:{}",strBuilder.toString());
return strBuilder.toString();
}
public static boolean isSimpleValueType(Class> clazz) {
return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() ||
CharSequence.class.isAssignableFrom(clazz) ||
Number.class.isAssignableFrom(clazz) ||
Date.class.isAssignableFrom(clazz) ||
URI.class == clazz || URL.class == clazz ||
Locale.class == clazz || Class.class == clazz);
}
}
errorHandler: 统一处理异常。
public class RedisCacheErrorHandler extends SimpleCacheErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(RedisCacheErrorHandler.class);
private static final Logger fatallogger = LoggerFactory.getLogger("fatal");
private static final int NUM = 3;
// private final Cache delegate;
/* public RedisCacheErrorHandler(Cache redisCache) {
this.delegate = redisCache;
}*/
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
// logger.info("get失败");
logger.error("cache get error, cacheName:{}, key:{}, msg:", cache.getName(), key, exception);
// throw exception;
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
// logger.info("put失败");
logger.error("cache put error, cacheName:{}, key:{}, msg:", cache.getName(), key, exception);
// throw exception;
}
//注解删除redis缓存失败时,进行手动删除,成功直接退出,失败重试NUM次,同时打印fatal日志
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
boolean checkEvit = false;
for(int i=1; i <= NUM; i++) {
try{
// delegate.evict(key);
cache.evict(key);
checkEvit = true;
}catch (RuntimeException e) {
fatallogger.error("cache evict error:{}, cacheName:{}, key:{}, msg:", i, cache.getName(), key, exception);
}
if(checkEvit) {
break;
}
}
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
logger.error("cache clear error, cacheName:{}, msg:", cache.getName(), exception);
}
}
redisUtil: redis工具类,方便需要手动操作缓存的地方。
public final class RedisUtil {
@Resource
private RedisTemplate redisTemplate;
/**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量删除key
*
* @param pattern
*/
public void removePattern(final String pattern) {
Set keys = redisTemplate.keys(pattern);
if (keys.size() > 0)
redisTemplate.delete(keys);
}
/**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
log.info("redis缓存读取...key【{}】",key);
Object result = null;
ValueOperations operations = redisTemplate
.opsForValue();
result = operations.get(key);
redisTemplate.expire(key, 3600, TimeUnit.SECONDS);//刷新失效时间
return result;
}
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
log.info("redis缓存写入...key【{}】value【{}】expireTime【{}】",key,value);
ValueOperations operations = redisTemplate
.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
log.error("redis缓存写入异常",e);
}
return result;
}
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
log.info("redis缓存写入...key【{}】value【{}】expireTime【{}】",key,value,String.valueOf(expireTime));
ValueOperations operations = redisTemplate
.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
log.error("redis缓存写入异常",e);
}
return result;
}
public void setRedisTemplate(
RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
第三步。配置redisCacheConfig,创建一个redisCacheConfig类
@Configuration
public class RedisCacheConfig {
@Bean
public KeyGenerator simpleKeyGenerator() {
return new RedisKeyGenerator();
}
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
Map initializeConfigs = initConfig();
return new RedisCacheManager(
RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)),
initializeConfigs
);
}
private static Map initConfig(){
Map configurations = new HashMap();
for(RedisTable rt : RedisTable.values()){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(rt.getExpireTime()));
configurations.put(rt.getOrdinal(), config);
}
return configurations;
}
其实不写这个类也可以,我写这个类主要是为了通过向RedisCacheManager里面传参来指定不同表空间的不同缓存生存时间。没有这个需求的话,可以直接跳过这步。
我的生存时间都定义在RedisTable里
public enum RedisTable {
T_DEVICE("device","设备表",(long)3000),
T_TRMNL("trmnl","终端表",(long)3000),
T_MERCHANT("merchant","商户表",(long)3000);
/**缓存表名**/
private String ordinal;
/**中文名称**/
private String chineseName;
/**缓存过期时间(s)**/
private Long expireTime;
private RedisTable(String idx,String chineseName,Long expireTime) {
this.ordinal = idx;
this.chineseName = chineseName;
this.expireTime = expireTime;
}
public String getOrdinal() {
return ordinal;
}
@Override
public String toString() {
return super.toString().toLowerCase();
}
public String getChineseName() {
return chineseName;
}
public Long getExpireTime() {
return expireTime;
}
private static final Map cacheTableMapping = new HashMap();
static{
for(RedisTable it :RedisTable.values()){
cacheTableMapping.put(it.ordinal,it);
}
}
public static final RedisTable fromCacheTableName(String name){
return cacheTableMapping.get(name);
}
public static Map getMap(){
return cacheTableMapping;
}
}
这样三步以后,配置就算完成了。接下来在需要做缓存的地方加上我们的注解
@Cacheable(value="device")
public Device findDeviceById(String id) {
return this.getDeviceDao().findById(id);
}
value就是我们的表空间,@Cacheable会将方法返回的对象序列化填入缓存,再次调用该方法时,就可以直接读取缓存数据,除此之外还有@CacheEvict(缓存淘汰)等其他注解,拥有不同的功能。
这样就算集成完毕了。