【SpringBoot实战专题】「开发实战系列」从零开始教你舒服的使用RedisTemplate操作Redis数据

1,384 阅读7分钟

SpringBoot快速操作Redis数据

在SpringBoot框架中提供了spring-boot-starter-data-redis的依赖组件进行操作Redis服务,当引入了该组件之后,只需要配置Redis的配置即可进行链接Redis服务并且进行操作Redis服务数据。

针对于不同的版本有了不同的底层客户端的支持的底层客户端框架是不同的:目前常见的客户端为Jedis和Lettuce。

低版本SpringBoot支持的Jedis

Jedis是很常用的Redis的Java 实现的客户端。支持基本的数据类型如:String、Hash、List、Set、Sorted Set。

特点:使用阻塞的 I/O,方法调用同步,程序流需要等到 socket 处理完 I/O 才能执行,不支持异步操作。Jedis 客户端实例不是线程安全的,需要通过连接池来使用 Jedis。

高版本版本SpringBoot支持的Lettuce

Lettuce客户端主要用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。Lettuce 的 API 是线程安全的,所以可以操作单个 Lettuce 连接来完成各种操作。

spring-data-redis针对jedis提供了如下功能:

Spring Boot 的 spring-boot-starter-data-redis 为 Redis 的相关操作提供了一个高度封装的 RedisTemplate 类,而且对每种类型的数据结构都进行了归类,实现连接池自动管理,提供了一个高度封装的“RedisTemplate”类。 针对jedis/Lettuce客户端中大量api进行了归类封装,将同一类型操作封装为operation接口。

通用的接口类型工厂方法

提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations:

  • ValueOperations - BoundValueOperations:String类型的简单K-V操作
  • SetOperations - BoundSetOperations:set类型数据操作
  • ZSetOperations - BoundListOperations:zset类型数据操作
  • HashOperations - BoundSetOperations:针对map类型的数据操作
  • ListOperations - BoundHashOperations:针对list类型的数据操作

序列化/反序列化的扩展机制

针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer)

JdkSerializationRedisSerializer

POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。

StringRedisSerializer

Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。

JacksonJsonRedisSerializer

jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】

Jackson2JsonRedisSerializer

使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。

OxmSerializer

提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】

扩展第三方序列化工具

当然了除了以上这几种基本的序列化器之外您还可以进行自定义一些更加优秀、速度更块的序列化方式,例如:FastJsonRedisSerializer和KryoRedisSerializer、FSTRedisSerializer等。

RedisSerializer接口

RedisSerializer 基础接口定义了将对象转换为字节数组(二进制数据)的序列化和反序列化方法。建议将实现设计为在序列化和反序列化端处理空对象/空字节数组。注意,Redis 不接受空键或空值,但可以返回 null(对于不存在的键)。

RedisSerializer 接口方法定义

序列化

序列化方法定义如下:

byte[] serialize(T t)

该方法将给定对象 t 序列化为二进制数据,及字节数组。注意:对象 t 和返回值可以为 null。

反序列化

反序列化方法定义如下:

T deserialize(byte[] bytes)

该方法将从给定的二进制数据(字节数组)反序列化为一个对象。注意:bytes 字节数组和返回值 T 均可以为 null。

注意:如果上面的 serialize() 和 deserialize() 方法在执行时报错,将抛出org.springframework.data.redis.serializer.SerializationException 异常。

引入spring-boot-starter-data-redis组件

springboot 与redis的整合,pom文件,依赖如下:

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置对应的application.properties文件

针对于配置我们按照jedis的配置为基础案例,如下所示。

# Redis数据库索引(默认为0)
spring.redis.database=0  
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8  
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1  
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8  
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0  
# 连接超时时间(毫秒)
spring.redis.timeout=0 

对应的SpringBoot-Redis的核心配置类

此处需要定义RedisTemplate对象的配置类,其中需要配置对应的RedisConnectionFactory对象类以及对应类型的序列化和反序列化组件起。如下所示

定义对应的redisTemplate对象类

默认是JDK的序列化策略,这里配置redisTemplate采用的是Jackson2JsonRedisSerializer的序列化策略,参数为redisConnectionFactory。

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 配置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        //redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        // 值采用json序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

定义对应的StringRedisTemplate对象类

但是对于 string 类型的数据,Spring Boot 还专门提供了 StringRedisTemplate 类,而且官方也建议使用该类来操作 String 类型的数据。stringRedisTemplate默认采用的是String的序列化策略。

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        return stringRedisTemplate;
    }
StringRedisTemplate和 RedisTemplate 又有啥区别呢?
  • RedisTemplate 是一个泛型类,而 StringRedisTemplate 不是,后者只能对键和值都为 String 类型的数据进行操作,而前者则可以操作任何类型。

  • 两者的数据是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate 中 的数据。

定义组合序列化方式

key采用String序列化,value使用jackson序列化,如下代码所示。

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

定义RedisTemplate的脚手架

将形成一个快速操作数据的工具类,将各种类型的操作类进行封装处理控制。

  • HashOperations:对hash类型的数据操作
  • ValueOperations:对redis字符串类型数据操作
  • ListOperations:对链表类型的数据操作
  • SetOperations:对无序集合类型的数据操作
  • ZSetOperations:对有序集合类型的数据操作

将以上各种类型的类直接进行暴漏,减少调用链路的路径长度。

  /**
     * 对hash类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }

定义基础层的操作处理定义RedisSupport

除了以上这几种类型的操作之外,还有一些基础相关的核心操作类,包含重命名,转移以及情况整个库的操作、设置TTL生命周期等。

@Component
public class RedisSupport {

	@Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 默认过期时长,单位:秒
     */
    public static final long DEFAULT_EXPIRE = 60 * 60 * 24;

    /**
     * 不设置过期时长
     */
    public static final long NOT_EXPIRE = -1;

    public boolean existsKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 重名名key,如果newKey已经存在,则newKey的原值被覆盖
     *
     * @param oldKey
     * @param newKey
     */
    public void renameKey(String oldKey, String newKey) {
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * newKey不存在时才重命名
     *
     * @param oldKey
     * @param newKey
     * @return 修改成功返回true
     */
    public boolean renameKeyNotExist(String oldKey, String newKey) {
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 删除key
     *
     * @param key
     */
    public void deleteKey(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 删除多个key
     *
     * @param keys
     */
    public void deleteKey(String... keys) {
        Set<String> kSet = Stream.of(keys).map(k -> k).collect(Collectors.toSet());
        redisTemplate.delete(kSet);
    }

    /**
     * 删除Key的集合
     *
     * @param keys
     */
    public void deleteKey(Collection<String> keys) {
        Set<String> kSet = keys.stream().map(k -> k).collect(Collectors.toSet());
        redisTemplate.delete(kSet);
    }

    /**
     * 设置key的生命周期
     *
     * @param key
     * @param time
     * @param timeUnit
     */
    public void expireKey(String key, long time, TimeUnit timeUnit) {
        redisTemplate.expire(key, time, timeUnit);
    }

    /**
     * 指定key在指定的日期过期
     *
     * @param key
     * @param date
     */
    public void expireKeyAt(String key, Date date) {
        redisTemplate.expireAt(key, date);
    }

    /**
     * 查询key的生命周期
     *
     * @param key
     * @param timeUnit
     * @return
     */
    public long getKeyExpire(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 将key设置为永久有效
     *
     * @param key
     */
    public void persistKey(String key) {
        redisTemplate.persist(key);
    }
}

至此整体对应的RedisTemplate对象的封装和扩展就到这里,可以把代码介入到你的项目里面,非常方便的进行操作Redis了,是不是很OK呢?