在Springboot中提供了spring-data-redis操作Redis很方便,底层它提供了Jedis和Lettuce两种方式来访问Redis,通过Springboot配置就可以完成Jedis和Lettuce的集成,使得集成非常简单。同时它也提供了统一的访问类RedisTemplate来访问Redis,使得访问Redis接口统一,代码也更优雅。同时它也提供了序列化数据的方式,使得读取和存储数据更加简单,更加高效。但在使用SpringDataRedis时,也需要注意一些问题,特别是序列化引起的兼容性问题,需要多加注意。这篇文章将从各个方面详细的讲解在SpringBoot中使用Redis的注意事项。
简单的集成方式
SpringDataRedis可以使用配置文件的方式来配置使用哪种连接方式,关于这两种访问方式的差别,主要体现在多线程安全上:
Redis不是线程安全,在多个线程间共享一个 Jedis 实例时是线程不安全的,因为它们共享一个socket变量,导致线程不安全。因此,推荐使用的方式是使用Jedis pool来线程安全的操作Redis。
Lettuce是线程安全的,它是基于Netty的,性能比较好。多线程使用同一连接实例时,也是线程安全的。
Springboot使用Redistemplate操作Redis,一旦配置后,RedisTemplate是线程安全的,能在多线程间重复利用,因为内部使用的也是使用连接池的方式。
Jedis的配置方式如下:
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=your password
spring.redis.timeout=2000
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=3
spring.redis.database=0
Lettuce的配置方式如下:
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=your password
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.max-wait=3
spring.redis.database=0
统一的接口访问方式
在Springboot中可以使用Jedis或者Lettuce的API来访问Redis,但这种原生方式比较麻烦,而RedisTemplate是SpringDataRedis中对Jedis或Lettuce API的高度封装,SpringDataRedis相对于原生API来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache。使用Redistemplate只需要在POM中增加下面的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
关于Redistemplate的API相比Jedis的API更加丰富,主要有以下两类。针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作ListOperations:针对list类型的数据操作
提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations:
BoundValueOperations
BoundSetOperations
BoundListOperations
BoundSetOperationsBoundHashOperations
下面是一个创建Redistemplate的Bean实例。
@Bean
@Primary
public RedisTemplate<String, Object> redisJacksonSerializerTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new JdkSerializationRedisSerializer());
template.setHashValueSerializer(new JdkSerializationRedisSerializer());
return template; }丰富的序列化方式
RedisTemplate提供了丰富的数据序列化和反序列化,使得操作更加简单,也更加优雅。Redis提供的接口比较单一,是byte[]和String,因此在使用这些API时,只能获取到数据后再在代码中增加反序列化操作,代码比较繁琐,而RedisTemplate除了字符串,byte数组,还提供了对象序列化,因此代码更加简洁。RedisTemplate为大多数操作提供了一个基于Java的序列化转换器。这意味着使用RedisTemplate写或读的任何对象都将会通过Java进行序列化/反序列化。RedisTemplate的序列化机制可以轻松的改变,Redis模块提供了多个可用的序列化实现,位于org.springframework.data.redis.serializer目录下。你也可以不设置序列化转换器,而使用原生的字节数组,同时需要设置enableDefaultSerializer为false。
注意事项:如果使用你的数据需要被第三方工具解析,那么数据应该使用StringRedisSerializer而不是其他序列化方式。因为第三方工具无法简单的读取RedisTemplate序列化后的对象,后面有详细的演示每种序列化后的结果。
JdkSerializationRedisSerializer
适合于POJO对象的存取场景,POJO对象必须实现Serializable接口,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。序列化的结果是以xAC\xED\x00\x05t\x00开头,后面再接一个字符串长度(十六进制)+ key。但对于这种序列化后的数据,通过jedis很难读取到,只能使用scan方式或者硬编码前缀方式来读取。序列化后的截图如下:
序列化设置如下:
@Bean
public RedisTemplate<String, Object> redisJacksonSerializerTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setHashKeySerializer(new JdkSerializationRedisSerializer());
template.setHashValueSerializer(new JdkSerializationRedisSerializer());
return template;
}
StringRedisSerializer
Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装,是最轻量级和高效的策略。当要存取的数据就是字符串类型数据的时候,那么使用StringRedisSerializer。如果是对象需要存储成json字符串,需要首先转换成字符串存储,或者使用JacksonJsonRedisSerializer,序列化后的结果如下图:当用第三方工具不是使用RedisTemplate来读取序列化后的数据时,比如jedis api来读取,此时最好使用StringRedisSerializer来序列化数据,因为Jedis只提供了byte[]和String两种读取方式,也没有提供自动反序列化设置,只能通过读取后的数据,自行反序列化。
JacksonJsonRedisSerializer
jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。指定enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)的话,存储到redis里的数据将是有类型的json数据,例如:
["java.util.ArrayList",[{"@class":"com.example.dto","id":12,"firstName":"tom","lastName":"zhang","type":2}]]
@Bean
@Primary
public RedisTemplate<String, Object> redisJacksonSerializerTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
在activateDefaultTyping中需要设置DefaultTyping类型,DefaultTyping定义如下:
public static enum DefaultTyping {
JAVA_LANG_OBJECT,
OBJECT_AND_NON_CONCRETE,
NON_CONCRETE_AND_ARRAYS,
NON_FINAL,
EVERYTHING;
private DefaultTyping() {
}
}
JAVA_LANG_OBJECT: 当对象属性类型为Object时生效;
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private int age;
private String name;
private Object object;
}
class l1nk3r {
public int length = 100;
}
序列化后的结果如下:
{"age":40,"name":"tom","object":["com.example.dto.l1nk3r",{"length":"100"}]}
OBJECT_AND_NON_CONCRETE: 当对象属性类型为Object或者非具体类型(抽象类和接口)时生效;
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private int age;
private String name;
private Object object;
private Address address
}
class l1nk3r {
public int length = 100;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Address{
private no;}序列化后的结果如下:
{"age":40,"name":"tom","object":["com.example.dto.l1nk3r",{"length":"100"}],"address":["com.example.dto.Address",{"no":"No.1"}]}
NON_CONCRETE_AND_ARRAYS: 同上, 另外所有的数组元素的类型都是非具体类型或者对象类型;
{"age":40,"name":"tom","object":["com.example.dto.l1nk3r",[{"length":100},{"length":200}]],"address":["com.example.dto.Address",{"no":"No.2}]}
NON_FINAL: 包括上文提到的所有特征,而且包含即将被序列化的类里的全部、非final的属性,也就是相当于整个类、除final外的的属性信息都需要被序列化和反序列化。
["com.example.dto.User",{"age":40,"name":"tom","object":["com.example.dto.l1nk3r",[{"length":100},{"length":200}]],"address":["com.example.dto.Address",{"no":"No.2}]}]