SpringBoot环境下使用Redis的那点事(一)

在springboot环境中使用redis是非常简单的,如果没有特殊需求,几乎可以不用做任何配置。
在使用的时候我们既可以通过springCache提供的注解进行缓存操作,也可以通过redisTemplate或stringRedisTemplate来进行缓存操作,要使用注解就直接在方法或类上加上注解,要使用redisTemplate或stringRedisTemplate注解通过依赖注入,什么连接工厂,缓存管理器统统都不需要我们自己去配置,这就是SpringBoot的自动化配置给我们带来的方便。那么下面我们来聊聊缓存操作的两种方式。
一.当我们只使用注解进行操作的时候
当我们只使用注解进行缓存操作,也就是说不管数据是从缓存中读取出来,还是将数据存储到缓存中区,都只使用注解来操作,那么将会是下面这个场景:
这里写图片描述
上面是redisCache默认的配置,可以看到Key的默认的序列化方式为stringRedisSerializer,而value的默认的序列化方式为jdkSerializationRedisSerializer,这是两种不同的序列化方式。
采用这种默认的配置方式有利有弊。
当我们对一个方法标注了@Cacheable注解的时候,程序会先去redis数据库中查找有没有对应的key,如果有对应的key则取出key的值,然后反序列化这个值转换成方法的返回值类型,最后返还给方法调用者,也就是说被标注的方法不会被执行了。如果找不到对应的key,那么则执行方法,然后将方法的返回值序列化之后存储到redis数据中。
采用jdk序列化方式的最直接的好处是,当我们取出key对应的值后,通过jdk的反序列化可以直接转换成方法返回值的类型,方法的调用者不需要做类型转换。
但是缺点也很明显,jdk序列化的值存储在reids数据库中会是下面这样值:
这里写图片描述
说实话没有人愿意看到这样的输出结果,尽管它的确就是正确的输出。另外jdk的序列化方式要求序列化的对象必须实现Serializable接口。

整体而言,采用默认的value的序列化方式不影响我们在程序中开发和使用,至于通过redis客户端查看的时候不太友好的问题,很多时候可以忽略。

