Tôi đang sử dụng Redis Data Redis with Jedis. Tôi đang cố gắng lưu trữ một băm với khóa vc:${list_id}
. Tôi đã có thể chèn thành công vào redis. Tuy nhiên khi tôi kiểm tra các phím thông qua redis-cli, tôi không thấy khóa vc:501381
. Thay vào đó, tôi thấy \xac\xed\x00\x05t\x00\tvc:501381
. Tại sao điều này xảy ra và làm cách nào để thay đổi điều này?Phím redis lạ với dữ liệu mùa xuân Jedis
Trả lời
Ok, googled xung quanh một lúc và tìm thấy trợ giúp tại http://java.dzone.com/articles/spring-data-redis.
Điều đó xảy ra do tuần tự hóa Java.
Các serializer then chốt cho redisTemplate cần phải được cấu hình để StringRedisSerializer
ví dụ như thế này:
<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"
/>
Bây giờ chìa khóa trong redis là vc:501381
.
Hoặc như @niconic nói, chúng ta cũng có thể thiết lập các serializer mặc định riêng của mình để serializer chuỗi như sau:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:defaultSerializer-ref="stringRedisSerializer"
/>
có nghĩa là tất cả các khóa và giá trị của chúng tôi là chuỗi. Tuy nhiên, lưu ý rằng điều này có thể không thích hợp hơn, vì bạn có thể muốn các giá trị của bạn không chỉ là các chuỗi.
Nếu giá trị của mình là một đối tượng tên miền, sau đó bạn có thể sử dụng Jackson serializer và cấu hình một serializer như đã đề cập here tức là như thế này:
<bean id="userJsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
<constructor-arg type="java.lang.Class" value="com.mycompany.redis.domain.User"/>
</bean>
và cấu hình mẫu của bạn như:
<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"
/>
Bạn phải serialize các đối tượng mà bạn đang gửi nó đến redis. Dưới đây là ví dụ chạy hoàn chỉnh của nó. Nó sử dụng giao diện DomainObject
như Serializable
Dưới đây là các bước
1) làm pom.xml maven bạn với lọ sau
<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) làm xml cấu hình của bạn như sau
<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) Tạo các lớp học của bạn như sau
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;
}
}
Để tham khảo thêm về jedis sprinf bạn có thể thấy http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html
Mẫu Mã được lấy từ http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-example.html
Sử dụng StringRedisTemplate
để thay thế RedisTemplate
.
Theo mặc định, RedisTemplate
sử dụng tuần tự hóa Java, StringRedisTemplate
sử dụng StringRedisSerializer
.
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
Đây là câu trả lời chính xác. RedisTemplate sử dụng tuần tự hóa Java do đó đảm bảo lớp học của bạn được viết bằng "triển khai thực hiện java.io.Serializable", điều đó là đủ. –
Tôi biết câu hỏi này đã được một thời gian, nhưng tôi đã làm một số nghiên cứu về chủ đề này một lần nữa thời gian gần đây, vì vậy tôi muốn chia sẻ cách này "bán băm" chìa khóa được tạo ra bằng cách đi qua một phần của mùa xuân mã nguồn ở đây.
Trước hết, Spring tận dụng AOP để giải quyết chú thích như @Cacheable, @CacheEvict or @CachePut
vv Lớp tư vấn là CacheInterceptor
từ phụ thuộc ngữ cảnh mùa xuân, là phân lớp của CacheAspectSupport
(cũng từ ngữ cảnh mùa xuân). Để dễ giải thích này, tôi sẽ sử dụng @Cacheable
làm ví dụ để chuyển qua một phần mã nguồn ở đây.
Khi phương pháp chú thích như @Cacheable
được gọi, AOP sẽ tuyến đường nó để phương pháp này protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver)
từ CacheAspectSupport
lớp, trong đó nó sẽ cố gắng giải quyết @Cacheable
chú thích này. Đổi lại, nó dẫn đến việc triệu gọi phương thức này public Cache getCache(String name)
trong CacheManager thực hiện. Đối với giải thích này, việc thực hiện CacheManage sẽ là RedisCacheManager
(từ Spring-data-redis dependency).
Nếu bộ nhớ cache không được nhấn, nó sẽ đi trước để tạo bộ nhớ cache. Dưới đây là những phương pháp quan trọng từ 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);
}
Về cơ bản, nó sẽ tạo một đối tượng RedisCache
. Để làm điều này, nó yêu cầu 4 tham số, cụ thể là, cacheName, prefix (đây là tham số chính liên quan đến trả lời câu hỏi này), redisOperation (aka, redisTemplate), hết hạn (mặc định là 0) và cacheNullValues (mặc định là sai). Hàm khởi tạo bên dưới hiển thị thêm chi tiết về 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())));
}
}
}
Vì vậy, việc sử dụng prefix
trong RedisCache này là gì? -> Như đã trình bày trong các nhà xây dựng về, nó được sử dụng trong bản Tuyên Bố này this.cacheMetadata = new RedisCacheMetadata(name, prefix);
, và các nhà xây dựng của RedisCacheMetadata
dưới đây cho thấy thêm chi tiết:
/**
* @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");
}
Tại thời điểm này, chúng ta biết rằng một số tham số tiền tố đã được thiết lập để RedisCacheMetadata
, nhưng tiền tố này được sử dụng như thế nào để tạo khóa trong Redis (ví dụ: \ xac \ xed \ x00 \ x05t \ x00 \ tvc: 501381 như bạn đã đề cập)?
Về cơ bản, CacheInterceptor
sẽ sau đó di chuyển về phía trước để gọi một phương pháp private RedisCacheKey getRedisCacheKey(Object key)
từ các đối tượng nêu trên RedisCache
, mà trả về một thể hiện của RedisCacheKey
bằng cách sử dụng tiền tố từ RedisCacheMetadata
và keySerializer từ RedisOperation
.
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
}
Bằng đạt thời điểm này, "trước" lời khuyên của CacheInterceptor
được hoàn thành, và nó sẽ đi trước để thực hiện các phương pháp thực tế chú thích bởi @Cacheable
. Và sau khi hoàn thành việc thực hiện phương pháp thực tế, nó sẽ thực hiện lời khuyên "post" của CacheInterceptor
, về cơ bản đặt kết quả cho RedisCache. Dưới đây là phương pháp đưa kết quả đến 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));
}
Trong đối tượng RedisCachePutCallback
, callback method của nó doInRedis()
thực sự gọi một phương pháp để tạo phím thực tế trong redis, và tên phương pháp là getKeyBytes()
từ RedisCacheKey
dụ.Dưới đây cho thấy các chi tiết của phương pháp này:
/**
* 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;
}
Như chúng ta có thể thấy trong các phương pháp getKeyBytes
, nó sử dụng cả hai phím thô (vc: 501.381 trong trường hợp của bạn) và chìa khóa tiền tố (\ xac \ cố định \ x00 \ x05t \ x00 \ t trong trường hợp của bạn).
Nếu thay đổi tất cả các bộ nối tiếp tham chiếu, có thể hữu ích khi thay đổi trực tiếp bộ nối tiếp mặc định. – reallynice
lưu ý rằng nếu một người đang sử dụng 'convertAndSend' với' redisTemplate' thì 'p: keySerializer' và' hashKeySerializer' là không đủ. thay đổi 'serializer mặc định' đã thực hiện công việc – oak
Cảm ơn! Bởi vì tôi đã sử dụng khởi động mùa xuân và tôi đã giải quyết nó bằng cách tham khảo tài liệu này: http://stackoverflow.com/questions/34201135/spring-redis-read-configuration-from-application-properties-file – zhuguowei