2012-11-04 6 views
34

Sto usando Spring Data Redis con Jedis. Sto cercando di memorizzare un hash con la chiave vc:${list_id}. Sono stato in grado di inserire correttamente i redis. Tuttavia, quando controllo i tasti tramite redis-cli, non vedo la chiave vc:501381. Invece vedo \xac\xed\x00\x05t\x00\tvc:501381. Perché sta succedendo questo e come posso cambiarlo?Chiave redis strana con dati di primavera Jedis

risposta

56

Ok, ho cercato su Google un po 'di tempo e ho trovato aiuto allo http://java.dzone.com/articles/spring-data-redis.

È successo a causa della serializzazione Java.

Il serializzatore chiave per redisTemplate deve essere configurato per StringRedisSerializer vale a dire in questo modo:

<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" 
/> 

Ora la chiave in Redis è vc:501381.

O come @niconic dice, possiamo anche impostare il serializzatore predefinita stessa al serializzatore stringa come segue:

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

che significa tutte le nostre chiavi e valori sono stringhe. Si noti tuttavia che questo potrebbe non essere preferibile, dal momento che si potrebbe desiderare che i propri valori non siano solo stringhe.

Se il valore è un oggetto di dominio, quindi è possibile utilizzare Jackson serializzatore e configurare un serializzatore come accennato here vale a dire in questo modo:

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

e configurare il modello come:

<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" 
/> 
+3

Se si modifica il riferimento a tutti i serializzatori, potrebbe essere utile modificare direttamente il serializzatore predefinito. – reallynice

+0

nota che se si utilizza 'convertAndSend' con' redisTemplate' il 'p: keySerializer' e' hashKeySerializer' non sono sufficienti. cambiare il 'serializzatore predefinito' ha fatto il lavoro – oak

+1

Grazie! Perché ho usato il boot primaverile e l'ho risolto facendo riferimento a questo documento: http://stackoverflow.com/questions/34201135/spring-redis-read-configuration-from-application-properties-file – zhuguowei

0

È devi serializzare gli oggetti che stai inviando ai redis. Di seguito è riportato l'esempio completo in esecuzione. Esso utilizza l'interfaccia DomainObject come Serializable

Di seguito sono riportati i passaggi

1) Fai la tua pom.xml Maven con i seguenti barattoli

<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) rendere il vostro XML di configurazione come segue

<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) Rendi i tuoi corsi come segue

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

} 

Per ulteriori riferimento Jedi sprinf potete vedere http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html

codice di esempio è tratto da http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-example.html

4

Usa StringRedisTemplate per sostituire RedisTemplate.

Per impostazione predefinita, RedisTemplate utilizza la serializzazione Java, StringRedisTemplate utilizza StringRedisSerializer.

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

Questa è la risposta esatta. RedisTemplate utilizza la serializzazione Java quindi assicurati che la tua classe sia scritta con "implements java.io.Serializable", che è abbastanza. –

3

So che questa domanda è stato un po ', ma ho fatto qualche ricerca su questo argomento ancora una volta di recente, quindi vorrei condividere come questa "semi-hashing" chiave è generata da passando attraverso una parte della molla codice sorgente qui.

Prima di tutto, Primavera sfrutta AOP per risolvere le annotazioni come @Cacheable, @CacheEvict or @CachePut ecc La classe consiglio è CacheInterceptor dalla primavera-contesto di dipendenza, che è una sottoclasse di CacheAspectSupport (anche a partire dalla primavera-contesto). Per la semplicità di questa spiegazione, vorrei usare @Cacheable come esempio per passare attraverso parte del codice sorgente qui.

Quando il metodo annotato come @Cacheable viene invocato, AOP avrebbe percorso a questo metodo protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) da CacheAspectSupport di classe, in cui si sarebbe cercare di risolvere questo @Cacheable annotazione. A sua volta, porta all'invocazione di questo metodo public Cache getCache(String name) nell'implementazione di CacheManager. Per questa spiegazione, l'implementazione di CacheManage sarà RedisCacheManager (dalla dipendenza Spring-data-redis).

Se la cache non è stata colpita, andrà avanti per creare la cache. Sotto è i principali metodi di 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); 
} 

sostanza, si istanzia un oggetto RedisCache. Per fare ciò, richiede 4 parametri, ovvero, cacheName, prefisso (questo è il parametro chiave per quanto riguarda la risposta a questa domanda), redisOperation (ovvero, redisTemplate configurato), expiration (predefinito su 0) e cacheNullValues ​​(predefinito per false). Il costruttore di seguito mostra maggiori dettagli su 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()))); 
     } 
    } 
} 

Così che l'uso di prefix in questo RedisCache? -> Come mostrato nel costruttore riguardo, è usato in questa informativa this.cacheMetadata = new RedisCacheMetadata(name, prefix);, e il costruttore di RedisCacheMetadata sotto mostra maggiori dettagli:

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

A questo punto, sappiamo che alcuni parametri prefisso è stato impostato su RedisCacheMetadata , ma come esattamente questo prefisso utilizzato per formare la chiave in Redis (ad esempio, \ xac \ xed \ x00 \ x05t \ x00 \ tvc: 501381 come hai menzionato)?

Fondamentalmente, il CacheInterceptor sarà successivamente avanzare di richiamare un metodo private RedisCacheKey getRedisCacheKey(Object key) dalla suddetta RedisCache oggetto, che restituisce un'istanza di RedisCacheKey utilizzando il prefisso da RedisCacheMetadata e keySerializer da RedisOperation.

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

Con il raggiungimento di questo punto, il consiglio "pre" di CacheInterceptor è stato completato, e sarebbe andare avanti per eseguire il metodo effettivo annotata da @Cacheable. E dopo aver completato l'esecuzione del metodo effettivo, farà il consiglio "post" di CacheInterceptor, che in sostanza metterà il risultato su RedisCache. Sotto è il metodo di mettere il risultato a Redis cache:

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

All'interno dell'oggetto RedisCachePutCallback, il suo metodo di callback doInRedis() effettivamente invocare un metodo per formare la chiave effettiva in Redis, e il nome del metodo è getKeyBytes() da RedisCacheKey esempio.Qui di seguito mostra i dettagli di questo metodo:

/** 
* 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; 
} 

Come si può vedere nel metodo getKeyBytes, utilizza sia la chiave prima (vc: 501.381 nel tuo caso) e la chiave di prefisso (\ xac \ sso \ x00 \ x05t \ x00 \ t nel tuo caso).