一、SpringBoot 集成 Redis
1.1 Jedis 与 Lettuce
在 SpringBoot 1.x 中默认采用的是 Jedis 作为连接 Redis Server 的客户端,在 SpringBoot 2.x 中默认采用 Lettuce 作为连接 Redis Server 的客户端。
Jedis 和 Lettuce 区别:
- Jedis:底层实现上直接连接 Redis Server,在多线程环境下是非线程安全的,需要配合连接池一起使用,例如:
commons-pool2
- Lettuce:底层基于 Netty 实现,多个实例可以复用一个连接,线程安全、性能优越。
1.2 添加依赖
由于 SpringBoot 2.x 默认将 Lettuce 作为 Redis 客户端,因此,无需引入额外的依赖,只需要在 pom.xml 文件中直接引入 Spring 官方提供的 redis starter即可,如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.3 配置文件
在 application.properties 配置文件中添加 Redis 相关的配置项,如下所示:
# Redis基础配置
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=redis
spring.redis.timeout=3000
# Redis连接池配置
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
连接池配置说明:
- max-active:连接池最大连接数,默认为8(若为负值,则表示不限制)
- max-wait:连接池最大阻塞等待时间,默认为-1(若为负值,则表示不限制)
- max-idle:连接池最大空闲连接数,默认为8
- min-idle:连接池最小空闲连接数,默认为0
二、RedisTemplate
2.1 自定义 RedisTemplate
当项目启动后,SpringBoot 会在 IOC 容器中自动了创建 RedisTemplate
和 StringRedisTemplate
两个 Bean,如下所示:
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
由RedisAutoConfiguration.class
源码可知,SpringBoot 自动创建的 StringRedisTemplate
适用于 value 为字符串的场景,而自动创建的 RedisTemplate
又是泛型的,使用起来需要进行各种类型的转换,极其不便。
为此,可以自己实现一个泛型为 <String, Object>
的 RedisTemplate
来替代 SpringBoot 自动生成的 RedisTemplate
。在 Redis 的 Template 中为 key、value、hashKey、hashValue 都定义了序列化器。因此,如果需要自己实现一个泛型为 <String, Object>
的 RedisTemplate
,则需要设置适当的序列化器,如下所示:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> getRedisTemplate(JedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(connectionFactory);
// 设置key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置hashKey的序列化器
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 设置value的序列化器
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 设置hashValue的序列化器
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
2.2 序列化器
StringRedisTemplate 默认使用 StringRedisSerializer 作为序列化器,RedisTemplate 默认使用 JdkSerializationRedisSerializer 作为序列号器,此时被序列化的对象必须实现 Serializable 接口。除此之外,还有 Jackson2JsonRedisSerializer 和 GenericJackson2JsonRedisSerializer,二者都能将对象序列化为 Json,但是,后者会在序列化的结果中添加一个@class
属性,属性值为对象的全类名,这样做的目的是方便反序列化。
三、封装工具类
下面对 Redis 相关的操作进行简易的封装,如下所示:
package com.hannibal.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
/**
* 设置过期时间
*
* @param key 键
* @param time 时间
* @return
*/
public boolean expire(final String key, final long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
return true;
}
return false;
} catch (Exception e) {
return false;
}
}
/**
* 获取过期时间(单位:秒)
* 返回0表示永久有效
*
* @param key 键
* @return
*/
public long ttl(final String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 删除key(支持传多个key)
*
* @param key 键
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**************************************** String 数据类型 ******************************************/
/**
* 获取缓存
*
* @param key 键
* @return
*/
public Object get(final String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 批量获取缓存
*
* @param keys 键
* @return
*/
public List<Object> mget(final List<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 添加缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean set(final String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 添加缓存,同时设置过期时间
*
* @param key 键
* @param value 值
* @param time 过期时间
* @return
*/
public boolean setex(final String key, Object value, final long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
return true;
} else {
throw new RuntimeException("过期时间必须大于 0");
}
} catch (Exception e) {
return false;
}
}
/**
* 如果key不存在,则添加缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean setnx(final String key, Object value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 批量添加缓存
*
* @param map 键值对
* @return
*/
public boolean mset(final Map<String, Object> map) {
return redisTemplate.opsForValue().multiSet(map);
}
/**
* 递增操作
*
* @param key 键
* @param number 数值
* @return
*/
public long incr(final String key, final long number) {
if (number > 0) {
return redisTemplate.opsForValue().increment(key, number);
} else {
throw new RuntimeException("参数 number 必须大于 0");
}
}
/**
* 递减操作
*
* @param key 键
* @param number 数值
* @return
*/
public long decr(final String key, final long number) {
if (number > 0) {
return redisTemplate.opsForValue().increment(key, -number);
} else {
throw new RuntimeException("参数 number 必须大于 0");
}
}
/**************************************** Hash 数据类型 ******************************************/
/**
* hget 操作,获取指定的值
*
* @param key 键
* @param field 字段
* @return
*/
public Object hget(final String key, final String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* hmget 操作,同时获取多个值
*
* @param key 键
* @param fields 字段列表
* @return
*/
public List<Object> hmget(final String key, List<String> fields) {
return redisTemplate.opsForHash().multiGet(key, fields);
}
/**
* hkeys 操作,获取 hash 中的所有字段
*
* @param key 键
* @return
*/
public Set<Object> hkeys(final String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* hvalues 操作,获取 hash 中的所有值
*
* @param key 键
* @return
*/
public List<Object> hvalues(final String key) {
return redisTemplate.opsForHash().values(key);
}
/**
* hgetall 操作,获取所有键值对
*
* @param key 键
* @return
*/
public Map<Object, Object> hgetall(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* hset 操作
*
* @param key 键
* @param field 字段
* @param value 值
* @return
*/
public boolean hset(final String key, final String field, Object value) {
try {
redisTemplate.opsForHash().put(key, field, value);
return true;
} catch (Exception e) {
return false;
}
}
/**
* hset 操作,同时设置过期时间
*
* @param key 键
* @param field 字段
* @param value 值
* @param time 过期时间
* @return
*/
public boolean hset(final String key, final String field, Object value, final long time) {
try {
redisTemplate.opsForHash().put(key, field, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
return false;
}
}
/**
* hsetnx 操作,如果字段不存在,则添加缓存
* @param key
* @param field
* @param value
* @return
*/
public boolean hsetnx(final String key, final String field, Object value) {
return redisTemplate.opsForHash().putIfAbsent(key, field, value);
}
/**
* hmset 操作
*
* @param key 键
* @param map 键值对
* @return
*/
public boolean hmset(final String key, final Map<Object, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
return false;
}
}
/**
* hmset 操作,同时设置过期时间
*
* @param key 键
* @param map 键值对
* @param time 过期时间
* @return
*/
public boolean hmset(final String key, final Map<Object, Object> map, final long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
return false;
}
}
/**
* hexists操作,判断 hash 中是否存在指定字段
* @param key 键
* @param field 字段
* @return
*/
public boolean hexists(final String key, final String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 删除 hash 中的键值对,支持同时删除多个键值对
* @param key 键
* @param fields 字段列表
* @return
*/
public boolean hdel(final String key, final String... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* hash 中的递增操作
*
* @param key 键
* @param field 字段
* @param number 递增的长度
* @return
*/
public long hincrby(final String key, final String field, final long number) {
return redisTemplate.opsForHash().increment(key, field, number);
}
/**
* hash 中的递减操作
*
* @param key 键
* @param field 字段
* @param number 递减的长度
* @return
*/
public long hdecrby(final String key, final String field, final long number) {
return redisTemplate.opsForHash().increment(key, field, -number);
}
/**************************************** Set 数据类型 ******************************************/
/**
* sadd操作,向集合添加元素
*
* @param key 键
* @param values 元素
* @return
*/
public long sadd(final String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* srem 操作,移除指定的元素
*
* @param key 键
* @param values 元素
* @return
*/
public long srem(final String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
/**
* scard 操作,获取集合中元素数量
*
* @param key 键
* @return
*/
public long scard(final String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* sismember 操作,判断指定值是否为集合中的元素
*
* @param key 键
* @param value 值
* @return
*/
public boolean sismember(final String key, Object value) {
return redisTemplate.opsForSet(key, value);
}
/**************************************** List 数据类型 ******************************************/
/**
* lpush操作,返回添加元素后list的长度
*
* @param key 键
* @param value 值
* @return
*/
public long lpush(final String key, Object value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* rpush操作,返回添加元素后list的长度
*
* @param key 键
* @param value 值
* @return
*/
public long rpush(final String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* lpop操作,返回移除的元素
*
* @param key 键
* @return
*/
public Object lpop(final String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* rpop操作,返回移除的元素
*
* @param key 键
* @return
*/
public Object rpop(final String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* lindex操作,返回指定索引位置的元素
*
* @param key 键
* @param index 索引
* @return
*/
public Object lIndex(final String key, final long index) {
return redisTemplate.opsForList().index(key, index);
}
/**
* 获取元素在list中的第一个出现的位置
*
* @param key 键
* @param value 值
* @return
*/
public long lIndecOf(final String key, Object value) {
return redisTemplate.opsForList().indexOf(key, value);
}
/**
* 获取元素在list中的最后出现的位置
*
* @param key 键
* @param value 值
* @return
*/
public long lLastIngdexOf(final String key, Object value) {
return redisTemplate.opsForList().lastIndexOf(key, value);
}
/**
* llen操作,获取list的长度
*
* @param key 键
* @return
*/
public long lLen(final String key) {
return redisTemplate.opsForList().size(key);
}
/**
* lrange操作,获取指定范围内的元素
*
* @param key 键
* @param start 开始位置
* @param end 结束位置
* @return
*/
public List<Object> lRange(final String key, final long start, final long end) {
return redisTemplate.opsForList().range(key, start, end);
}
}
参考文档: