RedisTemplate真实项目应用案例


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

猜你喜欢

转载自blog.csdn.net/m0_46897923/article/details/115358300