Redis 19 SpringBoot集成

概述

SpringBoot 整合 Redis 是使用 SpringData 实现的。

SpringData 是与 SpringBoot 齐名的顶级项目,整合了对常用数据库的模板型操作。

在 SpringBoot 2.x 之后,Jedis 被 Lettuce 替代了。

Jedis

采用的直连,多个线程操作的话,是不安全的。

如果想要避免不安全,就要使用 Jedis pool 连接池解决。

这样是有一些弊端的,比如线程数量太多了,Redis 服务就比较庞大,而且它是阻塞的。

Lettuce

底层采用 Netty,实例可以在多个线程中进行共享。

不存在线程不安全的情况,可以减少线程数量。

使用

导入依赖

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

yaml 配置

spring:
  redis:
    host: 47.100.222.85
    port: 6379
    jedis:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 500
        min-idle: 0
    lettuce:
      shutdown-timeout: 0ms

测试

@Resource
private RedisTemplate redisTemplate;

@Test
void contextLoads() {
    redisTemplate.opsForValue().set("myKey", "myValue");
    System.out.println(redisTemplate.opsForValue().get("myKey"));
}

输出为:myValue,连接成功。

源码分析

RedisAutoConfiguration

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 默认的 RedisTemplate 没有过多的设置,Redis 对象都是需要序列化的
        // 两个泛型都是 Object 的类型,我们使用需要强制转换,很不方便,预期是 <String, Object>
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    // 由于 String 是 Redis 最常使用的类型,所以说单独提出来了一个 Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

通过源码可以看出,SpringBoot 自动帮我们在容器中生成了一个 RedisTemplate 和一个 StringRedisTemplate。

但是,这个 RedisTemplate 的泛型是 <Object, Object>,写代码不方便,需要写好多类型转换的代码。

我们需要一个泛型为 <String, Object> 形式的 RedisTemplate。

并且,这个 RedisTemplate 没有设置数据存在 Redis 时,key 及 value 的序列化方式。

由 @ConditionalOnMissingBean 可以看出,如果 Spring 容器中有了自定义的 RedisTemplate 对象,自动配置的 RedisTemplate 不会实例化。

因此我们可以直接自己写个配置类,配置 RedisTemplate。

集成

Redis 配置类

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

/**
 * Redis 配置类
 */
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {

    @Value("${spring.redis.database}")
    private int database;

    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置数据库
        connectionFactory.setDatabase(database);
        // 刷新配置
        connectionFactory.afterPropertiesSet();
        // 重置连接
        connectionFactory.resetConnection();
        // 设置连接
        template.setConnectionFactory(connectionFactory);
        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.activateDefaultTyping(
                om.getPolymorphicTypeValidator(),
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);
        om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        this.registerLocalDateTime(om);
        serializer.setObjectMapper(om);
        // key采用String的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // value序列化方式采用jackson
        template.setValueSerializer(serializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        // 添加对事务的支持
        template.setEnableTransactionSupport(true);
        return template;
    }

    /**
     * 处理时间类型
     *
     * @param objectMapper 待序列化对象
     */
    private void registerLocalDateTime(ObjectMapper objectMapper) {
        // 设置java.util.Date时间类的序列化以及反序列化的格式
        objectMapper.setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN));

        JavaTimeModule timeModule = new JavaTimeModule();
        // LocalDateTime
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN);
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
        // LocalDate
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN);
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
        // LocalTime
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN);
        timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
        objectMapper.registerModule(timeModule);

    }

}

直接用 RedisTemplate 操作 Redis,比较繁琐。

因此直接封装好一个 RedisUtils,这样写代码更方便点。

这个 RedisUtils 交给Spring容器实例化,使用时直接注解注入。

Redis 工具类

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionCommands;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类
 */
@Component
public class RedisUtils {

