为啥在将 Jedis 与 Spring Data 结合使用时,数据会以奇怪的键存储在 Redis 中?
Posted
技术标签:
【中文标题】为啥在将 Jedis 与 Spring Data 结合使用时,数据会以奇怪的键存储在 Redis 中?【英文标题】:Why is data getting stored with weird keys in Redis when using Jedis with Spring Data?为什么在将 Jedis 与 Spring Data 结合使用时,数据会以奇怪的键存储在 Redis 中? 【发布时间】:2012-10-24 06:43:50 【问题描述】:我正在将 Spring Data Redis 与 Jedis 一起使用。我正在尝试使用密钥vc:$list_id
存储散列。我能够成功插入到redis。但是,当我使用 redis-cli 检查密钥时,我看不到密钥 vc:501381
。相反,我看到了\xac\xed\x00\x05t\x00\tvc:501381
。
为什么会发生这种情况,我该如何改变?
【问题讨论】:
【参考方案1】:好的,在 Google 上搜索了一段时间,并在 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中的key是vc:501381
。
或者像@niconic 说的那样,我们也可以将默认序列化器本身设置为字符串序列化器,如下所示:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:defaultSerializer-ref="stringRedisSerializer"
/>
这意味着我们所有的键和值都是字符串。但是请注意,这可能并不可取,因为您可能希望您的值不仅仅是字符串。
如果您的值是域对象,那么您可以使用 Jackson 序列化器并配置序列化器,如 here 所述,即:
<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"
/>
【讨论】:
如果更改所有序列化程序引用,直接更改默认序列化程序可能很有用。 请注意,如果将convertAndSend
与redisTemplate
一起使用,则p:keySerializer
和hashKeySerializer
是不够的。更改 default serializer
完成了这项工作
谢谢!因为我使用了spring boot,我通过参考这个文档解决了这个问题:***.com/questions/34201135/…【参考方案2】:
我知道这个问题已经有一段时间了,但是我最近又对这个话题做了一些研究,所以我想在这里分享一下这个“半散列”密钥是如何通过部分 spring 源代码生成的。
首先,Spring利用AOP来解析@Cacheable, @CacheEvict or @CachePut
等注解。通知类是来自Spring-context依赖的CacheInterceptor
,它是CacheAspectSupport
的子类(也来自Spring-context)。为了便于解释,我将使用@Cacheable
作为示例,在这里查看部分源代码。
当注解为@Cacheable
的方法被调用时,AOP 会将其从CacheAspectSupport
类路由到此方法protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver)
,并在其中尝试解析此@Cacheable
注解。反过来,它会导致在实现 CacheManager 中调用此方法public Cache getCache(String name)
。对于这个解释,实现 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、prefix(这是回答这个问题的关键参数)、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
随后会向前调用上述RedisCache
对象的private RedisCacheKey getRedisCacheKey(Object key)
方法,该方法通过利用前缀 中的前缀 返回RedisCacheKey
的实例RedisCacheMetadata
和来自 RedisOperation
的 keySerializer。
private RedisCacheKey getRedisCacheKey(Object key)
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
到此为止,CacheInterceptor
的“pre”advice 就完成了,它会继续执行@Cacheable
注释的实际方法。并且在完成实际方法的执行后,它会做CacheInterceptor
的“post”advice,本质上是把结果放到RedisCache。下面是将结果放入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));
在RedisCachePutCallback
对象内部,它的回调方法doInRedis()
实际上调用了一个方法来形成redis中的实际key,方法名是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)。
【讨论】:
解释得很好。【参考方案3】:这是一个非常古老的问题,但我的回答可能对在使用 Redis using Spring Boot 时遇到同样问题的人有所帮助。在 redis 中存储哈希类型数据时,我遇到了同样的问题。我已经为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;
如果数据类型是字符串,则不需要template.setHashValueSerializer(new StringRedisSerializer());
和template.setHashKeySerializer(new StringRedisSerializer());
。
【讨论】:
是的,它会起作用,但这会产生与开始使用 StringRedisTemplate 完全相同的行为,除了它涉及冗余配置。【参考方案4】:使用StringRedisTemplate
替换RedisTemplate
。
默认情况下,RedisTemplate
使用 Java 序列化,StringRedisTemplate
使用 StringRedisSerializer
。
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
【讨论】:
这是准确的答案。 RedisTemplate 使用 Java 序列化,因此确保您的类是用“implements java.io.Serializable”编写的,这就足够了。 这仅适用于您的 值 也是字符串的情况,因为StringRedisTemplate
仅支持字符串值。【参考方案5】:
你必须序列化你发送到 redis 的对象。下面是它的完整运行示例。它使用接口DomainObject
作为Serializable
以下是步骤
1) 使用以下 jars 制作您的 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;
有关 srinf 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
【讨论】:
以上是关于为啥在将 Jedis 与 Spring Data 结合使用时,数据会以奇怪的键存储在 Redis 中?的主要内容,如果未能解决你的问题,请参考以下文章
Spring data redis-StringRedisTemplate 用法
Spring集成Jedis(不依赖spring-data-redis)(单机/集群模式)(待实践)