柚子生活

www.pomelolive.com

Redis缓存通用对象遇到的问题

前言

最近在学习Redis缓存,需要缓存一些通用的对象,比如User,Item等等,这边我用Item举例子。以下是java bean

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item implements Serializable {
    @Id
    private long id;
    private long userId;
    private int itemId;
    private int count;
}

以下是写入Redis的代码,写入三个item

long userId = 1;
List<Item> itemList = new ArrayList<>();
Item item1 = new Item(Utils.getId(), userId, 1000, 10);
Item item2 = new Item(Utils.getId(), userId, 1001, 30);
Item item3 = new Item(Utils.getId(), userId, 1002, 1000);
itemList.add(item1);
itemList.add(item2);
itemList.add(item3);
for (Item item : itemList) {
    redisTemplate.opsForHash().put("items:" + userId, "item:" + item.getItemId(), item);
}

因为Key值都是字符串,最开始就想到了注入

@Autowired RedisTemplate<String, Object> redisTemplate

然后IDEA就马上红线报错了

Could not autowire. No beans of 'RedisTemplate<String, Object>' type found.

说是不能注入,因为没有找到RedisTemplate<String, Object>这个类型的bean对象。先不管他,也可能是误报了,运行测试,还是错了。

Parameter 1 of constructor in com.da.app.service.BagService required a bean of type 'org.springframework.data.redis.core.RedisTemplate' that could not be found.

还是不行,意思是构造函数参数1需要一个RedisTemplate类型的bean,而这个bean没有找到。看来是真的有问题了。

加入RedisTemplate<String, Object>这个bean

没有就手动加入吧

@Bean
public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);
    return redisTemplate;
}

再次运行,没有报错了,但是存入的都是乱码

Redis写入乱码.png

这个我知道,因为Redis默认用二进制序列化,修改序列化类型就OK了

修改序列化类型为 new StringRedisSerializer()

把Key和Value序列化类型改成new StringRedisSerializer(),其实这个就是StringRedisTemplate。

@Bean
public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(new StringRedisSerializer());
    redisTemplate.setConnectionFactory(factory);
    return redisTemplate;
}

又报错了

Constructor threw exception; nested exception is java.lang.ClassCastException: com.da.app.dao.entity.Item cannot be cast to java.lang.String

不能把Item对象转换为java.lang.String,这个很好理解,因为我序列化是用字符串序列化的。于是有了下一个版本。

把值序列化类型改成 new Jackson2JsonRedisSerializer<>(Object.class)

修改测试

@Bean
public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
    redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
    redisTemplate.setConnectionFactory(factory);
    return redisTemplate;
}

这次成功了

通过Object序列化写入.png

写入了自然要读取啊。于是又开始继续码代码

public List<Item> getItemListFromRedis(long userId) {
    List<Object> objectList = redisTemplate.opsForHash().values("items:" + userId);

    List<Item> itemList = new ArrayList<>();
    for (Object o : objectList) {
        itemList.add((Item)o);
    }
    return itemList;
}

读取getItemListFromRedis(1);又是一堆错误

nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.da.app.dao.entity.Item

类型不能转换为Item,我存入的是Item,为啥不能转换为Item呢?好奇怪,究其原因对象本身就不是Item类型,如果是Item类型,转换是不会报错的。继续下一个版本

把值序列化类型改成  new Jackson2JsonRedisSerializer<>(Item.class)

既然Object类型不行,那就老老实实写具体类型吧

@Bean
public RedisTemplate<String, Item> redisTemplate(@Autowired RedisConnectionFactory factory) {
    RedisTemplate<String, Item> redisTemplate = new RedisTemplate<>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Item.class));
    redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Item.class));
    redisTemplate.setConnectionFactory(factory);
    return redisTemplate;
}

注入的地方也要修改

@Autowired RedisTemplate<String, Item> redisTemplate

通过测试打印是可以读取了,以下是测试结果。

读取Object写入的Item.png

但是这样子太麻烦了,我每次添加一个类型都要写一个那个类型的bean。继续下一个版本

把值序列化类型改成 new GenericJackson2JsonRedisSerializer()

然后就继续寻找序列化类型,new GenericJackson2JsonRedisSerializer() 通过字面意思理解,是通用的Json序列化。修改bean

@Bean
public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    redisTemplate.setConnectionFactory(factory);
    return redisTemplate;
}

运行又是报错

Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'

缺少属性@class,应该是没有给一个指定的class吧。于是把Redis缓存清除注入新的RedisTemplate再生成一份数据。

通过通用Json序列化写入Redis.png

读出来的数据同样也是正确的。

这次就恍然大悟了,里面多了个@class属性。之前为什么转换不了都是因为没有@class属性。

小结

值序列化类型用 new GenericJackson2JsonRedisSerializer() 可以序列化通用的对象。原因是把@class类型也存起来了,反序列化自然也就知道转成什么类型的对象了。

发表评论:

Powered By Z-BlogPHP

© CopyRight 2019-2020 pomelolive.com, 京ICP备19052672号