不过因为JDK序列化的存在的弊端,所以网上有很多的教程告诉我们如何去换一种value的序列化方式,从而能够让我们在redis中查看的时候能够很友好,一般来说是显示成json格式,配置的方式也很简单:
public class MyRedisSerializable implements RedisSerializer {
static final byte[] EMPTY_ARRAY = new byte[0];//设置如果空字节数组
private final Charset charset;//设置字符集
/**
* 无参构造,默认编码为Utf-8
*/
public MyRedisSerializable() {
// TODO Auto-generated constructor stub
this(Charset.forName(“UTF8”));
}
/**
* 有参构造 设置编码方式
* @param charset
*/
public MyRedisSerializable(Charset charset) {
// TODO Auto-generated constructor stub
Assert.notNull(charset);
this.charset = charset;
}
/**
*
*@methodName serialize
*@description 序列化对象
*@param t
*@return
*@throws SerializationException
*@author 吴健
*@lastUpdate 2018年9月4日
*/
@Override
public byte[] serialize(Object t) throws SerializationException {
if(t==null){
return MyRedisSerializable.EMPTY_ARRAY;
}
JSONObject object=JSONObject.fromObject(t);
String result=object.toString();
return (result==null?MyRedisSerializable.EMPTY_ARRAY:result.getBytes(charset));
}
/**
*
*@methodName deserialize
*@description 反序列化
*@param bytes
*@return
*@throws SerializationException
*@author 吴健
*@lastUpdate 2018年9月4日
*/
@SuppressWarnings(“unchecked”)
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if(bytes==null){
return null;
}
String result=new String(bytes,charset);
JSONObject object=JSONObject.fromObject(result);
Object o=(Object) object;
return o;
}

}
其实就是定义一个类,让它实现RedisSerializer接口,然后实现接口的两个方法,那两个方法分别对应着序列化和反序列化操作。原理也很简单,序列化的时候就是将对象转换成json格式字符串,然后将json格式字符串转换成字节数组,反序列化则是将字节数组转换成json对象。
然后在配置类中配置一个RedisCacheConfiguration的bean:
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new MyRedisSerializable()));
return configuration;
}
在这个bean中,我们将value的默认序列化方式改为我们自己写的序列化方式。
配置好之后,当我们使用注解来操作缓存的时候,存储的数据的value将通过我们自己写的序列化类进行序列化和发序列化处理。这个时候我们通过注解来存储一条数据:
@Override
@Cacheable(value = “redisDemo0”, key = “#key1”)
public User redisTest0(String key1) {
User u = new User();
u.setUsername(“testRedis”);
u.setPassword(“testRedisPass”);
return u;
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class DaSWebApplicationTests {
@Autowired
private SysService sysService;

@Test
public void contextLoads() {
    this.sysService.redisTest0("testKey");

}

}
执行完成后,查看一下redis数据库:
这里写图片描述
这里写图片描述
我们通过客户端能够很清楚的看到存储的key对应的value的值,而不需要通过程序来输出查看了,看上去这种序列化方式似乎非常美好,比什么JDK序列化方式要好多了,但是等等,真的如此吗?我们来看看下面的几个问题:
在上面的例子中,我们存储了一个key为redisDemo0::testKey,value为一个User类型的对象,接下来我们再次执行this.sysService.redisTest0(“testKey”),因为redisTest0(String key1)标注了@Cacheable这个注解,所以它会先去redis数据库查找有没有一个key叫redisDemo0::testKey的数据,很显然我们第一次执行这个方法的时候已经缓存过这对key-value数据了,所以找到这个key之后,会将其对应的value取出来然后进行反序列化操作,因为之前我们已经将value的序列化方式换成了我们自己定义的序列化方式,所以这里的反序列化操作,实际上执行的使我们自定义的序列化类的反序列化方法,也就是下面这段代码:
@SuppressWarnings(“unchecked”)
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if(bytes==null){
return null;
}
String result=new String(bytes,charset);
JSONObject object=JSONObject.fromObject(result);
Object o=(Object) object;
return o;
}
它会将从redis数据库中取出来的字节数组转换成string,然后再将String转换成一个JSONObject,最后通过强制转换,转换成Object类型返回,返回后的值回被再次转换成被标注的方法的返回值类型,然后返还给方法的调用者,好像是没有什么问题,但是我们来看一下实际运行的结果:
这里写图片描述
这个错非常简单,一个JSONObject类型的对象不能转换成一个User类型对象,注意这里的转换指的是强制类型转换,而不是通过JSONObject的toBean()方法来转换。
因为我们反序列化方法返回的是一个jsonobject,所以也就意味着被标注的方法的返回值必须也是jsonobject,要不然从缓存数据库中取出来的值转换后,无法转换成被标注的方法的返回值,也就无法返还给方法的调用者,但是如果把所有@Cacheable标注的方法的返回值都改成jsonobject类型那也是不可能的,因为当被标注的方法第一次执行的时候,缓存数据库里并没有对应的key-value,所以方法会正常执行,如果返回类型必须是jsonobject的话,那么也就意味着不管什么被标注的方法,都得在方法执行完前,将返回的对象转换成jsonobject对象再返回,这明显是不可能被操作的,所以我们在自定会的反序列化方法里,将jsonobject强制转换成了object类型,这样我们被标注的方法的返回值可以设为object,如此一来,我们原先的方法该怎么写还怎么写,因为不管你返回什么类型的对象,它都是Object。

所以看完上面这一大段之后,我们会发现用了自定义的序列化方式之后,虽然被序列化的对象不用实现Serializable接口了,存储到数据库之后查看起来也更加直观友好了,但是对于我们程序的影响还是挺大的,毕竟只要是被标注的方法,方法的返回类型都得是Object就很别扭。

但你以为用了自定义序列化方式后,问题就这么多吗,我们再看看下面这种例子:
@Override
@Cacheable(value = “redisDemo1”, key = “#key1”)
public ArrayList redisTest1(String key1) {
User u = new User();
u.setUsername(“testRedis”);
u.setPassword(“testRedisPass”);
User u1 = new User();
u1.setUsername(“testRedis”);
u1.setPassword(“testRedisPass”);
ArrayList users=new ArrayList();
users.add(u);
users.add(u1);
return users;
}

这一次我们缓存的不是一个普通的POJO了,而是一个集合,我们执行一下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DaSWebApplicationTests {
@Autowired
private SysService sysService;

@Test
public void contextLoads() {
    this.sysService.redisTest1("testKey");

}

}
这里写图片描述
我们会发现又报错了,这次的错误也很明显我们要序列化的对象是一个数组类型的数据,所以不能直接转换成jsonObject对象,而应该使用jsosnArray来代替。
所以我们还要修改我们自定义的序列化方法,在执行序列化方法的时候,先判断一下传进来的对象是不是一个数组类型的数据,如果是则将它转换成jsonArray对象,然后再转换成字符串,最后转换成字节数组进行存储。而执行反序列化方法的时候,先将字节数组转换成一个字符串,然后判断该字符串是jsonObject格式,还是jsonArray格式的,根据判断的结果选择转换的类型:
public class MyRedisSerializable implements RedisSerializer {
static final byte[] EMPTY_ARRAY = new byte[0];//设置如果空字节数组
private final Charset charset;//设置字符集
/**
* 无参构造,默认编码为Utf-8
*/
public MyRedisSerializable() {
// TODO Auto-generated constructor stub
this(Charset.forName(“UTF8”));
}
/**
* 有参构造 设置编码方式
* @param charset
*/
public MyRedisSerializable(Charset charset) {
// TODO Auto-generated constructor stub
Assert.notNull(charset);
this.charset = charset;
}
/**
*
*@methodName serialize
*@description 序列化对象
*@param t
*@return
*@throws SerializationException
*@author 吴健
*@lastUpdate 2018年9月4日
*/
@Override
public byte[] serialize(Object t) throws SerializationException {
if(t==null){
return MyRedisSerializable.EMPTY_ARRAY;
}
if(t.toString().startsWith(“[“)){
JSONArray arry=JSONArray.fromObject(t);
String result=arry.toString();
return (result==null?MyRedisSerializable.EMPTY_ARRAY:result.getBytes(charset));
}
JSONObject object=JSONObject.fromObject(t);
String result=object.toString();
return (result==null?MyRedisSerializable.EMPTY_ARRAY:result.getBytes(charset));
}
/**
*
*@methodName deserialize
*@description 反序列化
*@param bytes
*@return
*@throws SerializationException
*@author 吴健
*@lastUpdate 2018年9月4日
*/
@SuppressWarnings(“unchecked”)
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if(bytes==null){
return null;
}
String result=new String(bytes,charset);
if(result.startsWith(“[“)){
JSONArray object=JSONArray.fromObject(result);
Object o=(Object)object;
return o;
}
JSONObject object=JSONObject.fromObject(result);
Object o=(Object) object;
return o;
}

}
修改完成之后,我们再次执行this.sysService.redisTest1(“testKey”);:
这里写图片描述
会发现数据已经正确存储读取了,好了到此为止,我们标注的方法即可以返回一个对象,也可以返回一个集合,应该没有问题了吧?我们再写个例子看看:
@Override
@Cacheable(value = “redisDemo2”, key = “#key1”)
public String redisTest2(String key1) {
// TODO Auto-generated method stub
return “helloWorld”;
}
这一次我们想要缓存的数据是一个普通的字符串,然后执行该方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DaSWebApplicationTests {
@Autowired
private SysService sysService;

@Test
public void contextLoads() {
    this.sysService.redisTest2("testKey");

}

}
这里写图片描述
糟糕又报错了,这次是说一个jsonObject的文本必须以{开头,换句话说一个字符串要想被转换成jsonObject,这个字符串必须符合json文本的格式要求,也就是{key1:value1,ke2:value2,…}的形式,所以当我们用了自定义的序列化方式,通过json字符串与字节数组相互转换的方式来进行序列化和反序列化操作的时候,待序列化的对象必须能转换成jsonObject或jsonArray,一个普通的字符串,或者其他不符合json格式的数据类型都无法进行缓存。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值