前言:
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的连接池,性能其实并不高,但是作为一个小项目的使用方案,其实无伤大雅,如果后期需要提升性能。可以再实现连接池的部分。