SpringBoot 集成 Redis

864 阅读6分钟

一、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 容器中自动了创建 RedisTemplateStringRedisTemplate两个 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);
    }
}

参考文档: