前言:
redis作为当下最流行的缓存解决方案,这篇文章便是总结redis在实际开发中与SpringBoot的整合。
一.还原设计过程
这部分总结RedisTemplate从定义到使用的过程,并还原这个设计过程。
1.引入依赖,修改配置文件
依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
提供redis的配置,如下:
spring.redis.port=6379
spring.redis.host=192.168.150.101
spring.redis.database=0
2.注入自定义的RedisTemplate
我们知道,自己定义RedisTemplate并注入Spring容器中,会覆盖掉默认的RedisTemplate。
@Configuration
public class RedisConfig {
//重新注入RedisTemplate,重新注入会覆盖掉Spring默认提供的
@Bean
public RedisTemplate<String,Object> getRedisTemplate(RedisConnectionFactory factoryBean){
System.out.println("自定义的RedisTemplate被初始化了");
RedisTemplate redisTemplate = new RedisTemplate();
System.out.println("真正使用的factoryBean:"+factoryBean.toString());
redisTemplate.setConnectionFactory(factoryBean);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashValueSerializer(n);
//自定义redisTemplate必须执行该方法
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
启动项目查看,自定义的RedisTemplate有没有初始化,并且查看默认使用的连接工厂是谁。
从图中可以看出控制台输出了两行
自定义的RedisTemplate被初始化了
真正使用的factoryBean:org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory@6136998b
可以看出自定义的RedisTemplate被初始化了,且使用的是LettuceConnectionFactory连接工厂,这里我们使用的是默认的工厂里的参数,然后我们去使用下自己注入的RedisTemplate,实现个接口如下:
@Controller
public class TestRedisController {
@Autowired
RedisTemplate redisTemplate;
@RequestMapping("/test")
public void test2(){
//设置序列化策略
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//获取值,这个过程自动反序列化
redisTemplate.opsForValue().set("key","value");
redisTemplate.opsForHash().put("外键","内键","值");
System.out.println(redisTemplate.opsForValue().get("key"));
System.out.println(redisTemplate.opsForHash().get("外键","内键"));
}
}
然后调用该接口,查看结果:
请忽略报错,通过控制台可以看到数据正常存取了,没啥问题,我们测试的类型还是比较简单,来自定义一个类型看看是否可以正常获取到,自定义类型如下:
@Data
public class Human implements Serializable {
private String name;
private Integer old;
private Double height;
private String like;
private Boolean sex;
private List<String> list;
}
然后改写控制器,如下:
@Controller
public class TestRedisController {
@Autowired
RedisTemplate redisTemplate;
@RequestMapping("/test")
public void test2(){
Human human = new Human();
human.setHeight(50d);
human.setLike("吃");
human.setList(Arrays.asList("父亲","母亲","妻子"));
human.setName("秦始皇");
human.setSex(true);
human.setOld(20);
//设置序列化策略
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//获取值,这个过程自动反序列化
redisTemplate.opsForValue().set("key","value");
redisTemplate.opsForHash().put("外键","内键",human);
System.out.println(redisTemplate.opsForValue().get("key"));
System.out.println(redisTemplate.opsForHash().get("外键","内键"));
}
}
然后我们重启看下是否正确获取了:
我们可以看到正常获取了,使用没有问题。
3.为hashvalue设置序列化策略
经笔者反复测试,并没有发现这块不设置对hashvalue值的获取有什么影响,既然加上和不加上效果一样,那么选择加上。首先我们需要提供一个序列化对象的类RedisObjectSerializer
public class RedisObjectSerializer implements RedisSerializer<Object> {
// 做一个空数组,不是null
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
// 为了方便进行对象与字节数组的转换,所以应该首先准备出两个转换器
private final Converter<Object, byte[]> serializingConverter = new SerializingConverter();
private final Converter<byte[], Object> deserializingConverter = new DeserializingConverter();
@Override
public byte[] serialize(Object obj) {
// 这个时候没有要序列化的对象出现,所以返回的字节数组应该就是一个空数组
if (obj == null) {
return EMPTY_BYTE_ARRAY;
}
// 将对象变为字节数组
return this.serializingConverter.convert(obj);
}
@Override
public Object deserialize(byte[] data) {
// 此时没有对象的内容信息
if (data == null || data.length == 0) {
return null;
}
return this.deserializingConverter.convert(data);
}
}
然后修改,调用的部分如下:
然后测试,反复结果都是一样,并没有任何区别,这里记录下,希望知道区别的小伙伴不吝赐教。
4.自定义redis工具类
使用redisTemplate,去写代码会有很多重复部分,所以项目里并不会去直接使用redisTemplate,而是通过封装一个工具类,我们每次去调用这个工具类。工具类代码如下:
public class RedisRepository {
/**
* 默认编码
*/
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
/**
* key序列化
*/
private static final RedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
/**
* value 序列化
*/
private static final RedisSerializer OBJECT_SERIALIZER = new RedisObjectSerializer();
/**
* Spring Redis Template
*/
private RedisTemplate<String, Object> redisTemplate;
public RedisRepository(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.redisTemplate.setKeySerializer(STRING_SERIALIZER);
this.redisTemplate.setHashKeySerializer(STRING_SERIALIZER);
this.redisTemplate.setValueSerializer(OBJECT_SERIALIZER);
this.redisTemplate.setHashValueSerializer(OBJECT_SERIALIZER);
}
/**
* 存储hash对象
*/
public void set(String keyS,String keyIn,Object value){
redisTemplate.opsForHash().put(keyS,keyIn,value);
}
/**
* 获取hash对象
*/
public Object get(String keyS,String keyIn){
return redisTemplate.opsForHash().get(keyS,keyIn);
}
/**
* 获取链接工厂
*/
public RedisConnectionFactory getConnectionFactory() {
return this.redisTemplate.getConnectionFactory();
}
/**
* 获取 RedisTemplate对象
*/
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}
/**
* 清空DB
*
* @param node redis 节点
*/
public void flushDB(RedisClusterNode node) {
this.redisTemplate.opsForCluster().flushDb(node);
}
/**
* 添加到带有 过期时间的 缓存
*
* @param key redis主键
* @param value 值
* @param time 过期时间(单位秒)
*/
public void setExpire(final byte[] key, final byte[] value, final long time) {
redisTemplate.execute((RedisCallback<Long>) connection -> {
connection.setEx(key, time, value);
// log.debug("[redisTemplate redis]放入 缓存 url:{} ========缓存时间为{}秒", key, time);
return 1L;
});
}
/**
* 添加到带有 过期时间的 缓存
*
* @param key redis主键
* @param value 值
* @param time 过期时间(单位秒)
*/
public void setExpire(final String key, final Object value, final long time) {
redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = OBJECT_SERIALIZER.serialize(value);
connection.setEx(keys, time, values);
return 1L;
});
}
/**
* 添加到带有 过期时间的 缓存
*
* @param key redis主键
* @param time 过期时间(单位秒)
*/
public void setExpire(final String key, final long time) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
/**
* 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销
*
* @param keys redis主键数组
* @param values 值数组
* @param time 过期时间(单位秒)
*/
public void setExpire(final String[] keys, final Object[] values, final long time) {
redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
for (int i = 0; i < keys.length; i++) {
byte[] bKeys = serializer.serialize(keys[i]);
byte[] bValues = OBJECT_SERIALIZER.serialize(values[i]);
connection.setEx(bKeys, time, bValues);
}
return 1L;
});
}
/**
* 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销
*
* @param keys the keys
* @param values the values
*/
public void set(final String[] keys, final Object[] values) {
redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
for (int i = 0; i < keys.length; i++) {
byte[] bKeys = serializer.serialize(keys[i]);
byte[] bValues = OBJECT_SERIALIZER.serialize(values[i]);
connection.set(bKeys, bValues);
}
return 1L;
});
}
/**
* 添加到缓存
*
* @param key the key
* @param value the value
*/
public void set(final String key, final Object value) {
redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = OBJECT_SERIALIZER.serialize(value);
connection.set(keys, values);
// log.debug("[redisTemplate redis]放入 缓存 url:{}", key);
return 1L;
});
}
/**
* 查询在这个时间段内即将过期的key
*
* @param key the key
* @param time the time
* @return the list
*/
public List<String> willExpire(final String key, final long time) {
final List<String> keysList = new ArrayList<>();
redisTemplate.execute((RedisCallback<List<String>>) connection -> {
Set<String> keys = redisTemplate.keys(key + "*");
for (String key1 : keys) {
Long ttl = connection.ttl(key1.getBytes(DEFAULT_CHARSET));
if (0 <= ttl && ttl <= 2 * time) {
keysList.add(key1);
}
}
return keysList;
});
return keysList;
}
/**
* 查询在以keyPatten的所有 key
*
* @param keyPatten the key patten
* @return the set
*/
public Set<String> keys(final String keyPatten) {
return redisTemplate.execute((RedisCallback<Set<String>>) connection -> redisTemplate.keys("*" + keyPatten + "*"));
}
/**
* 根据key获取对象
*
* @param key the key
* @return the byte [ ]
*/
public byte[] get(final byte[] key) {
byte[] result = redisTemplate.execute((RedisCallback<byte[]>) connection -> connection.get(key));
// log.debug("[redisTemplate redis]取出 缓存 url:{} ", key);
return result;
}
/**
* 根据key获取对象
*
* @param key the key
* @return the string
*/
public Object get(final String key) {
Object resultStr = redisTemplate.execute((RedisCallback<Object>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = connection.get(keys);
return OBJECT_SERIALIZER.deserialize(values);
});
// log.debug("[redisTemplate redis]取出 缓存 url:{} ", key);
return resultStr;
}
/**
* 根据key获取对象
*
* @param keyPatten the key patten
* @return the keys values
*/
public Map<String, Object> getKeysValues(final String keyPatten) {
// log.debug("[redisTemplate redis] getValues() patten={} ", keyPatten);
return redisTemplate.execute((RedisCallback<Map<String, Object>>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
Map<String, Object> maps = new HashMap<>(16);
Set<String> keys = redisTemplate.keys(keyPatten + "*");
if (!CollectionUtils.isEmpty(keys)) {
for (String key : keys) {
byte[] bKeys = serializer.serialize(key);
byte[] bValues = connection.get(bKeys);
Object value = OBJECT_SERIALIZER.deserialize(bValues);
maps.put(key, value);
}
}
return maps;
});
}
/**
* Ops for hash hash operations.
*
* @return the hash operations
*/
public HashOperations<String, String, Object> opsForHash() {
return redisTemplate.opsForHash();
}
/**
* 对HashMap操作
*
* @param key the key
* @param hashKey the hash key
* @param hashValue the hash value
*/
public void putHashValue(String key, String hashKey, Object hashValue) {
// log.debug("[redisTemplate redis] putHashValue() key={},hashKey={},hashValue={} ", key, hashKey, hashValue);
opsForHash().put(key, hashKey, hashValue);
}
/**
* 获取单个field对应的值
*
* @param key the key
* @param hashKey the hash key
* @return the hash values
*/
public Object getHashValues(String key, String hashKey) {
// log.debug("[redisTemplate redis] getHashValues() key={},hashKey={}", key, hashKey);
return opsForHash().get(key, hashKey);
}
/**
* 根据key值删除
*
* @param key the key
* @param hashKeys the hash keys
*/
public void delHashValues(String key, Object... hashKeys) {
// log.debug("[redisTemplate redis] delHashValues() key={}", key);
opsForHash().delete(key, hashKeys);
}
/**
* key只匹配map
*
* @param key the key
* @return the hash value
*/
public Map<String, Object> getHashValue(String key) {
// log.debug("[redisTemplate redis] getHashValue() key={}", key);
return opsForHash().entries(key);
}
/**
* 批量添加
*
* @param key the key
* @param map the map
*/
public void putHashValues(String key, Map<String, Object> map) {
opsForHash().putAll(key, map);
}
/**
* 集合数量
*
* @return the long
*/
public long dbSize() {
return redisTemplate.execute(RedisServerCommands::dbSize);
}
/**
* 清空redis存储的数据
*
* @return the string
*/
public String flushDB() {
return redisTemplate.execute((RedisCallback<String>) connection -> {
connection.flushDb();
return "ok";
});
}
/**
* 判断某个主键是否存在
*
* @param key the key
* @return the boolean
*/
public boolean exists(final String key) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.exists(key.getBytes(DEFAULT_CHARSET)));
}
/**
* 删除key
*
* @param keys the keys
* @return the long
*/
public long del(final String... keys) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
long result = 0;
for (String key : keys) {
result = connection.del(key.getBytes(DEFAULT_CHARSET));
}
return result;
});
}
public long del(byte[] key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.del(key));
}
/**
* 获取 RedisSerializer
*
* @return the redis serializer
*/
protected RedisSerializer<String> getRedisSerializer() {
return redisTemplate.getStringSerializer();
}
/**
* 对某个主键对应的值加一,value值必须是全数字的字符串
*
* @param key the key
* @return the long
*/
public long incr(final String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> redisSerializer = getRedisSerializer();
return connection.incr(redisSerializer.serialize(key));
});
}
/**
* redis List 引擎
*
* @return the list operations
*/
public ListOperations<String, Object> opsForList() {
return redisTemplate.opsForList();
}
/**
* redis List数据结构 : 将一个或多个值 value 插入到列表 key 的表头
*
* @param key the key
* @param value the value
* @return the long
*/
public Long leftPush(String key, Object value) {
return opsForList().leftPush(key, value);
}
/**
* redis List数据结构 :移除列表key中值等于value的所有元素
*
* @param key 1
* @param value 2
* @return {@link Long}
* @author wangchunfeng
* @date 2020/7/28 9:57
**/
public Long lrem(String key, Object value) {
return opsForList().remove(key, 0, value);
}
/**
* redis List数据结构 : 移除并返回列表 key 的头元素
*
* @param key the key
* @return the string
*/
public Object leftPop(String key) {
return opsForList().leftPop(key);
}
/**
* redis List数据结构 :将一个或多个值 value 插入到列表 key 的表尾(最右边)。
*
* @param key the key
* @param value the value
* @return the long
*/
public Long in(String key, Object value) {
return opsForList().rightPush(key, value);
}
/**
* redis List数据结构 : 移除并返回列表 key 的末尾元素
*
* @param key the key
* @return the string
*/
public Object rightPop(String key) {
return opsForList().rightPop(key);
}
/**
* redis List数据结构 : 返回列表 key 的长度 ; 如果 key 不存在,则 key 被解释为一个空列表,返回 0 ; 如果 key 不是列表类型,返回一个错误。
*
* @param key the key
* @return the long
*/
public Long length(String key) {
return opsForList().size(key);
}
/**
* redis List数据结构 : 根据参数 i 的值,移除列表中与参数 value 相等的元素
*
* @param key the key
* @param i the
* @param value the value
*/
public void remove(String key, long i, Object value) {
opsForList().remove(key, i, value);
}
/**
* redis List数据结构 : 将列表 key 下标为 index 的元素的值设置为 value
*
* @param key the key
* @param index the index
* @param value the value
*/
public void set(String key, long index, Object value) {
opsForList().set(key, index, value);
}
/**
* redis List数据结构 : 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 end 指定。
*
* @param key the key
* @param start the start
* @param end the end
* @return the list
*/
public List<Object> getList(String key, int start, int end) {
return opsForList().range(key, start, end);
}
/**
* redis List数据结构 : 批量存储
*
* @param key the key
* @param list the list
* @return the long
*/
public Long leftPushAll(String key, List<String> list) {
return opsForList().leftPushAll(key, list);
}
/**
* redis List数据结构 : 将值 value 插入到列表 key 当中,位于值 index 之前或之后,默认之后。
*
* @param key the key
* @param index the index
* @param value the value
*/
public void insert(String key, long index, Object value) {
opsForList().set(key, index, value);
}
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
/**
* 将key 的值设为 value ,当且仅当 key 不存在
* 默认时间单位是秒
*
* @param key
* @param value 自定义 value
* @param seconds 自定义过期时间秒数
* @return 设置成功返回 true 失败返回false
*/
public boolean setNx(String key, Object value, int seconds) {
return this.setNx(key, value, seconds, TimeUnit.SECONDS);
}
/**
* 将key 的值设为 value ,当且仅当 key 不存在
* 注:常用与分布式锁
*
* @param key
* @param value
* @param duration 时间量
* @param timeUnit 时间单位枚举
* @return 设置成功返回 true 失败返回false
*/
public boolean setNx(String key, Object value, int duration, TimeUnit timeUnit) {
if (this.exists(key)) {
return false;
}
return this.exists(key) ? false : this.put(key, value, duration, timeUnit);
}
/**
* 添加数据到redis
* 自定义过期时间
* 注:从 Redis 2.6.12 版本开始, SET 在设置操作成功完成时,返回 OK
*
* @param key
* @param value
* @param duration 时间量
* @param timeUnit 时间单位枚举
*/
public boolean put(final String key, final Object value, final int duration, final TimeUnit timeUnit) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = OBJECT_SERIALIZER.serialize(value);
// 删除缓存数据
connection.del(key.getBytes(DEFAULT_CHARSET));
// 添加缓存数据
connection.set(keys, values);
// 设置过期时间
long seconds = timeUnit.toSeconds(duration);
connection.setEx(keys, seconds, values);
return true;
});
}
}
注入工具类的对象到Spring容器中
@Bean
public RedisRepository getRedisRepository(RedisTemplate redisTemplate){
return new RedisRepository(redisTemplate);
}
修改测试控制器如下:
@Controller
public class TestRedisController {
@Autowired
RedisRepository redisRepository;
@RequestMapping("/test")
public void test2(){
Human human = new Human();
human.setHeight(50d);
human.setLike("吃");
human.setList(Arrays.asList("父亲","母亲","妻子"));
human.setName("秦始皇");
human.setSex(true);
human.setOld(20);
redisRepository.set("key","newvalue");
redisRepository.set("外键","内健",human);
System.out.println(redisRepository.get("key"));
System.out.println(redisRepository.get("外键","内健"));
}
}
测试结果如下:
可以看出值的获取与插入都是没有问题的。
二.总结
记录了一个小项目中集成redis的方案,在做的另一个大型分布式项目使用的dubbo服务提供的redis接口,看不到源码,只能通过rpc接口去调用,并看不到实现,很是遗憾,这种集成有一个很明显的缺陷就是没有自定义redis的连接池,性能其实并不高,但是作为一个小项目的使用方案,其实无伤大雅,如果后期需要提升性能。可以再实现连接池的部分。