web-dev-qa-db-fra.com

Pourquoi les données sont-elles stockées avec des clés étranges dans Redis lors de l'utilisation de Jedis avec Spring Data?

J'utilise Spring Data Redis avec Jedis. J'essaie de stocker un hachage avec la clé vc:${list_id}. J'ai pu insérer avec succès dans Redis. Cependant, lorsque j'inspecte les clés avec redis-cli, je ne vois pas la clé vc:501381. Au lieu de cela, je vois \xac\xed\x00\x05t\x00\tvc:501381

Pourquoi cela se produit-il et comment puis-je changer cela? 

37
arun

Ok, googlé pendant un moment et trouvé de l'aide à http://Java.dzone.com/articles/spring-data-redis .

C'est arrivé à cause de la sérialisation Java. 

Le sérialiseur de clé pour redisTemplate doit être configuré sur StringRedisSerializer, par exemple:

<bean 
    id="jedisConnectionFactory" 
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" 
    p:Host-name="${redis.server}" 
    p:port="${redis.port}" 
    p:use-pool="true"/>

<bean 
    id="stringRedisSerializer" 
    class="org.springframework.data.redis.serializer.StringRedisSerializer"/>

<bean 
    id="redisTemplate" 
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" 
    p:keySerializer-ref="stringRedisSerializer"
    p:hashKeySerializer-ref="stringRedisSerializer" 
/>

Maintenant, la clé dans redis est vc:501381.

Ou, comme le dit @niconic, nous pouvons également définir le sérialiseur par défaut lui-même sur le sérialiseur de chaîne comme suit:

<bean 
    id="redisTemplate" 
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" 
    p:defaultSerializer-ref="stringRedisSerializer"
/>

ce qui signifie que toutes nos clés et valeurs sont des chaînes. Notez cependant que cela peut ne pas être préférable, car vous voudrez peut-être que vos valeurs ne soient pas que des chaînes.

Si votre valeur est un objet de domaine, vous pouvez utiliser le sérialiseur de Jackson et configurer un sérialiseur comme indiqué here i.e, comme ceci:

<bean id="userJsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
    <constructor-arg type="Java.lang.Class" value="com.mycompany.redis.domain.User"/>
</bean>

et configurez votre modèle comme suit:

<bean 
    id="redisTemplate" 
    class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory" 
    p:keySerializer-ref="stringRedisSerializer"
    p:hashKeySerializer-ref="stringRedisSerializer" 
    p:valueSerialier-ref="userJsonRedisSerializer"
/>
64
arun

Utilisez StringRedisTemplate pour remplacer RedisTemplate.

Par défaut, RedisTemplate utilise la sérialisation Java, StringRedisTemplate utilise StringRedisSerializer.

<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
6
xxg

Je sais que cette question dure depuis un certain temps, mais j’ai récemment effectué quelques recherches sur ce sujet. Je voudrais donc expliquer comment cette clé "semi-hachée" est générée en parcourant une partie du code source Spring ici.

Tout d'abord, Spring utilise AOP pour résoudre des annotations telles que @Cacheable, @CacheEvict or @CachePut, etc. La classe de conseil est CacheInterceptor à partir de la dépendance du contexte Spring, qui est une sous-classe de CacheAspectSupport (également de Spring-context). Pour la facilité de cette explication, j’utiliserais @Cacheable comme exemple pour parcourir une partie du code source ici.

Lorsque la méthode annotée en tant que @Cacheable est appelée, AOP l'achemine vers cette méthode protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) à partir de la classe CacheAspectSupport, dans laquelle elle tente de résoudre cette annotation @Cacheable. À son tour, cela conduit à l'appel de cette méthode public Cache getCache(String name) dans l'implémentation de CacheManager. Pour cette explication, la mise en œuvre de CacheManage serait RedisCacheManager (à partir de la dépendance Spring-data-redis).

Si le cache n'a pas été touché, il sera créé pour créer le cache. Vous trouverez ci-dessous les méthodes principales de RedisCacheManager

protected Cache getMissingCache(String name) {
    return this.dynamic ? createCache(name) : null;
}

@SuppressWarnings("unchecked")
protected RedisCache createCache(String cacheName) {
    long expiration = computeExpiration(cacheName);
    return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
            cacheNullValues);
}

