Spring Data RedisとJedisを使用しています。キーvc:${list_id}
でハッシュを保存しようとしています。 redisに正常に挿入できました。ただし、redis-cliを使用してキーを検査すると、キーvc:501381
が表示されません。代わりに、\xac\xed\x00\x05t\x00\tvc:501381
が表示されます。
なぜこれが起こっているのですか?これをどのように変更しますか?
OK、しばらくグーグルで探して、 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シリアライザーを使用して、前述のようにシリアライザーを構成できます 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"
/>
私はこの質問がしばらく前であることを知っていますが、最近このトピックに関するいくつかの研究を行ったので、ここで春のソースコードの一部を通過することによってこの「半ハッシュ」キーがどのように生成されるかを共有したいと思います。
まず、SpringはAOPを利用して@Cacheable, @CacheEvict or @CachePut
などの注釈を解決します。アドバイスクラスは、Springコンテキスト依存関係のCacheInterceptor
です。これはCacheAspectSupport
のサブクラスです(Springコンテキストからも)。この説明を簡単にするために、ここでソースコードの一部を説明する例として@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
オブジェクトをインスタンス化します。これを行うには、cacheName、prefix(この質問への回答に関する重要なパラメーター)、redisOperation(別名、構成されたredisTemplate)、有効期限の4つのパラメーターが必要です。 (デフォルトは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())));
}
}
}
それでは、このRedisCacheでprefix
をどのように使用しますか? ->コンストラクターで示されているように、このステートメント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
からprefixを使用してRedisCacheMetadata
のインスタンスを返します。 RedisOperation
のkeySerializer。
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
}
このポイントに達すると、CacheInterceptor
の「事前」アドバイスが完了し、@Cacheable
で注釈された実際のメソッドを実行します。そして、実際のメソッドの実行を完了した後、CacheInterceptor
の「投稿」アドバイスを行います。これは、本質的に結果を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で形成し、メソッド名はRedisCacheKey
インスタンスからのgetKeyBytes()
です。以下に、このメソッドの詳細を示します。
/**
* 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)の両方を利用します。
StringRedisTemplate
を置き換えるには、RedisTemplate
を使用します。
デフォルトでは、RedisTemplate
はJavaシリアル化を使用し、StringRedisTemplate
はStringRedisSerializer
を使用します。
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
これは非常に古い質問ですが、私の答えは 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;
}
}
_
データ型がStringの場合、template.setHashValueSerializer(new StringRedisSerializer());
およびtemplate.setHashKeySerializer(new StringRedisSerializer());
は不要です。
Redisに送信するオブジェクトをシリアル化する必要があります。以下はその完全な実行例です。 DomainObject
としてインターフェイスSerializable
を使用します
以下は手順です
1)次のjarを使用して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 から取得されます