redistemplate - spring-boot-starter-data-redis
Llave redis extraña con datos de primavera Jedis (5)
Estoy usando Spring Data Redis con Jedis. Estoy tratando de almacenar un hash con la clave vc:${list_id}
. Pude insertar con éxito en redis. Sin embargo, cuando inspecciono las claves a través de redis-cli, no veo la clave vc:501381
. En su lugar, veo /xac/xed/x00/x05t/x00/tvc:501381
. ¿Por qué está sucediendo esto y cómo lo cambio?
Es una pregunta muy antigua, pero mi respuesta podría ser útil para alguien que tiene el mismo problema al trabajar en redis con arranque de primavera . Estaba trabajando en el mismo problema mientras almacenaba los datos de tipo hash en redis. He escrito los cambios requeridos del archivo de configuración para Spring boot RedisTemplate Bean.
@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());
template.setHashValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
return template;
}
}
Si el tipo de datos es Cadena, entonces template.setHashValueSerializer (new StringRedisSerializer ()); y template.setHashKeySerializer (new StringRedisSerializer ()); no son necesarios.
Ok, busqué en Google por un tiempo y encontré ayuda en http://java.dzone.com/articles/spring-data-redis .
Sucedió debido a la serialización de Java.
El serializador de claves para redisTemplate debe configurarse en StringRedisSerializer
es decir, así:
<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"
/>
Ahora la clave en redis es vc:501381
.
O como dice @niconic, también podemos configurar el serializador predeterminado para el serializador de cadenas de la siguiente manera:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:defaultSerializer-ref="stringRedisSerializer"
/>
lo que significa que todas nuestras claves y valores son cadenas. Sin embargo, tenga en cuenta que esto puede no ser preferible, ya que es posible que desee que sus valores no sean solo cadenas.
Si su valor es un objeto de dominio, puede usar el serializador Jackson y configurar un serializador como se menciona here es decir, así:
<bean id="userJsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
<constructor-arg type="java.lang.Class" value="com.mycompany.redis.domain.User"/>
</bean>
y configura tu plantilla como:
<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"
/>
Sé que esta pregunta ha pasado un tiempo, pero investigué un poco más sobre este tema recientemente, así que me gustaría compartir cómo se genera esta clave "semi-hash" yendo a través de parte del código fuente de la primavera aquí.
En primer lugar, Spring aprovecha AOP para resolver anotaciones como @Cacheable, @CacheEvict or @CachePut
etc. La clase de consejos es CacheInterceptor
de Spring-context dependency, que es una subclase de CacheAspectSupport
(también de Spring-context). Para facilitar esta explicación, usaría @Cacheable
como ejemplo para @Cacheable
una parte del código fuente aquí.
Cuando se invoca el método anotado como @Cacheable
, AOP lo @Cacheable
a este método protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver)
protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver)
de la clase CacheAspectSupport
, en la que intentará resolver esta anotación @Cacheable
. A su vez, lleva a la invocación de este método public Cache getCache(String name)
en la implementación de CacheManager. Para esta explicación, la implementación de CacheManage sería RedisCacheManager
(de Spring-data-redis dependency).
Si no se golpeó la memoria caché, continuará para crear la memoria caché. A continuación se detallan los métodos clave 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);
}
Básicamente, creará una instancia de un objeto RedisCache
. Para ello, requiere 4 parámetros, a saber, cacheName, prefix (este es el parámetro clave para responder a esta pregunta), redisOperation (también conocido como redisTemplate configurado), expiration (valor predeterminado a 0) y cacheNullValues (predeterminado a falso) . El constructor a continuación muestra más detalles sobre 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())));
}
}
}
Entonces, ¿qué uso del prefix
en este RedisCache? -> Como se muestra en el constructor about, se usa en esta declaración this.cacheMetadata = new RedisCacheMetadata(name, prefix);
, y el constructor de RedisCacheMetadata
continuación muestra más detalles:
/**
* @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");
}
En este punto, sabemos que algún parámetro de prefijo se ha establecido en RedisCacheMetadata
, pero cómo se usa exactamente este prefijo para formar la clave en Redis (por ejemplo, / xac / xed / x00 / x05t / x00 / tvc: 501381 como mencionó) ?
Básicamente, el CacheInterceptor
avanzará posteriormente para invocar un método private RedisCacheKey getRedisCacheKey(Object key)
del objeto RedisCache
mencionado RedisCache
, que devuelve una instancia de RedisCacheKey
utilizando el prefijo de RedisCacheMetadata
y keySerializer de RedisOperation
.
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
}
Al llegar a este punto, se completa el consejo "pre" de CacheInterceptor
y se ejecutará el método real anotado por @Cacheable
. Y después de completar la ejecución del método real, hará el consejo "post" de CacheInterceptor
, que esencialmente pone el resultado en RedisCache. A continuación se muestra el método de poner el resultado a la memoria caché de 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));
}
Dentro del objeto RedisCachePutCallback
, su método de devolución de llamada doInRedis()
realidad invoca un método para formar la clave real en redis, y el nombre del método es getKeyBytes()
desde la instancia de RedisCacheKey
. A continuación se muestran los detalles de este método:
/**
* 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;
}
Como podemos ver en el método getKeyBytes
, utiliza tanto la clave sin formato (vc: 501381 en su caso) como la clave del prefijo (/ xac / xed / x00 / x05t / x00 / t en su caso).
Tienes que serializar los objetos que lo estás enviando a redis. A continuación se muestra el ejemplo completo de ejecución de la misma. Utiliza la interfaz DomainObject
como Serializable
Debajo están los pasos
1) haz tu maven pom.xml con los siguientes tarros
<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) haga su configuración xml de la siguiente manera
<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) Haz tus clases de la siguiente manera
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;
}
}
Para obtener más referencias sobre sprinf jedis, puede ver http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html
El código de muestra está tomado de http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-example.html
Utilice StringRedisTemplate
para reemplazar RedisTemplate
.
De forma predeterminada, RedisTemplate
utiliza la serialización de Java, StringRedisTemplate
utiliza StringRedisSerializer
.
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>