Essentiellement, il instanciera un objet RedisCache. Pour ce faire, il nécessite 4 paramètres, à savoir cacheName, prefix (paramètre clé pour la réponse à cette question), redisOperation (également appelé redisTemplate configuré), expiration (0 par défaut) et cacheNullValues ​​( par défaut à false). Le constructeur ci-dessous montre plus de détails sur RedisCache.

/**
 * Constructs a new {@link RedisCache} instance.
 *
 * @param name cache name
 * @param prefix must not be {@literal null} or empty.
 * @param redisOperations
 * @param expiration
 * @param allowNullValues
 * @since 1.8
 */
public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
        long expiration, boolean allowNullValues) {

    super(allowNullValues);

    Assert.hasText(name, "CacheName must not be null or empty!");

    RedisSerializer<?> serializer = redisOperations.getValueSerializer() != null ? redisOperations.getValueSerializer()
            : (RedisSerializer<?>) new JdkSerializationRedisSerializer();

    this.cacheMetadata = new RedisCacheMetadata(name, prefix);
    this.cacheMetadata.setDefaultExpiration(expiration);
    this.redisOperations = redisOperations;
    this.cacheValueAccessor = new CacheValueAccessor(serializer);

    if (allowNullValues) {

        if (redisOperations.getValueSerializer() instanceof StringRedisSerializer
                || redisOperations.getValueSerializer() instanceof GenericToStringSerializer
                || redisOperations.getValueSerializer() instanceof JacksonJsonRedisSerializer
                || redisOperations.getValueSerializer() instanceof Jackson2JsonRedisSerializer) {
            throw new IllegalArgumentException(String.format(
                    "Redis does not allow keys with null value ¯\\_(ツ)_/¯. "
                            + "The chosen %s does not support generic type handling and therefore cannot be used with allowNullValues enabled. "
                            + "Please use a different RedisSerializer or disable null value support.",
                    ClassUtils.getShortName(redisOperations.getValueSerializer().getClass())));
        }
    }
}

Alors, quelle est l’utilisation de prefix dans ce RedisCache? -> Comme indiqué dans le constructeur about, il est utilisé dans cette instruction this.cacheMetadata = new RedisCacheMetadata(name, prefix);, et le constructeur de RedisCacheMetadata ci-dessous montre plus de détails:

/**
     * @param cacheName must not be {@literal null} or empty.
     * @param keyPrefix can be {@literal null}.
     */
    public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {

        Assert.hasText(cacheName, "CacheName must not be null or empty!");
        this.cacheName = cacheName;
        this.keyPrefix = keyPrefix;

        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        // name of the set holding the keys
        this.setOfKnownKeys = usesKeyPrefix() ? new byte[] {} : stringSerializer.serialize(cacheName + "~keys");
        this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
    }

