spring-data-redis中同時使用set()、get()、increment()的問題

spring-data-redis中同時使用set()、get()、increment()的問題

1.問題描述

最近開發代碼,使用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

  • 問題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存儲的數據格式有問題致使,可是爲何會有這種問題呢?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 for RedisCache and RedisTemplate.
  • the StringRedisSerializer.

其中RedisTemplate的默認序列化策略是JdkSerializationRedisSerializer,而StringRedisTemplate的序列化策略是StringRedisSerializeride

RedisTemplate是使用的JdkSerializationRedisSerializer序列化,序列化後的值包含了對象信息,版本號,類信息等,是一串字符串,因此沒法進行數值自增操做。工具

而StringRedisTemplate序列化策略是字符串的值直接轉爲字節數組,因此存儲到redis中是數值,因此能夠進行自增操做。this

因此,由於代碼中注入的是RedisTemplate實現,使用了JdkSerializationRedisSerializer做爲序列化方法,因此set和get的時候都會進行序列化和反序列化,而increment操做不會進行序列化,因此致使上述兩個問題。idea

3.解決辦法

解決方法很簡單,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的序列化工具的區別:

  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");
        }
    }
相關文章
相關標籤/搜索