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中引入相关的依赖包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-keyvalue</artifactId>
<version>2.0.9.RELEASE</version>
</dependency>
第二步。redis.xml文件中配置,这里推荐使用jedisConnectionFactory连接池
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.2.xsd "
default-lazy-init="true">
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer" >
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer" >
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg type = "redis.clients.jedis.JedisPoolConfig" ref="jedisPoolConfig" />
<property name="hostName" value="172.30.252.123"></property>
<property name="port" value="6379"></property>
</bean>
<bean id = "jedisPoolConfig" class = "redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="300" />
<!-- 最大连接数 -->
<property name="maxTotal" value="2000" />
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="500" />
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="true" />
<!-- 在返回的时候检查有效性, 默认false -->
<property name="testOnReturn" value="false" />
<!-- 在空闲的时候检查有效性, 默认false -->
<property name="testWhileIdle" value="true" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="false" />
</bean>
<bean class="com.ums.redis.RedisCacheConfig"/>
<cache:annotation-driven cache-manager="redisCacheManager" key-generator="simpleKeyGenerator"
error-handler="errorHandler"/>
<bean id = "redisKeyGenerator" class = "com.ums.redis.RedisKeyGenerator" />
<bean id = "errorHandler" class = "com.ums.redis.RedisCacheErrorHandler"/>
<bean id = "redisUtil" class = "com.ums.redis.util.RedisUtil"/>
</beans>
redisTemplate的keySerializer和valueSerializer可以根据实际需要选择,本例使用的是SpringRedisSerializer(字符串)和JdkSerializationRedisSerializer(对象),适用于<String, Object>形式的键值对缓存,如果没有特殊需求的话,就已经够用了,因为JdkSerializationRedisSerializer效率还是挺高的。除此之外还支持Json的序列化格式。
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<Serializable, Object> 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<Serializable> 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<Serializable, Object> 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<Serializable, Object> 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<Serializable, Object> 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<Serializable, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
第三步。配置redisCacheConfig,创建一个redisCacheConfig类
@Configuration
public class RedisCacheConfig {
@Bean
public KeyGenerator simpleKeyGenerator() {
return new RedisKeyGenerator();
}
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
Map<String,RedisCacheConfiguration> initializeConfigs = initConfig();
return new RedisCacheManager(
RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(300)),
initializeConfigs
);
}
private static Map<String,RedisCacheConfiguration> initConfig(){
Map<String,RedisCacheConfiguration> configurations = new HashMap<String,RedisCacheConfiguration>();
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<String,RedisTable> cacheTableMapping = new HashMap<String, RedisTable>();
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<String,RedisTable> getMap(){
return cacheTableMapping;
}
}
这样三步以后,配置就算完成了。接下来在需要做缓存的地方加上我们的注解
@Cacheable(value="device")
public Device findDeviceById(String id) {
return this.getDeviceDao().findById(id);
}
value就是我们的表空间,@Cacheable会将方法返回的对象序列化填入缓存,再次调用该方法时,就可以直接读取缓存数据,除此之外还有@CacheEvict(缓存淘汰)等其他注解,拥有不同的功能。
这样就算集成完毕了。