À ce stade, nous savons que certains paramètres de préfixe ont été définis sur RedisCacheMetadata, mais comment exactement ce préfixe est-il utilisé pour former la clé dans Redis (par exemple,\xac\xed\x00\x00\x05t\x00\tvc: 501381 comme vous l'avez mentionné) ? 

Fondamentalement, CacheInterceptor avancera ensuite pour invoquer une méthode private RedisCacheKey getRedisCacheKey(Object key) à partir de l'objet RedisCache mentionné ci-dessus, qui renvoie une instance de RedisCacheKey en utilisant le préfixe prefix de RedisCacheMetadata et keySerializer de RedisOperation.

private RedisCacheKey getRedisCacheKey(Object key) {
    return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
            .withKeySerializer(redisOperations.getKeySerializer());
}

En atteignant ce point, le "pré-avis" de CacheInterceptor est terminé et la méthode actuelle annotée par @Cacheable sera exécutée. Et après l’achèvement de l’exécution de la méthode proprement dite, l’avis "post" de CacheInterceptor, qui met essentiellement le résultat à RedisCache, sera exécuté. Voici la méthode pour mettre le résultat dans le cache redis:

public void put(final Object key, final Object value) {

    put(new RedisCacheElement(getRedisCacheKey(key), toStoreValue(value))
            .expireAfter(cacheMetadata.getDefaultExpiration()));
}

/**
 * Add the element by adding {@link RedisCacheElement#get()} at {@link RedisCacheElement#getKeyBytes()}. If the cache
 * previously contained a mapping for this {@link RedisCacheElement#getKeyBytes()}, the old value is replaced by
 * {@link RedisCacheElement#get()}.
 *
 * @param element must not be {@literal null}.
 * @since 1.5
 */
public void put(RedisCacheElement element) {

    Assert.notNull(element, "Element must not be null!");

    redisOperations
            .execute(new RedisCachePutCallback(new BinaryRedisCacheElement(element, cacheValueAccessor), cacheMetadata));
}

Dans l'objet RedisCachePutCallback, sa méthode de rappel doInRedis() appelle en réalité une méthode pour former la clé réelle dans redis, et le nom de la méthode est getKeyBytes() à partir de RedisCacheKey instance. Ci-dessous montre les détails de cette méthode:

/**
 * Get the {@link Byte} representation of the given key element using prefix if available.
 */
public byte[] getKeyBytes() {

    byte[] rawKey = serializeKeyElement();
    if (!hasPrefix()) {
        return rawKey;
    }

    byte[] prefixedKey = Arrays.copyOf(prefix, prefix.length + rawKey.length);
    System.arraycopy(rawKey, 0, prefixedKey, prefix.length, rawKey.length);

    return prefixedKey;
}

Comme nous pouvons le constater dans la méthode getKeyBytes, elle utilise à la fois la clé brute (vc: 501381 dans votre cas) et la clé de préfixe (\ xac\xed\x00\x00\x05t\x00\t dans votre cas).

5
imarchuang

Vous devez sérialiser les objets que vous envoyez à Redis. Vous trouverez ci-dessous l'exemple complet en cours d'exécution. Il utilise l'interface DomainObject comme Serializable

Ci-dessous les étapes

1) faites votre maven pom.xml avec les pots suivants

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2.2</version>
    </dependency>

    <dependency>
           <groupId>org.springframework.data</groupId>
           <artifactId>spring-data-redis</artifactId>
           <version>1.3.0.RELEASE</version>
        </dependency>

            <dependency>
               <groupId>redis.clients</groupId>
               <artifactId>jedis</artifactId>
               <version>2.4.1</version>
            </dependency>

    <dependency>
        <groupId>org.Apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.0</version>
    </dependency>

2) faire votre configuration XML comme suit

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
        xmlns:c="http://www.springframework.org/schema/c"
        xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd

        http://www.springframework.org/schema/cache 
        http://www.springframework.org/schema/cache/spring-cache.xsd">



    <bean id="jeidsConnectionFactory"
      class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
      p:Host-name="localhost" p:port="6379" p:password="" />

     <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
      p:connection-factory-ref="jeidsConnectionFactory" />

     <bean id="imageRepository" class="com.self.common.api.poc.ImageRepository">
      <property name="redisTemplate" ref="redisTemplate"/>
     </bean>

</beans>

3) Faites vos cours comme suit

package com.self.common.api.poc;

import Java.awt.image.BufferedImage;
import Java.io.ByteArrayInputStream;
import Java.io.ByteArrayOutputStream;
import Java.io.File;
import Java.io.IOException;

import javax.imageio.ImageIO;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import Sun.misc.BASE64Decoder;
import Sun.misc.BASE64Encoder;

public class RedisMainApp {

 public static void main(String[] args) throws IOException {
  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mvc-dispatcher-servlet.xml");
  ImageRepository imageRepository = (ImageRepository) applicationContext.getBean("imageRepository");

  BufferedImage img = ImageIO.read(new File("files/img/TestImage.png"));
  BufferedImage newImg;
  String imagestr;
  imagestr = encodeToString(img, "png");
  Image image1 = new Image("1", imagestr);

  img = ImageIO.read(new File("files/img/TestImage2.png"));
  imagestr = encodeToString(img, "png");
  Image image2 = new Image("2", imagestr);

  imageRepository.put(image1);
  System.out.println(" Step 1 output : " + imageRepository.getObjects());
  imageRepository.put(image2);
  System.out.println(" Step 2 output : " + imageRepository.getObjects());
  imageRepository.delete(image1);
  System.out.println(" Step 3 output : " + imageRepository.getObjects());

 }

 /**
  * Decode string to image
  * @param imageString The string to decode
  * @return decoded image
  */
 public static BufferedImage decodeToImage(String imageString) {

     BufferedImage image = null;
     byte[] imageByte;
     try {
         BASE64Decoder decoder = new BASE64Decoder();
         imageByte = decoder.decodeBuffer(imageString);
         ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
         image = ImageIO.read(bis);
         bis.close();
     } catch (Exception e) {
         e.printStackTrace();
     }
     return image;
 }

