在使用Spring Boot2.x 与 Redis 集成时,遇到如下异常,解决这个问题,让我费了些工夫,走了些弯路。
org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR no such key
org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR no such key
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:54)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52)\
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:257)
at org.springframework.data.redis.connection.lettuce.LettuceKeyCommands.convertLettuceAccessException(LettuceKeyCommands.java:650)
at org.springframework.data.redis.connection.lettuce.LettuceKeyCommands.rename(LettuceKeyCommands.java:249)
at org.springframework.data.redis.connection.lettuce.LettuceClusterKeyCommands.rename(LettuceClusterKeyCommands.java:119)
at org.springframework.data.redis.connection.DefaultedRedisConnection.rename(DefaultedRedisConnection.java:96)
at org.springframework.data.redis.core.RedisTemplate.lambda$rename$13(RedisTemplate.java:889)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
at org.springframework.data.redis.core.RedisTemplate.rename(RedisTemplate.java:888)
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveChangeSessionId(RedisOperationsSessionRepository.java:815)
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.saveDelta(RedisOperationsSessionRepository.java:772)
at org.springframework.session.data.redis.RedisOperationsSessionRepository$RedisSession.access$000(RedisOperationsSessionRepository.java:649)
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:384)
at org.springframework.session.data.redis.RedisOperationsSessionRepository.save(RedisOperationsSessionRepository.java:245)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:234)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access$100(SessionRepositoryFilter.java:197)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryResponseWrapper.onResponseCommitted(SessionRepositoryFilter.java:185)
at org.springframework.session.web.http.OnCommittedResponseWrapper.doOnResponseCommitted(OnCommittedResponseWrapper.java:227)
at org.springframework.session.web.http.OnCommittedResponseWrapper.access$000(OnCommittedResponseWrapper.java:38)
at org.springframework.session.web.http.OnCommittedResponseWrapper$SaveContextServletOutputStream.flush(OnCommittedResponseWrapper.java:494)
at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.flush(OnCommittedResponseWrapper.java:514)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.flush(UTF8JsonGenerator.java:1100)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:915)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:286)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:102)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:271)
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:218)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:119)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at ....
除了网上所说的那些解决方法外,试过了都没有解决我的现实问题。
此类的解决方案大致有如下几种方式:
1.添加 Spring boot Redis 和 Spring Session 支持, pom.xml 如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2.注意版本问题,我使用的Spring Boot 2.0 ,要使用与之匹配的 Redis 版本 。
3.Spring 提供 的StringRedisTemplate和RedisTempate的两个类的使用 ,如下错误代码也会报 no such key的异常,根本不管spring session什么事。
public boolean setString(String key,String value ){
try {
stringRedisTemplate.opsForList().set(key,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
所以在网上Copy工具类时,也不可完全放心的用,还是得仔细检查一翻较好!
以下配置文件及工具类仅供参考:
1.application.yml
spring:
# REDIS(RedisProperties)
# (普通集群,不使用则不用开启)在群集中执行命令时要遵循的最大重定向数目。
redis:
database: 0
host: 127.0.0.1
port: 6379
timeout: 60000
password:
ssl: false
pool:
max-active: 8
max-idle: 1
max-wait: -1
min-idle: 0
# Redis服务器端口。
#spring.redis.port=6379
# (哨兵模式,不使用则不用开启)Redis服务器的名称。
# spring.redis.sentinel.master=
# (哨兵模式,不使用则不用开启)主机:端口对的逗号分隔列表。
# spring.redis.sentinel.nodes=
# 以毫秒为单位的连接超时。
2.RedisConfig.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @ClassName RedisConfig
* @Description <p>TODO</p>
* @Author Jakemanse
* @Date 2018/11/7 16:50
* @Version 1.0
**/
@Configuration
@EnableCaching
public class RedisConfig {
/**
* 从application.yml取得redis的host地址.
*/
@Value("${spring.redis.host}")
private String redisHost;
/**
* 从application.yml取得redis的端口号.
*/
@Value("${spring.redis.port}")
private Integer redisPort;
/**
* Jedis 连接工厂.
*
* @return 配置好的Jedis连接工厂
*/
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration configuration =
new RedisStandaloneConfiguration(redisHost, redisPort);
return new JedisConnectionFactory(configuration);
}
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
/*
* Redis 序列化器.
*
* RedisTemplate 默认的系列化类是 JdkSerializationRedisSerializer,用JdkSerializationRedisSerializer序列化的话,
* 被序列化的对象必须实现Serializable接口。在存储内容时,除了属性的内容外还存了其它内容在里面,总长度长,且不容易阅读。
*
* Jackson2JsonRedisSerializer 和 GenericJackson2JsonRedisSerializer,两者都能系列化成 json,
* 但是后者会在 json 中加入 @class 属性,类的全路径包名,方便反系列化。前者如果存放了 List 则在反系列化的时候如果没指定
* TypeReference 则会报错 java.util.LinkedHashMap cannot be cast to
*/
RedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 定义RedisTemplate,并设置连接工程
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// key 的序列化采用 StringRedisSerializer
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value 值的序列化采用 GenericJackson2JsonRedisSerializer
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
// 设置连接工厂
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean
public CacheManager initRedisCacheManager(RedisConnectionFactory factory) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
.RedisCacheManagerBuilder.fromConnectionFactory(factory);
return builder.build();
}
}
2.RedisUtils.java
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @ClassName RedisUtils
* @Description <p>TODO</p>
* @Author Jakemanse
* @Date 2018/11/7 13:55
* @Version 1.0
**/
@Component
public class RedisUtils {
private static final Logger logger = Logger.getLogger(RedisUtils.class);
/** 字符串缓存模板 */
@Autowired
private StringRedisTemplate stringRedisTemplate;
/** 对象,集合缓存模板 */
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
public void reset(String key, Long seconds){
stringRedisTemplate.expire(key, seconds, TimeUnit.SECONDS);
}
/**
* 获取匹配的key
* @param pattern
* @return Set<String>
*/
public Set<String> keys(String pattern){
return stringRedisTemplate.keys(pattern);
}
/**
* 批量删除keys
* @param pattern
*/
public void delKeys(String pattern){
redisTemplate.delete(stringRedisTemplate.keys(pattern));
}
/**
* 添加Set集合
* @param key
* @param set
*/
public void addSet(String key ,Set<?> set){
try{
redisTemplate.opsForSet().add(key, set);
}catch(Exception ex){
ex.printStackTrace();
}
}
/**
* 获取Set集合
* @param key
* @return
*/
public Set<?> getSet(String key){
try{
return redisTemplate.opsForSet().members(key);
}catch(Exception ex){
ex.printStackTrace();
}
return null ;
}
public void addString(String key , String value,Long seconds){
try {
// 字符串redis 存储
ValueOperations<String, String> valOps = stringRedisTemplate.opsForValue();
if(seconds!=null){
valOps.set(key, value,seconds,TimeUnit.SECONDS);
}else{
valOps.set(key, value);
}
} catch (Exception e) {
logger.warn(spellString("addString {0}={1},{2}", key,value,seconds),e);
}
}
public String getString(String key) {
String result = "";
try {
result = stringRedisTemplate.opsForValue().get(key);
} catch (Exception e) {
logger.warn(spellString("getString {0}", key), e);
}
return result;
}
public void delString(String key) {
try {
stringRedisTemplate.delete(key);
} catch (Exception e) {
logger.warn(spellString("delString {0}", key),e);
}
}
public void delAllString(String key) {
if(key==null || "".equals(key)){
return;
}
try {
if (!key.endsWith("*")) {
key += "*";
}
Set<String> keys = stringRedisTemplate.keys(key);
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String singleKey = it.next();
delString(singleKey);
}
} catch (Exception e) {
logger.warn(spellString("delString {0}", key), e);
}
}
/**
* @see add 缓存数据
* @param key
* @param value
* @param time
*/
public void addObj(String key ,Object obj, Long seconds){
try {
//对象redis存储
ValueOperations<Object, Object> objOps = redisTemplate.opsForValue();
if(seconds!=null){
objOps.set(key, obj, seconds, TimeUnit.SECONDS);
}else{
objOps.set(key, obj);
}
} catch (Exception e) {
logger.warn(spellString("addObj {0}={1},{2}", key,obj,seconds),e);
}
}
/**
* @see get 缓存数据
* @param key
* @return Object
*/
public Object getObject(String key) {
Object object = null;
try {
object = redisTemplate.opsForValue().get(key);
} catch (Exception e) {
logger.warn(spellString("getObj {0}", key), e);
}
return object;
}
/**
* @see get 缓存数据
* @param key
* @return Object
*/
@SuppressWarnings({ "unchecked"})
public <T> T getObj(String key, T t) {
Object o = null;
try {
o = redisTemplate.opsForValue().get(key);
} catch (Exception e) {
logger.warn(spellString("getObj {0}->{1}", key, t), e);
}
return o == null ? null : (T) o;
}
public void expire(String key,long second){
try {
stringRedisTemplate.expire(key, second, TimeUnit.SECONDS);
} catch (Exception e) {
logger.warn(spellString("expire {0}={1}", key, second),e);
}
}
/**
* @see del 缓存数据
* @param key
*/
public void delObj(String key) {
try {
redisTemplate.delete(key);
} catch (Exception e) {
logger.warn(spellString("delObj {0}", key),e);
}
}
/**
* 压栈
*
* @param key
* @param value
* @return
*/
public Long push(String key, String value) {
Long result = 0l;
try {
result = stringRedisTemplate.opsForList().leftPush(key, value);
} catch (Exception e) {
logger.warn(spellString("push {0}={1}", key,value),e);
}
return result;
}
/**
* 出栈
*
* @param key
* @return
*/
public String pop(String key) {
String popResult = "";
try {
popResult = stringRedisTemplate.opsForList().leftPop(key);
} catch (Exception e) {
logger.warn(spellString("pop {0}", key), e);
}
return popResult;
}
/**
* 入队
*
* @param key
* @param value
* @return
*/
public Long in(String key, String value) {
Long inResult = 0l;
try {
inResult = stringRedisTemplate.opsForList().rightPush(key, value);
} catch (Exception e) {
logger.warn(spellString("in {0}={1}", key, value), e);
}
return inResult;
}
/**
* 出队
*
* @param key
* @return
*/
public String out(String key) {
String outResult = "";
try {
outResult = stringRedisTemplate.opsForList().leftPop(key);
} catch (Exception e) {
logger.warn(spellString("out {0}", key),e);
}
return outResult;
}
/**
* 栈/队列长
*
* @param key
* @return
*/
public Long length(String key) {
Long lengthResult = 0l;
try {
lengthResult = stringRedisTemplate.opsForList().size(key);
} catch (Exception e) {
logger.warn(spellString("length {0}", key), e);
}
return lengthResult;
}
/**
* 范围检索
*
* @param key
* @param start
* @param end
* @return
*/
public List<String> range(String key, int start, int end) {
List<String> rangeResult = null;
try {
rangeResult = stringRedisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
logger.warn(spellString("range {0},{1}-{2}", key, start, end), e);
}
return rangeResult;
}
/**
* 移除
*
* @param key
* @param i
* @param value
*/
public void remove(String key, long i, String value) {
try {
stringRedisTemplate.opsForList().remove(key, i, value);
} catch (Exception e) {
logger.warn(spellString("remove {0}={1},{2}", key,value,i),e);
}
}
/**
* 检索
*
* @param key
* @param index
* @return
*/
public String index(String key, long index) {
String indexResult = "";
try {
indexResult = stringRedisTemplate.opsForList().index(key, index);
} catch (Exception e) {
logger.warn(spellString("index {0}", key), e);
}
return indexResult;
}
/**
* 置值
*
* @param key
* @param index
* @param value
*/
public void setObject(String key, Object value,long index) {
try {
redisTemplate.opsForValue().set(key,value,index);
} catch (Exception e) {
logger.warn(spellString("set {0}={1},{2}", key,value,index),e);
}
}
public boolean setString(String key,String value ){
try {
stringRedisTemplate.opsForValue().set(key,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 裁剪
*
* @param key
* @param start
* @param end
*/
public void trim(String key, long start, int end) {
try {
stringRedisTemplate.opsForList().trim(key, start, end);
} catch (Exception e) {
logger.warn(spellString("trim {0},{1}-{2}", key,start,end),e);
}
}
/**
* 方法说明: 原子性自增
* @param key 自增的key
* @param value 每次自增的值
* @time: 2017年3月9日 下午4:28:21
* @return: Long
*/
public Long incr(String key, long value) {
Long incrResult = 0l;
try {
incrResult = stringRedisTemplate.opsForValue().increment(key, value);
} catch (Exception e) {
logger.warn(spellString("incr {0}={1}", key, value), e);
}
return incrResult;
}
/**
* 拼异常内容
* @param errStr
* @param arguments
* @return
*/
private String spellString(String errStr,Object ... arguments){
return MessageFormat.format(errStr,arguments);
}
}
使用工具类时,要特别注意,StringRedisTemplate和RedisTemplate的区别!