1.问题描述

最近使用redis时,遇到一个有意思的问题,问题代码如下:

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");
        }
}

如代码注释描述:

  • 问题1:先set key1,再get key1没有问题,但是再increment就会报错,报错内容如下:
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]
  • 问题2:先increment key2(key2之前不存在),再get key2就会报错,报错内容如下:
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]

2.问题分析

        分析上面的报错内容可知,两个问题都是redis存储的数据格式有问题导致,但是为什么会有这种问题呢?

        查阅spring文档(Spring Data Redis 10.8. Serializers)可知,Spring对Redis常用序列化的策略有两种

Multiple implementations are available (including two that have been already mentioned in this documentation):

  • JdkSerializationRedisSerializer, which is used by default for RedisCache and RedisTemplate.
  • the StringRedisSerializer.

        其中RedisTemplate的默认序列化策略是JdkSerializationRedisSerializer,而StringRedisTemplate的序列化策略是StringRedisSerializer

        RedisTemplate是使用的JdkSerializationRedisSerializer序列化,序列化后的值包含了对象信息,版本号,类信息等,是一串字符串,所以无法进行数值自增操作。

        而StringRedisTemplate序列化策略是字符串的值直接转为字节数组,所以存储到redis中是数值,所以可以进行自增操作。

        所以,因为代码中注入的是RedisTemplate实现,使用了JdkSerializationRedisSerializer作为序列化方法,所以set和get的时候都会进行序列化和反序列化,而increment操作不会进行序列化,所以导致上述两个问题。

3.解决办法

        解决方法很简单,JdkSerializationRedisSerializer序列化导致的问题,那么将序列化换成StringRedisSerializer即可。代码如下,直接注入RedisTemplate<String, String>StringRedisTemplate都可以获得StringRedisTemplate的实例,从而解决问题。

注意: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的序列化工具的区别:

  1. new StringRedisSerializer(), value只能为String(如代码1)
  2. new GenericToStringSerializer<>(Integer.class),根据入参的不同,可以设置不同类型的value,但最后保存到redis时会转为String,取出时,最后会转为设置的类型,较为方便(如代码2)
  3. 注意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");
        }
    }

其他

为了测试各个序列化器的区别,我做了个测试

@Test
    public void testSerializer(){

        RedisTemplate<String, String> template = new RedisTemplate<String, String>();
        template.setConnectionFactory(connectFactory);
        template.afterPropertiesSet();

        ValueOperations<String, String>  operations = template.opsForValue();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        operations.set("StringRedisSerializer", "1");

        template.setKeySerializer(new GenericToStringSerializer<String>(String.class));
        template.setValueSerializer(new GenericToStringSerializer<String>(String.class));
        operations.set("GenericToStringSerializer", "1");


        template.setKeySerializer(new GenericJackson2JsonRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        operations.set("GenericJackson2JsonRedisSerializer", "1");

        template.setKeySerializer(new Jackson2JsonRedisSerializer<String>(String.class));
        template.setValueSerializer(new Jackson2JsonRedisSerializer<String>(String.class));
        operations.set("Jackson2JsonRedisSerializer", "1");

        template.setKeySerializer(new JdkSerializationRedisSerializer());
        template.setValueSerializer(new JdkSerializationRedisSerializer());
        operations.set("JdkSerializationRedisSerializer", "1");
    }

来自:https://blog.csdn.net/wangjun5159/article/details/52387782

参考:

spring-data-redis中同时使用set()、get()、increment()的问题 - 程序员进化之路 - SegmentFault 思否

org.springframework.dao.InvalidDataAccessApiUsageException: ERR value is not an integer or out of ra_wangjun5159的博客-CSDN博客

将数据压缩为GZIP格式存入redis再取出解压_CH·ST的博客-CSDN博客_redis数据压缩

GZIP在redis存取过程中出现 java.util.zip.ZipException: Not in GZIP format错误(已解决)_CH·ST的博客-CSDN博客

Spring Boot Redis increment() 后获取结果失败 - 简单教程,简单编程

关于spring boot使用redis的increment()方法自增问题 - 青山与妙高 - 博客园

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