最近开发代码,使用redis时,遇到一个有意思的问题,问题代码以下:html
class Test { /** * redis操做句柄 */ @Autowired private RedisTemplate redisTemplate; public void test() { // 先set key1,再get key1没有问题,可是再increment就会报错 redisTemplate.opsForValue().set("key1", 1); Integer val1 = redisTemplate.opsForValue().get("key1"); Long incr1 = redisTemplate.opsForValue().increment("key1"); // 先increment key2(key2以前不存在),再get key2就会报错 Long incr2 = redisTemplate.opsForValue().increment("key2"); Integer val2 = redisTemplate.opsForValue().get("key2"); } }
如代码注释描述:java
org.springframework.dao.InvalidDataAccessApiUsageException: ERR value is not an integer or out of range; nested exception is redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:69) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:42) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE]
org.springframework.data.redis.serializer.SerializationException: 反序列化对象失败; nested exception is com.caucho.hessian.io.HessianProtocolException: unknown code for readObject at 0x31 (1) at tech.joymo.framework.redis.sserializer.HessianSerializer.deserialize(HessianSerializer.java:61) ~[joymo-framework-redis-1.0-20210202.122946-5.jar:1.0-SNAPSHOT] at org.springframework.data.redis.core.AbstractOperations.deserializeValue(AbstractOperations.java:335) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:61) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:96) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE] at org.springframework.data.redis.core.DefaultValueOperations.get(DefaultValueOperations.java:53) ~[spring-data-redis-2.3.6.RELEASE.jar:2.3.6.RELEASE]
分析上面的报错内容可知,两个问题都是redis存储的数据格式有问题致使,可是为何会有这种问题呢?redis
查阅spring文档(Spring Data Redis 10.8. Serializers)可知,Spring对Redis经常使用序列化的策略有两种spring
Multiple implementations are available (including two that have been already mentioned in this documentation):数组
JdkSerializationRedisSerializer
, which is used by default forRedisCache
andRedisTemplate
.- the
StringRedisSerializer
.
其中RedisTemplate
的默认序列化策略是JdkSerializationRedisSerializer
,而StringRedisTemplate
的序列化策略是StringRedisSerializer
。ide
RedisTemplate是使用的JdkSerializationRedisSerializer序列化,序列化后的值包含了对象信息,版本号,类信息等,是一串字符串,因此没法进行数值自增操做。工具
而StringRedisTemplate序列化策略是字符串的值直接转为字节数组,因此存储到redis中是数值,因此能够进行自增操做。this
因此,由于代码中注入的是RedisTemplate
实现,使用了JdkSerializationRedisSerializer
做为序列化方法,因此set和get的时候都会进行序列化和反序列化,而increment操做不会进行序列化,因此致使上述两个问题。idea
解决方法很简单,JdkSerializationRedisSerializer
序列化致使的问题,那么将序列化换成StringRedisSerializer
便可。代码以下,直接注入RedisTemplate<String, String>
和StringRedisTemplate
均可以得到StringRedisTemplate的实例,从而解决问题。code
注意:StringRedisTemplate的实例value只能为String
class Test { /** * redis操做句柄 */ @Autowired private RedisTemplate<String, String> redisTemplate; /** * redis操做句柄 */ @Autowired private StringRedisTemplate stringRedisTemplate; public void test() { // 先set key1,再get key1没有问题,可是再increment就会报错 redisTemplate.opsForValue().set("key1", "1"); String val1 = redisTemplate.opsForValue().get("key1"); String incr1 = redisTemplate.opsForValue().increment("key1"); // 先increment key2(key2以前不存在),再get key2就会报错 String incr2 = redisTemplate.opsForValue().increment("key2"); String val2 = redisTemplate.opsForValue().get("key2"); } }
或者使用redisTemplate前设置序列化策略
注意:设置value的序列化工具的区别:
- new StringRedisSerializer(), value只能为String(如代码1)
- new GenericToStringSerializer<>(Integer.class),根据入参的不一样,能够设置不一样类型的value,但最后保存到redis时会转为String,取出时,最后会转为设置的类型,较为方便(如代码2)
- 注意1和2获取值是都必需要类型转换,这是由于不设置泛型时,默认出参类型为Object,因此为了代码清晰明确,建议设置key和value的类型,并且idea也不会高亮提醒(很丑)(如代码3)
代码1:
class Test { /** * redis操做句柄 */ @Autowired private RedisTemplate redisTemplate; public void test() { redisTemplate.setValueSerializer(new StringRedisSerializer()); // 先set key1,再get key1没有问题,可是再increment就会报错 redisTemplate.opsForValue().set("key1", "1"); String val1 = (String) redisTemplate.opsForValue().get("key1"); Long incr1 = redisTemplate.opsForValue().increment("key1"); // 先increment key2(key2以前不存在),再get key2就会报错 Long incr2 = redisTemplate.opsForValue().increment("key2"); String val2 = (String) redisTemplate.opsForValue().get("key2"); } }
代码2:
class Test { /** * redis操做句柄 */ @Autowired private RedisTemplate redisTemplate; public void test() { redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Integer.class)); // 先set key1,再get key1没有问题,可是再increment就会报错 redisTemplate.opsForValue().set("key1", 1); Integer val1 = (Integer) redisTemplate.opsForValue().get("key1"); Long incr1 = redisTemplate.opsForValue().increment("key1"); // 先increment key2(key2以前不存在),再get key2就会报错 Long incr2 = redisTemplate.opsForValue().increment("key2"); Integer val2 = (Integer) redisTemplate.opsForValue().get("key2"); } }
代码3:
class Test { /** * redis操做句柄 */ @Autowired private RedisTemplate<String, Integer> redisTemplate; public void test() { redisTemplate.setValueSerializer(new StringRedisSerializer()); // 先set key1,再get key1没有问题,可是再increment就会报错 redisTemplate.opsForValue().set("key1", 1); Integer val1 = redisTemplate.opsForValue().get("key1"); Long incr1 = redisTemplate.opsForValue().increment("key1"); // 先increment key2(key2以前不存在),再get key2就会报错 Long incr2 = redisTemplate.opsForValue().increment("key2"); Integer val2 = redisTemplate.opsForValue().get("key2"); } }