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