 /**
  * Encode image to string
  * @param image The image to encode
  * @param type jpeg, bmp, ...
  * @return encoded string
  */
 public static String encodeToString(BufferedImage image, String type) {
     String imageString = null;
     ByteArrayOutputStream bos = new ByteArrayOutputStream();

     try {
         ImageIO.write(image, type, bos);
         byte[] imageBytes = bos.toByteArray();

         BASE64Encoder encoder = new BASE64Encoder();
         imageString = encoder.encode(imageBytes);

         bos.close();
     } catch (IOException e) {
         e.printStackTrace();
     }
     return imageString;
 }
}

package com.self.common.api.poc;

public class Image implements DomainObject {

 public static final String OBJECT_KEY = "IMAGE";

 public Image() {
 }

 public Image(String imageId, String imageAsStringBase64){
  this.imageId = imageId;
  this.imageAsStringBase64 = imageAsStringBase64;
 }
 private String imageId;
 private String imageAsStringBase64;

 public String getImageId() {
  return imageId;
 }

 public void setImageId(String imageId) {
  this.imageId = imageId;
 }

 public String getImageName() {
  return imageAsStringBase64;
 }

 public void setImageName(String imageAsStringBase64) {
  this.imageAsStringBase64 = imageAsStringBase64;
 }

 @Override
 public String toString() {
  return "User [id=" + imageAsStringBase64 + ", imageAsBase64String=" + imageAsStringBase64 + "]";
 }

 @Override
 public String getKey() {
  return getImageId();
 }

 @Override
 public String getObjectKey() {
  return OBJECT_KEY;
 }
}

package com.self.common.api.poc;

import Java.io.Serializable;

public interface DomainObject extends Serializable {

 String getKey();

 String getObjectKey();
}

package com.self.common.api.poc;

import Java.util.List;

import com.self.common.api.poc.DomainObject;

public interface Repository<V extends DomainObject> {

 void put(V obj);

 V get(V key);

 void delete(V key);

 List<V> getObjects();
}

package com.self.common.api.poc;

import Java.util.ArrayList;
import Java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import com.self.common.api.poc.DomainObject;

public class ImageRepository implements Repository<Image>{

 @Autowired
 private RedisTemplate<String,Image> redisTemplate;

 public RedisTemplate<String,Image> getRedisTemplate() {
  return redisTemplate;
 }

 public void setRedisTemplate(RedisTemplate<String,Image> redisTemplate) {
  this.redisTemplate = redisTemplate;
 }

 @Override
 public void put(Image image) {
  redisTemplate.opsForHash()
    .put(image.getObjectKey(), image.getKey(), image);
 }

 @Override
 public void delete(Image key) {
  redisTemplate.opsForHash().delete(key.getObjectKey(), key.getKey());
 }

 @Override
 public Image get(Image key) {
  return (Image) redisTemplate.opsForHash().get(key.getObjectKey(),
    key.getKey());
 }

 @Override
 public List<Image> getObjects() {
  List<Image> users = new ArrayList<Image>();
  for (Object user : redisTemplate.opsForHash().values(Image.OBJECT_KEY) ){
   users.add((Image) user);
  }
  return users;
 }

}

Pour plus de références sur sprinf jedis, vous pouvez consulter http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html

L'exemple de code provient de http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-exemple.html

0
ravi ranjan

C'est une très vieille question, mais ma réponse pourrait être utile pour quelqu'un qui a le même problème en travaillant avec Redis en utilisant Spring Boot . J'étais coincé sur le même problème lors du stockage des données de type hash dans Redis. J'ai écrit les modifications de fichier de configuration requises pour le RedisTemplate .

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.redis")
public class AppCofiguration {

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
        jedisConFactory.setHostName("127.0.0.1");
        jedisConFactory.setPort(6379);
        return jedisConFactory;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(jedisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());

        // the following is not required      
        template.setHashValueSerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        return template;
    }

}

Si le type de données est String, alors template.setHashValueSerializer(new StringRedisSerializer()); et template.setHashKeySerializer(new StringRedisSerializer()); ne sont pas requis.

0
Mithun Debnath