Подтвердить что ты не робот

Weird redis key с данными spring Jedis

Я использую Spring Data Redis с Jedis. Я пытаюсь сохранить хэш с ключом vc:${list_id}. Мне удалось успешно вставить redis. Однако, когда я проверяю ключи через redis-cli, я не вижу ключа vc:501381. Вместо этого я вижу \xac\xed\x00\x05t\x00\tvc:501381. Почему это происходит и как это изменить?

4b9b3361

Ответ 1

Хорошо, немного разошлись и нашли помощь в http://java.dzone.com/articles/spring-data-redis.

Это произошло из-за сериализации Java.

Ключевой сериализатор для redisTemplate должен быть настроен на StringRedisSerializer, например:

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

Теперь клавишей redis является vc:501381.

Или, как и @niconic, мы также можем установить сериализатор по умолчанию для сериализатора строк следующим образом:

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

что означает, что все наши ключи и значения являются строками. Обратите внимание, однако, что это может быть не предпочтительным, так как вы можете хотеть, чтобы ваши значения были не просто строками.

Если ваше значение является объектом домена, вы можете использовать сериализатор Jackson и настроить сериализатор, как указано здесь, например:

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

и настройте свой шаблон как:

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

Ответ 2

Используйте StringRedisTemplate для замены RedisTemplate.

По умолчанию RedisTemplate использует сериализацию Java, StringRedisTemplate использует StringRedisSerializer.

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

Ответ 3

Я знаю, что этот вопрос прошел некоторое время, но недавно я немного исследовал эту тему, поэтому хотел бы поделиться тем, как этот "полу-хэшированный" ключ генерируется путем перехода через часть исходного кода spring здесь.

Прежде всего, spring использует AOP для разрешения аннотаций, таких как @Cacheable, @CacheEvict or @CachePut и т.д. Класс рекомендаций - это CacheInterceptor из Spring -контекстной зависимости, который является подклассом CacheAspectSupport (также из Spring -context). Для удобства этого объяснения я использовал бы @Cacheable в качестве примера, чтобы перейти сюда через часть исходного кода.

Когда метод, аннотированный как @Cacheable, вызывается, AOP направит его к этому методу protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) из класса CacheAspectSupport, в котором он попытается разрешить эту аннотацию @Cacheable. В свою очередь, это приводит к вызову этого метода public Cache getCache(String name) в реализации CacheManager. Для этого объяснения реализация CacheManage будет RedisCacheManager (из Spring -data-redis зависимости).

Если кеш не был удален, он будет продолжать создавать кеш. Ниже приведены ключевые методы из 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);
}

По существу, он создаст экземпляр объекта RedisCache. Для этого требуется 4 параметра: cacheName, префикс (это ключевой параметр для ответа на этот вопрос), redisOperation (иначе, сконфигурированный redisTemplate), expiration (по умолчанию 0) и cacheNullValues ​​(по умолчанию - false). Ниже приведено описание конструктора 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())));
        }
    }
}

Итак, что использование prefix в этом RedisCache? → Как показано в конструкторе about, он используется в этом выражении this.cacheMetadata = new RedisCacheMetadata(name, prefix);, а конструктор RedisCacheMetadata ниже показывает более подробную информацию:

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

В этот момент мы знаем, что для некоторого префиксного параметра установлено значение RedisCacheMetadata, но как именно этот префикс используется для формирования ключа в Redis (например,\xac\xed\x00\x05t\x00\tvc: 501381, как вы упомянули)?

В основном, CacheInterceptor будет впоследствии перемещаться вперед, чтобы вызвать метод private RedisCacheKey getRedisCacheKey(Object key) из вышеупомянутого объекта RedisCache, который возвращает экземпляр RedisCacheKey, используя префикс от RedisCacheMetadata и keySerializer от RedisOperation.

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

Достигнув этой точки, "pre" совет CacheInterceptor завершен, и он выполнит фактический метод, аннотированный @Cacheable. И после завершения выполнения фактического метода он выполнит "пост" совет CacheInterceptor, который по существу поместит результат в RedisCache. Ниже приведен метод возврата кэш-памяти:

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

Внутри объекта RedisCachePutCallback его метод обратного вызова doInRedis() фактически вызывает метод формирования фактического ключа в redis, а имя метода - getKeyBytes() из RedisCacheKey экземпляра. Ниже приведены сведения об этом методе:

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

Как мы видим в методе getKeyBytes, он использует как сырой ключ (vc: 501381 в вашем случае), так и префиксный ключ (\ xac\xed\x00\x05t\x00\t в вашем случае).

Ответ 4

Вы должны сериализовать те объекты, которые вы отправляете в redis. Ниже приведен полный пример его запуска. Он использует интерфейс DomainObject как Serializable

Ниже приведены шаги

1) сделайте свой maven pom.xml со следующими банками

    <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) сделайте конфигурацию xml следующим образом

<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) Сделайте свои классы следующим образом

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

}

Для получения дополнительной информации о sprinf jedis вы можете увидеть http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html

Пример кода берется из http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-example.html