Spring-data-redisでset()、get()、increment()を同時に使用する問題

8571 ワード

Spring-data-redisでset()、get()、increment()を同時に使用する問題
1.問題の説明
最近コードを開発して、redisを使う時、1つの面白い問題に出会って、問題のコードは以下の通りです:
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 key 1、更にget key 1は問題ありませんが、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:incrementkey 2(key 2以前は存在しなかった)を先にincrement key 2してからget key 2がエラーを報告し、エラー内容は以下の通り:
  • 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に一般的にシーケンス化するポリシーには2つあることがわかります.
    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操作ではシーケンス化が行われないため,上記の2つの問題が生じる.
    3.解決策
    解決方法は簡単で、JdkSerializationRedisSerializerのシーケンス化による問題は、シーケンス化をStringRedisSerializerに置き換えればよい.コードは以下の通りであり、RedisTemplateStringRedisTemplateの両方を直接注入してStringRedisTemplateの例を得ることができ、それによって問題を解決することができる.
    注記:StringRedisTemplateのインスタンスvalueはStringのみ
    class Test {
    
            /**
             * redis    
             */
            @Autowired
            private RedisTemplate 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 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");
            }
        }