    private static RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        RedisUtils.redisTemplate = redisTemplate;
    }

    /**
     * 连接测试
     *
     * @return 测试结果
     */
    public static String ping() {
        return redisTemplate.execute(RedisConnectionCommands::ping);
    }

    /**
     * 切换数据库
     *
     * @param dbIndex 数据库编号(默认有16个数据库,编号在0-15之间)
     */
    public static void select(int dbIndex) {
        // 获取连接工厂
        LettuceConnectionFactory connectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
        if (ObjUtil.isNull(connectionFactory)) {
            return;
        }
        // 设置数据库
        connectionFactory.setDatabase(dbIndex);
        // 刷新配置
        connectionFactory.afterPropertiesSet();
        redisTemplate.setConnectionFactory(connectionFactory);
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒),指定为-1代表永久有效
     */
    public static boolean expire(String key, long time) {
        return Boolean.TRUE.equals(redisTemplate.expire(key, time, TimeUnit.SECONDS));
    }

    /**
     * 根据key获取过期时间
     *
     * @param key 键
     * @return 时间(秒)。-1:永久有效;-2:该键已过期或不存在
     */
    public static long getExpire(String key) {
        Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        return ObjUtil.isNull(expire) ? 0 : expire;
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return 结果
     */
    public static boolean has(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     * 删除缓存
     *
     * @param key 键,可以传一个或多个值
     */
    public static void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollUtil.toList(key));
            }
        }
    }

    /**
     * 字符串缓存取值(不能获取token值,因为其自带加密,无法解析)
     *
     * @param key 键
     * @return 值
     */
    public static Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 字符串缓存设值
     *
     * @param key   键
     * @param value 值
     */
    public static void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 只有在 key 不存在时设置 key 的值
     *
     * @param key   键
     * @param value 值
     */
    public static void setIfAbsent(String key, String value) {
        redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 字符串缓存设值并设置生效时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒),time要大于0,如果time小于等于0,将设置无限期
     */
    public static void set(String key, Object value, long time) {
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } else {
            set(key, value);
        }
    }

    /**
     * 递增
     *
     * @param key 键
     * @param num 要增加几
     */
    public static long inc(String key, long num) {
        Long increment = redisTemplate.opsForValue().increment(key, num);
        return increment == null ? 0 : increment;
    }

    /**
     * 递减
     *
     * @param key 键
     * @param num 要减少几
     */
    public static long dec(String key, long num) {
        Long decrement = redisTemplate.opsForValue().decrement(key, num);
        return decrement == null ? 0 : decrement;
    }

    /**
     * 获取list缓存的内容
     *
     * @param key 键
     * @return 列表
     */
    public static List<Object> getList(String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束。0到-1代表所有值
     */
    public static List<Object> getList(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public static long getListSize(String key) {
        Long size = redisTemplate.opsForList().size(key);
        return size == null ? 0 : size;
    }

    /**
     * 获取list中的值
     *
     * @param key   键
     * @param index 索引index>=0时,0:表头,1:第二个元素,依次类推;<br>
     *              索引index<0时,-1:表尾,-2:倒数第二个元素,依次类推
     */
    public static Object getList(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     */
    public static void setList(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 将list放入缓存并设置生效时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public static void setList(String key, Object value, long time) {
        redisTemplate.opsForList().rightPush(key, value);
        if (time > 0) {
            expire(key, time);
        }
    }

    /**
     * 将list放入列表缓存
     *
     * @param key   键
     * @param value 值
     */
    public static void setList(String key, List<Object> value) {
        redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 将list放入列表缓存并设置生效时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     */
    public static void setList(String key, List<Object> value, long time) {
        redisTemplate.opsForList().rightPushAll(key, value);
        if (time > 0) {
            expire(key, time);
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     */
    public static void setList(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 移除n个值为value的
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     */
    public static void delList(String key, long count, Object value) {
        redisTemplate.opsForList().remove(key, count, value);
    }

    /**
     * 左弹出list缓存的内容
     *
     * @param key 键
     */
    public static void popList(String key) {
        redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     */
    public static Set<Object> getSet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 随机获取变量中的元素
     *
     * @param key 键
     * @return value值
     */
    public static Object randomSet(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }
    
    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public static long getSetSize(String key) {
        Long size = redisTemplate.opsForSet().size(key);
        return size == null ? 0 : size;
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值,可以是多个
     */
    public static void setSet(String key, Object... values) {
        redisTemplate.opsForSet().add(key, values);
    }

    /**
     * 将set数据放入缓存并设置生效时间
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值,可以是多个
     */
    public static void setSetTime(String key, long time, Object... values) {
        redisTemplate.opsForSet().add(key, values);
        if (time > 0) {
            expire(key, time);
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return 结果
     */
    public static boolean hasSet(String key, Object value) {
        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));

    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值,可以是多个
     */
    public static void delSet(String key, Object... values) {
        redisTemplate.opsForSet().remove(key, values);
    }
    
    /**
     * 添加元素,有序集合是按照元素的score值由小到大排列
     *
     * @param key   键
     * @param value 对象名称
     * @param score 数据值
     */
    public static void setZset(String key, TypedTuple<Object> value, double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * 添加元素,向集合中插入多个元素
     *
     * @param key    键
     * @param values set集合
     */
    public static void setZset(String key, Set<TypedTuple<Object>> values) {
        redisTemplate.opsForZSet().add(key, values);
    }

    /*
     * 获取元素集合,从小到大排序
     * @param key
     * @param start 开始位置
     * @param end 结束位置, -1查询所有
     * @return 元素集合
     */
    public static Set<Object> getZset(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 从集合中删除指定元素
     *
     * @param key    键
     * @param values 值
     */
    public static void delZset(String key, Object... values) {
        redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * 增加元素的score值,并返回增加后的值
     *
     * @param key   键
     * @param value 值
     * @param num   增加多少score值
     * @return 增加后的score值
     */
    public static double incScoreZset(String key, String value, double num) {
        Double result = redisTemplate.opsForZSet().incrementScore(key, value, num);
        return ObjUtil.isNull(result) ? 0 : result;
    }

    /**
     * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
     *
     * @param key   键
     * @param value 值
     * @return 0表示第一位
     */
    public static long rankZset(String key, Object value) {
        Long result = redisTemplate.opsForZSet().rank(key, value);
        return ObjUtil.isNull(result) ? 0 : result;
    }

    /**
     * 获取集合大小
     *
     * @param key 键
     * @return 集合大小
     */
    public static long getZsetSize(String key) {
        Long result = redisTemplate.opsForZSet().size(key);
        return ObjUtil.isNull(result) ? 0 : result;
    }

    /**
     * 获取键对应的所有值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public static Map<Object, Object> getHash(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 根据键和项取值
     *
     * @param key  键
     * @param item 项
     */
    public static Object getHash(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 设值
     *
     * @param key 键
     * @param map 对应多个键值
     */
    public static void setHash(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 设值
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     */
    public static void setHash(String key, Map<String, Object> map, long time) {
        redisTemplate.opsForHash().putAll(key, map);
        if (time > 0) {
            expire(key, time);
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在则创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     */
    public static void setHash(String key, String item, Object value) {
        redisTemplate.opsForHash().put(key, item, value);
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     */
    public static void setHash(String key, String item, Object value, long time) {
        redisTemplate.opsForHash().put(key, item, value);
        if (time > 0) {
            expire(key, time);
        }
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键,不能为null
     * @param item 项,不能为null
     * @return 结果
     */
    public static boolean hasHash(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键,不能为null
     * @param item 项,可以是多个,不能为null
     */
    public static void delHash(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * hash递增,如果不存在,就会创建一个,并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param num  要增加几(大于0)
     */
    public static double incHash(String key, String item, double num) {
        return redisTemplate.opsForHash().increment(key, item, num);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param num  要减少几(小于0)
     */
    public static double decHash(String key, String item, double num) {
        return redisTemplate.opsForHash().increment(key, item, -num);
    }

}

参考

https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0

版本

6.2.6

posted @ 2022-06-09 23:06  天航星  阅读(38)  评论(0编辑  收藏  举报