Skip to content
0

Redis - Spring Data Redis

NOTE

前面的 Redis - Java 整合 主要介绍了 Jedis 的使用,这是官方提供连接 Redis 的类,类似于原生 JDBC,这样有一个问题,那就是需要写很多样板代码,如创建、关闭等,而现在主流使用的是 Spring 对 Jedis 的进一步封装:RedisTemplate。

本内容先从 Spring 如何使用 Redis,再介绍 Spring Boot 使用 Redis,两者的区别就在于配置而已,API 代码依然是一样的。

顺便介绍前面 Redis 安装没有的:Windows 下安装 Redis、Docker 安装 Redis。

2022-12-11 @Young Kbt

介绍

Redis 支持

Spring Data 支持的键值存储之一是 Redis。引用 Redis 项目主页:

Redis 是一个开源(BSD 许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串(String)、哈希表(Hash)、列表(List)、集合(Set)、有序集合(sorted set),位图,hyperloglogs 等数据类型。内置复 制、Lua 脚本、LRU 收回、事务以及不同级别磁盘持久化功能,同时通过 Redis Sentinel 提供高可用,通过 Redis Cluster 提供自动分区。

为什么要用缓存:

  1. 提高存储读写的性能(高性能):统计数据 MySQL,解决 MySQL 查询速度慢的问题
  2. 提供并发(高并发):解决 MySQL 并发不足的问题

和数据库相比:

  • 操作:关系型数据库(表,通过 SQL 执行操作) ,Redis 是缓存数据库(Key­value,通过 Redis 命令 get、set... 执行操作)
  • 作用上:
    • MySQL 用于持久化的存储数据到硬盘,功能强大,但是速度较慢
    • Redis 用于存储使用较为频繁的数据到缓存中,读取速度快

为什么使用 Spring Data Redis?

Spring Framework 是领先的全栈 Java/JEE 应用程序框架。它通过使用依赖注入、AOP 和可移植服务抽象提供了一个轻量级容器和一个非侵入式编程模型。

NoSQL 存储系统提供了经典 RDBMS 的替代方案,以实现水平可扩展性和速度。在实现方面,键值存储代表 NoSQL 空间中最大(也是最古老)的成员之一。

Spring Data Redis (SDR) 框架通过 Spring 出色的基础架构支持消除了与存储交互所需的冗余任务和样板代码,从而可以轻松编写使用 Redis 键值存储的 Spring 应用程序。

原生 Jedis 有很多的样板代码:

java
// 连接本地的 Redis 服务
Jedis jedis = new Jedis("localhost",6379);
System.out.println("连接成功");
try {
    // 设置 redis 字符串数据
    jedis.set("name", "可乐");
    // 获取存储的数据并输出
    System.out.println("redis 存储的字符串为: "+ jedis.get("name"));
}
finally {
    jedis.close();
}

spring-data-redis 提供了如下功能:

  • 连接池自动管理,RedisTemplate 为执行各种 Redis 操作、异常转换和序列化支持提供高级抽象。
    • ValueOperations:String 类型简单 K-V 操作
    • SetOperations:Set 类型数据操作
    • ZSetOperations:Zset 类型数据操作
    • HashOperations:针对 Map 类型的数据操作
    • ListOperations:针对 List 类型的数据操作
  • 支持多个 Redis 驱动程序(Lettuce 和 Jedis)的低级抽象
  • 支持 Repository 接口的自动实现,包括使用 @EnableRedisRepositories
  • 发布订阅支持(例如用于消息驱动的 POJO 的 MessageListenerContainer)
  • 针对 Jedis 客户端中大量 API 进行了归类封装,将同一类型操作封装为 operation 接口等

参考官网 https://spring.io/projects/spring-data-redis

Redis 环境搭建

基于 Windows

下载 Redis,下载地址:https://github.com/MicrosoftArchive/redis/releases。Windows 安装 Redis,只需要找到 .zip 下载,如:Redis-x64-3.2.100.zip

下载完后解压到指定目录,然后打开 cmd 进入当前的目录,执行 Redis 的启动命令:redis-server.exe redis.windows.conf

用 Windows 客户端,这里推荐我使用的软件:Another-Redis-Desktop-Manager。下载地址

因为放在自己的服务器,宽带为 1M,下载慢,所以也可以自己去网上找。

基于 Linux — Docker

下载镜像

java
docker pull redis

创建实例并启动

sh
# .创建目录和配置文件 redis.conf
mkdir /docker
mkdir /docker/redis
mkdir /docker/redis/conf
mkdir /docker/redis/data

# 创建 redis.conf 配置文件
touch /docker/redis/conf/redis.conf

创建启动容器,加载配置文件并持久化数据

sh
docker run ‐d ‐‐privileged=true ‐p 6379:6379 ‐v /docker/redis/conf/redis.conf:/etc/redis/redis.conf ‐v /docker/r
edis/data:/data ‐‐name redis redis:latest redis‐server /etc/redis/redis.conf ‐‐appendonly yes

参数说明:

  • ‐‐privileged=true:容器内的 root 拥有真正 root 权限,否则容器内 root 只是外部普通用户权限
  • ‐v /docker/redis/conf/redis.conf:/etc/redis/redis.conf:映射配置文件
  • ‐v /docker/redis/data:/data:映射数据目录
  • redis‐server /etc/redis/redis.conf:指定配置文件启动 redis‐server 进程
  • ‐‐appendonly yes:开启数据持久化

进入容器测试

sh
docker exec ‐it redis redis‐cli
sh
set test 测试
get test

快速开始

先从 Spring 配置 Redis,版本根据你当前情况选择,这里仅是演示,所以版本随意取的。

pom.xml:

xml
<dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring‐data‐redis</artifactId>
        <version>3.1.2</version>
    </dependency>

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.7.0</version>
    </dependency>
    <!‐‐ junit5 ‐‐>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit‐jupiter</artifactId>
        <version>5.6.3</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring‐test</artifactId>
        <version>5.3.10</version>
        <scope>test</scope>
    </dependency>
</dependencies>

基于 xml

xml
<?xml version="1.0" encoding="UTF‐8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/sp
                           ring‐beans.xsd">

    <bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use‐pool="true"/>

    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connection‐fac
          tory‐ref="jedisConnectionFactory"/>
    ...
</beans>

基于 Java 的配置(推荐)

java
@Configuration
public class RedisConfig {

    // 1. redis 的连接工厂
    @Bean
    public RedisConnectionFactory redisConnectionFactory(){

        // 设置单机 redis 远程服务地址
        RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1",6379);

        // 集群
        // new RedisClusterConfiguration(传入多个远程地址 ip)

        // 连接池相关配置
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接数(根据并发量估算)
        jedisPoolConfig.setMaxTotal(100);
        // 最大连接数= 最大空闲数
        jedisPoolConfig.setMaxIdle(100);
        // 最小空闲连接数
        jedisPoolConfig.setMinIdle(10);
        // 最长等待时间 0 无限等待 解决线程堆积问题 最好设置
        jedisPoolConfig.setMaxWaitMillis(2000);

        // 在 SDR2.0+ 建议使用 JedisClientConfiguration 来设置连接池
        // 默认设置了读取超时和连接超时为2秒
        JedisClientConfiguration clientConfiguration =
            JedisClientConfiguration.builder()
            .usePooling()
            .poolConfig(jedisPoolConfig).build();


        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration,clientConfi
                                                                                   guration);
        return jedisConnectionFactory;
    }

    // 2. redis 模板类(工具类)
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // StringRedisSerializer 只能传入 String 类型的值(存入 Redis 之前就要转换成 String)
        // redisTemplate.setDefaultSerializer(new StringRedisSerializer());

        // 区分 Key 和 Value 的序列化(推荐)
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        return redisTemplate;
    }
}

关于 setDefaultSerializer 的选择,官方默认的是 jdkSerializeable 二进制字节码,即存到 Redis 之前,会将数据转成二进制字节码再存进去,所以我们去 Redis 看发现数据可读性差,且 有安全风险

所以我们需要告诉 RedisTemplate,按照我们规定的序列化存入 Redis。setDefaultSerializer 是 Key 和 Value 统一同一个,如果细分为 Key 和 Value 的序列化,则按照上面推荐的方式序列化。这也是网上教程给的序列化方式。

测试

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class SpringDataRedis {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testKeyBoundOperations(){
        redisTemplate.boundValueOps("name").set("可乐");
        Object name = redisTemplate.boundValueOps("name").get();
        System.out.println(name);
    }


    @Test
    public void testKeyTypeOperations(){
        ValueOperations opsForValue = redisTemplate.opsForValue();
        opsForValue.set("name","xushu");

        Object name =opsForValue.get("name");
        System.out.println(name);
    }
}

这里没有用到 SpringBootTest 注解是因为这是 Spring,还不到 Spring Boot。

而 Junit 5 在 Spring 测试只需要这一个注解,如果是 Junit 4,则需要两个,自行百度哪两个,不过官方都推荐使用 Junit 5 了。

Redis 常用数据类型的 CRUD 操作:

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class SpringDataRedis {
    // 注入 template 对象
    @Autowired
    private RedisTemplate redisTemplate;
    //‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ Hash 类型的操作:经常用 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

    //‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 值类型的操作:因为操作数量少,所以不长用 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

    //‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ set 类型的操作:因为操作数量少,所以不长用 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

    /**
     * 存入值
     */
    @Test
    public void boundSetOpsAdd(){
        redisTemplate.boundSetOps("nameset").add("曹操"); //放入值
        redisTemplate.boundSetOps("nameset").add("刘备");
        redisTemplate.boundSetOps("nameset").add("孙权");
    }
    /**
     * 提取值
     */
    @Test
    public void boundSetOpsGet(){
        Set names= redisTemplate.boundSetOps("nameset").members();//取出值
        System.out.println(names);
    }
    /**
     * 删除集合中的某一个值
     */
    @Test
    public void boundSetOpsDelete(){
        redisTemplate.boundSetOps("nameset").remove("曹操");
    }

    /**
     * 删除整个集合
     */
    @Test
    public void boundSetOpsDeleteAll(){
        redisTemplate.delete("nameset");
    }

    //‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐list 类型的操作:因为操作数量少,所以不长用‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

}

API

RedisTemplate 提供了两大类 API,即一种在初始化绑定 key 的。一种初始化不绑定 key,后续操作再绑定 key,后者更灵活,也是最常用的。

键类型操作(初始化不绑定 key):

界面描述
GeoOperationsRedis 地理空间操作,例如 GEOADD,GEORADIUS
HashOperationsRedis 哈希操作
HyperLogLogOperationsRedis HyperLogLog 操作,例如:PFADD、PFCOUNT
ListOperationsRedis 列表操作
SetOperationsRedis 设置操作
ValueOperationsRedis 字符串(或值)操作
ZSetOperationsRedis ZSet(或排序集)操作

键绑定操作(初始化绑定 key):

界面描述
BoundGeoOperationsRedis 键绑定地理空间操作,例如 GEOADD,GEORADIUS
BoundHashOperationsRedis 哈希键绑定操作
BoundKeyOperationsRedis 键绑定操作
BoundListOperationsRedis 列键绑定操作
BoundSetOperationsRedis 设置键绑定操作
BoundValueOperationsRedis 字符串(或值)键绑定操作
BoundZSetOperationsRedis ZSet(或排序集)键绑定操作

Redis 支持 5 种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zset(sorted set:有序集合)。

下面代码演示使用 键绑定操作,即带有 Bound 的 API,因为演示尽量代码少,所以初始化直接绑定 key 了,否则多出一行代码去绑定 key。

String

string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。value 其实不仅是 String,也可以是数字,string 类型的值最大能存储 512MB。

常用命令:更多 string 命令

字符串常用操作

java
SET  key  value   // 存入字符串键值对
MSET  key  value [key value ...]   // 批量存储字符串键值对(不支持)
SETNX  key  value   // 存入一个不存在的字符串键值对
setIfAbsent
GET  key   // 获取一个字符串键值
MGET  key  [key ...]    // 批量获取字符串键值(不支持)
DEL  key  [key ...]   // 删除一个键
redisTemplate.delete(key)
EXPIRE  key  seconds   // 设置一个键的过期时间(秒)
username.set("可乐", 10, TimeUnit.SECONDS);
username.expire()

原子加减(原子性将整个操作视为一个整体)

java
INCR  key   // 将 key 中储存的数字值加1
DECR  key   // 将 key 中储存的数字值减1
INCRBY  key  increment   // 将 key 所储存的值加上 increment
DECRBY  key  decrement   // 将 key 所储存的值减去 decrement

计数器

java
INCR article:readcount:{文章id}
GET article:readcount:{文章id}

使用场景:文字的阅读量等。

代码

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisStringTest {

    // 它是线程安全的
    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test01() {
        // String 类型
        BoundValueOperations username = redisTemplate.boundValueOps("username");
        // 10 秒过期
        username.set("可乐", 10, TimeUnit.SECONDS);
    }


    @Test
    public void test02() {
        // String 类型
        BoundValueOperations username = redisTemplate.boundValueOps("username");
        // 设置不存在的字符串  true = 存入成功  false = 失败
        System.out.println(username.setIfAbsent("冰糖"));
    }

    @Test
    public void test03() {
        // String类型
        System.out.println(redisTemplate.delete("username"));
    }

    @Test
    public void test04() {
        // String 类型
        BoundValueOperations count = redisTemplate.boundValueOps("count");
        // 原子性累加 + 1
        count.increment();
    }


    /**
     * 秒杀 1 小时
     * 预热 100 库存
     */
    @Test
    public void test05() {
        BoundValueOperations stock = redisTemplate.boundValueOps("stock");
        // 预热 100 个库存  1 小时候过期
        stock.set(1, 1, TimeUnit.HOURS);
    }

    // 秒杀的接口
    @Test
    public void test06() {
        BoundValueOperations stock = redisTemplate.boundValueOps("stock");

        if (stock.decrement() < 0) {
            System.out.println("库存不足");
        } else {
            System.out.println("秒杀成功");
        }
    }

    @Test
    public void test08() {
        BoundValueOperations stock = redisTemplate.boundValueOps("user");
        User user = new User();
        user.setId(1);
        user.setUsername("可乐");
        stock.set(user);
    }
}

Hash

Redis hash 是一个键值(key => value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。(可以把 value 当做 Map)。

常用命令:更多 Hash 命令

java
HSET  key  field  value  // 存储一个哈希表 key 的键值
HSETNX  key  field  value  // 存储一个不存在的哈希表 key 的键值
HMSET  key  field  value [field value ...]  // 在一个哈希表 key 中存储多个键值对
支持根据map 批量存储
HGET  key  field  // 获取哈希表 key 对应的 field 键值
HMGET  key  field  [field ...]  // 批量获取哈希表 key 中多个 field 键值
不支持根据指定key获取map, 仅支持获取所有键或获取所有值.keys() .values()
HDEL  key  field  [field ...]  // 删除哈希表key中的field键值
HLEN  key  // 返回哈希表 key 中 field 的数量
HGETALL  key  // 返回哈希表 key 中所有的键值
HINCRBY  key  field  increment  // 为哈希表 key 中 field 键的值加上原子增量 increment

使用场景:存储部分变更数据,如用户登录信息、做购物车列表等。相对于 string 来说,对于对象存储,不用来回进行序列化,减少内存和 CPU 的消耗。

代码

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisHashTest {

    // 它是线程安全的
    @Autowired
    RedisTemplate redisTemplate;

    // hash
    @Test
    public void test01() {
        BoundHashOperations car = redisTemplate.boundHashOps("car");
        // set
        car.put("p_id", 1);
        car.putIfAbsent("p_total", 10);  // 不存在则存入
    }

    // 获取 map
    @Test
    public void test02() {
        BoundHashOperations car = redisTemplate.boundHashOps("car");
        // set
        System.out.println(car.entries());
    }

    // 获取 map 某一项
    @Test
    public void test03() {
        BoundHashOperations car = redisTemplate.boundHashOps("car");
        // set
        System.out.println(car.get("p_total"));
    }

    // 删除 map 某一项
    @Test
    public void test05() {
        BoundHashOperations car = redisTemplate.boundHashOps("car");

        // 删除整个 car
        //redisTemplate.delete("car");

        System.out.println(car.delete("p_id"));
    }

    // 获取 map 的长度
    @Test
    public void test06() {
        BoundHashOperations car = redisTemplate.boundHashOps("car");

        // 删除整个car
        //redisTemplate.delete("car");

        System.out.println(car.size());
    }

    // 对 map 中的某个 value 进行增量
    @Test
    public void test07() {
        BoundHashOperations car = redisTemplate.boundHashOps("car");

        car.increment("p_total", 1);
    }

    // 对 map 中的某个 value 进行增量
    @Test
    public void testcar(){
        BoundHashOperations car = redisTemplate.boundHashOps("car"+99);

        car.put("p_"+15,10);
        car.put("p_"+16,2);
        car.put("p_"+17,1);
    }

    // 增加购物车商品
    @Test
    public void testaddCar(){
        BoundHashOperations car = redisTemplate.boundHashOps("car"+99);

        // 增加购物车商品
        // car.put("p_"+18,1);

        // 增加购物车数量
        //car.increment("p_"+15,1);

        // 商品总数
        //System.out.println(car.size());

        // 删除商品
        // car.delete("p_"+15);

        // 获得所有的 key
        System.out.println(car.keys());

        // 获得所有的 value
        //car.values()
    }
}

List

Redis List 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。(相当于 Java 的 LinkedList(链表)和 ArrayList,允许重复。

常用命令:更多 List 命令

java
LPUSH  key  value [value ...]  // 将一个或多个值value插入到key列表的表头(最左边)
RPUSH  key  value [value ...]  // 将一个或多个值value插入到key列表的表尾(最右边)
LPOP  key  // 移除并返回 key 列表的头元素
RPOP  key  // 移除并返回 key 列表的尾元素
LRANGE  key  start  stop  // 返回列表key中指定区间内的元素,区间以偏移量start和stop指定

应用场景:Redis List 的应用场景非常多,也是 Redis 最重要的数据结构之一,比如微博的关注列表,粉丝列表、热搜、top 榜单等都可以用 Redis 的 list 结构来实现。

代码

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisListTest {

    // 它是线程安全的
    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test01() {
        BoundListOperations list = redisTemplate.boundListOps("list");
        for (int i = 0; i < 10; i++) {
            // 往尾部插入
            list.rightPush(i);
            // 往头部插入
            // list.leftPush()
            // 往尾部批量插入
            // list.rightPushAll(1,2,3,4);
            // list.leftPushAll(1,2,3,4);
        }
    }

    @Test
    public void test02() {
        BoundListOperations list = redisTemplate.boundListOps("list");
        // 范围获取
        // System.out.println(list.range(0, list.size()));
        // 根据下标获取某一个
        System.out.println(list.index(5));
    }

    @Test
    public void test03() {
        BoundListOperations list = redisTemplate.boundListOps("list");

        // 从头部删除指定数量
        // list.leftPop(2);
        // 从尾部删除指定数量
        //list.rightPop()

        // 根据值进行删除
        //list.remove(1,5);

        // 保留指定范围, 其余的都删掉
        //list.trim(1,2);

        // 数量
        // list.size();

        // 往指定的索引 添加/修改值
        list.set(1, 10);
    }
}

Set

Redis Set 是 string 类型的无序集合。不允许重复。

Set 常用操作

java
SADD  key  member  [member ...]  // 往集合 key 中存入元素,元素存在则忽略,若 key 不存在则新建
// Java
add(V... values);

SREM  key  member  [member ...]  // 从集合 key 中删除元素
// Java
remove(Object... values);

SMEMBERS  key  // 获取集合 key 中所有元素
// Java
members();

SCARD  key  // 获取集合 key 的元素个数
// Java
size();

SISMEMBER  key  member  // 判断 member 元素是否存在于集合 key 中
// Java
isMember(Object o);

SRANDMEMBER  key  [count]  // 从集合 key 中选出 count 个随机元素,元素不从 key 中删除
// Java
randomMembers(long count); distinctRandomMembers(long count);

SPOP  key  [count]  // 从集合 key 中选出 count 个元素,元素从 key 中删除
// Java
pop();

Set 运算操作

java
SINTER  key  [key ...]   // 交集运算,set1: 123456,set2: 123789,交集:123
// Java
intersect(K key);

SINTERSTORE  destination  key  [key ..]  // 将交集结果存入新集合 destination中
// Java
intersectAndStore(K key, K destKey);

SUNION  key  [key ..]   // 并集运算,set1: 123456,set2: 123789,并集:123456789
// Java
union(K key);

SUNIONSTORE  destination  key  [key ...]  // 将并集结果存入新集合 destination 中
// Java
unionAndStore(K key, K destKey);

SDIFF  key  [key ...]   // 差集运算
// Java
diff(K key);

SDIFFSTORE  destination  key  [key ...]  // 将差集结果存入新集合 destination 中
// Java
diffAndStore(K keys, K destKey);

应用场景:抽奖、共同关注。

代码

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisSetTest {

    // 它是线程安全的
    @Autowired
    RedisTemplate redisTemplate;

    // 添加
    @Test
    public void testAdd() {
        BoundSetOperations set = redisTemplate.boundSetOps("set");

        set.add(1, 2, 3, 4, 5, 6);
    }

    // 获取
    @Test
    public void testGet() {
        BoundSetOperations set = redisTemplate.boundSetOps("set");

        // 获取整个 set
        System.out.println(set.members());
        // 获取个数
        System.out.println(set.size());
        // 根据某个元素判断是否在 set 中
        System.out.println(set.isMember(1));

        // 根据count获取随机元素 不带删除
        System.out.println(set.randomMembers(2));
        System.out.println(set.randomMembers(2));

        // 带删除
        // System.out.println(set.pop());

        SetOperations setOperations = redisTemplate.opsForSet();
        System.out.println(setOperations.pop("set", 2));
    }

    // Set 运算操作
    @Test
    public void testAdd2() {
        BoundSetOperations set = redisTemplate.boundSetOps("set2");

        set.add(1, 2, 3, 7, 8, 9);
    }

    // 交集
    @Test
    public void testCacl1() {
        BoundSetOperations set = redisTemplate.boundSetOps("set");
        set.intersectAndStore("set2", "set3");

        BoundSetOperations set3 = redisTemplate.boundSetOps("set3");
        System.out.println(set3.members());
    }

    // 并集
    @Test
    public void testCacl2() {
        BoundSetOperations set = redisTemplate.boundSetOps("set");
        System.out.println(set.union("set2"));
    }

    // 差集
    @Test
    public void testCacl3() {
        BoundSetOperations set = redisTemplate.boundSetOps("set2");
        System.out.println(set.diff("set"));
    }
}

Sorted Set

Redis Sorted Set(Zset)与 Set 类似,区别是 Set 是无序的,Sorted Set 是有序的并且不重复的集合列表,可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。

常用命令:更多 Sorted Set 命令

ZSet 常用操作:

java
ZADD key score member [[score member]…]  // 往有序集合 key 中加入带分值元素
ZREM key member [member …]  // 从有序集合 key 中删除元素
// Java
Long remove(Object... values);
Long removeRange(long start, long end); // 根据顺序范围
Long removeRangeByScore(double min, double max); // 根据分数范围

ZSCORE key member   // 返回有序集合 key 中元素 member 的分值
// Java
List<Double> score(Object... o);
Double score(Object o);

ZINCRBY key increment member  // 为有序集合 key 中元素 member 的分值加上 increment
// Java
Double incrementScore(V value, double delta); // 原子添加

ZCARD key  // 返回有序集合 key 中元素个数
// Java
Long size();

ZRANGE key start stop [WITHSCORES]  // 正序获取有序集合 key 从 start 下标到 stop 下标的元素
// Java
Set<V> range(long start, long end); // 根据顺序范围
Set<V> rangeByScore(double min, double max); // 根据分数范围

ZREVRANGE key start stop [WITHSCORES]  //倒序获取有序集合 key 从 start 下标到 stop 下标的元素
// Java
Set<V> reverseRange(long start, long end);
Set<V> reverseRangeByScore(double min, double max);

ZSet 集合操作 和 Set 一样差不多

java
ZUNIONSTORE destkey numkeys key [key ...]   // 并集计算
// Java
Set<V> union(K otherKey)

ZINTERSTORE destkey numkeys key [key …]  // 交集计算
// Java
Set<V> intersect(K otherKey)

应用场景:排行榜,如微博热搜。

代码

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisZSetTest {

    // 它是线程安全的
    @Autowired
    RedisTemplate redisTemplate;

    // 添加
    @Test
    public void testAdd() {
        BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");
        zset.add("张三", 100);

        Set<ZSetOperations.TypedTuple> tupleSet = new HashSet<>();
        tupleSet.add(ZSetOperations.TypedTuple.of("李四", Double.valueOf(60)));
        tupleSet.add(ZSetOperations.TypedTuple.of("王五", Double.valueOf(80)));
        tupleSet.add(ZSetOperations.TypedTuple.of("赵六", Double.valueOf(70)));

        zset.add(tupleSet);
    }

    @Test
    public void testGet() {
        BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");
        // 根据元素获取分数
        System.out.println(zset.score("张三"));
        // 个数
        System.out.println(zset.size());
        // 原子添加并且返回
        System.out.println(zset.incrementScore("李四", 1));
    }

    @Test
    public void testGetRange() {
        BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");
        // 范围升序查询

        // 升序顺序范围
        System.out.println(zset.range(0, 2));
        // 分数范围
        System.out.println(zset.rangeByScore(60, 90));
        // 带分数
        System.out.println(zset.rangeWithScores(0, 2));
        System.out.println(zset.rangeByScoreWithScores(60, 90));

        System.out.println("--------------------");

        // 范围降序查询
        // 降序顺序范围
        System.out.println(zset.reverseRange(0, 2));
        // 降序分数范围
        System.out.println(zset.reverseRangeByScore(60, 90));
        // 降序带分数
        System.out.println(zset.reverseRangeWithScores(0, 2));
        System.out.println(zset.reverseRangeByScoreWithScores(60, 90));
    }

    @Test
    public void testDel() {
        BoundZSetOperations zset = redisTemplate.boundZSetOps("zset");

        System.out.println(zset.remove("张三"));

        // 按照升序范围移除
        zset.removeRange(0, 1);
        zset.removeRangeByScore(100, 90);
    }


    // 统计每日新闻 top10
    @Test
    public void test() {
        BoundZSetOperations zset = redisTemplate.boundZSetOps("news20000101");

        zset.incrementScore("守护香港", 1);

        System.out.println(zset.reverseRangeWithScores(0, 9));

        redisTemplate.convertAndSend("hello!", "world");
    }
}

Redis 事务

简单介绍下 Redis 的几个事务命令:

Redis 事务四大指令: MULTI(开启事务)、EXEC(提交)、DISCARD(回滚)、WATCH。这四个指令构成了 Redis 事务处理的基础。

  1. MULTI 用来组装一个事务
  2. EXEC 用来执行一个事务
  3. DISCARD 用来取消一个事务
  4. WATCH 类似于乐观锁机制里的版本号。被 WATCH 的 key 如果在事务执行过程中被并发修改,则事务失败。需要重试或取消。

SpringDataRedis 提供了 2 种事务的支持方式:

execute(SessionCallback session) 方法

java
<T> T execute(SessionCallback<T> session);

SessionCallback 包含了一个回调函数 execute(RedisOperations operations),在这个函数里实现以上的操作,就可以保证事务的正常使用。

通过 multi(开启事务),exec(提交事务)和 discard(回滚事务)命令控制事务。

java
public void testRedisTx1() {
    List<Object> r = template.execute(new SessionCallback<List<Object>>() {

        @Override
        public List<Object> execute(RedisOperations operations) throws DataAccessException {
            operations.multi();
            operations.opsForValue().set("hxm", "9999");
            // 此处打印 null,因为事务还没真正执行
            System.out.println(operations.opsForValue().get("hxm"));
            return operations.exec(); // 提交事务
        }
    });

    System.out.println(r);
}

@Transactional 的支持

另一种实现事务的是 @Transactional 注解,这种方法是把事务交由给 Spring 事务管理器进行自动管理。使用这种方法之前,跟 JDBC 事务一样,要先注入事务管理器,如果工程中已经有配置了事务管理器,就可以复用这个事务管理器,不用另外进行配置。另外,需要注意的是,跟第一种事务操作方法不一样的地方就是 RedisTemplate 的 setEnableTransactionSupport(boolean enableTransactionSupport) 方法要 set 为 true,此处贴出官方的配置框架:

java
@Configuration
@EnableTransactionManagement
public class RedisTxContextConfiguration {

    @Bean
    public StringRedisTemplate redisTemplate() {
        StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
        // explicitly enable transaction support
        template.setEnableTransactionSupport(true);// 此处必须设置为 true,不然没法实现事务管理
        return template;
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        // jedis || Lettuce
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws SQLException {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public DataSource dataSource() throws SQLException {
        // ...
    }
}

这种方法的使用方法比较简单,就在要使用事务的方法注解 @Transactional,这跟 JDBC 事务使用是一样的,这样就不用手工的执行 multi、exec 方法了,这些事务控制方法会由 Spring 事务管理器自动完成。实例如下:

java
@Transactional
public void testRedisTx2() {

    template.opsForValue().set("name", "xushu");
    System.out.println(tranRedisTemplate.keys("*"));
    System.out.println(template.opsForValue().get("name"));  // 此处打印为 null
}

然而,这种便利的使用方法有局限性,就是不支持只读操作,如果执行 get 之类的操作,将会返回 null,所以使用的时候要多加注意。

两种方法的对比

execute(SessionCallback session) 方法

  • 事务代码块的范围灵活可控

@Transactional 注解方法

  • 使用方便,代码比较优雅
  • 不够灵活,事务控制范围是整个方法

综合了以上的对比,两种方法各有优缺点,但个人更偏向于使用 execute 方法。如果使用 @Transactiona l 这种注解式方法,有个建议是初始化两个 RedisTemplate,一个支持事务的,一个不支持事务的,即 enableTransactionSupport 一个设为 true,一个设为 false(默认是 false)。不然,如果用支持事务的 RedisTemplate 来进行非事务性操作时,有些地方要注意,比如要手工的关闭连接等,不然是会踩坑的。

Redis 消息传递(发布/订阅)

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(PUBLISH)发送消息,订阅者(SUBSCRIBE\PSUBSCRIBE)接收消息。下图展示了三个频道 news-word1、news-message、sms-message,以及订阅这个频道的三个客户端之间的关系:

image-20221211153905971

当有新消息通过发布者发送给频道时,这个消息就会被发送给订阅它的客户端:Redis 客户端可以订阅任意数量的频道。

在 Java 里,发布者的实现非常简单,直接调用 RedisTemplate 往某个频道发送消息即可。

难的是怎么发给订阅者,因为无法知道谁订阅了这个频道,所以我们不仅需要一个订阅者,还需要一个将订阅者和发布者关联的监听者。

监听者定义在 RedisConfig 里,在定义的过程,绑定一个或者多个频道和给监听者,当发布者发布消息时,监听者收到消息,马上往频道推送,

而订阅者需要我们自定义一个类,实现(implement)官方提供的监听者,这样这个订阅者就绑定在监听者。

发布消息 Publishing

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisMsgTest {
    // 它是线程安全的
    @Autowired
    RedisTemplate redisTemplate;

    // 发布者
    @Test
    public void test02() {
        redisTemplate.convertAndSend("log","1");

    }
}

接收消息 Subscribing

订阅者

java
@Component
public class Subscriber implements MessageListener {

    @Autowired
    RedisTemplate redisTemplate;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 消息处理 日志记录 积分 邮件发送 ...
        // Message 封装消息管道和消息内容
        // 管道
        byte[] channel = message.getChannel();     // key
        byte[] body = message.getBody();           // value


        Object channelInfo = redisTemplate.getKeySerializer().deserialize(channel);
        Object bodyInfo = redisTemplate.getValueSerializer().deserialize(body);

        // 消息体
        System.out.println("管道:"+channelInfo+"消息内容:"+bodyInfo);
    }
}

监听器

java
@Configuration
@ComponentScan("com.tuling")
public class RedisConfig {

    // ...
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {

        // 设置单机redis远程服务地址
        RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);

        // 连接池相关配置 (过期)
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接数(根据并发量估算)
        jedisPoolConfig.setMaxTotal(100);
        // 最大连接数= 最大空闲数
        jedisPoolConfig.setMaxIdle(100);
        // 最小空闲连接数
        jedisPoolConfig.setMinIdle(10);
        // 最长等待时间 0无限等待 解决线程堆积问题 最好设置
        jedisPoolConfig.setMaxWaitMillis(2000);

        // 在 SDR2.0+ 建议使用 JedisClientConfiguration 来设置连接池
        // 默认设置了读取超时和连接超时为2秒
        JedisClientConfiguration clientConfiguration =
            JedisClientConfiguration.builder()
            .usePooling()
            .poolConfig(jedisPoolConfig).build();

        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration, clientConfiguration);
        return jedisConnectionFactory;
    }

    // 配置在订阅者
    // 消息监听者容器负责:管理线程池、消息分发,分发给对应管道的监听者
    // 可以动态设置订阅者、和管道,通过这个特性在不重启服务器的情况下动态设置订阅者、和管道
    @Bean
    RedisMessageListenerContainer container(MessageListenerAdapter messageListenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        // 设置连接工厂
        container.setConnectionFactory(redisConnectionFactory());
        // 设置监听者绑定的管道
        List<Topic> topicList = new ArrayList<>();
        topicList.add(new PatternTopic("log"));

        // 第一个参数:监听者,第二个参数:绑定管道
        container.addMessageListener(messageListenerAdapter, topicList);
        return container;
    }

    /**
     * 消息侦听器适配器,能将消息委托给目标侦听器方法
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapter(MessageListener listener) {
        return new MessageListenerAdapter(listener);
    }
}

Redis 发布订阅和 MQ 的区别

可靠性

Redis 没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将丢失,不会存在内存中。

RabbitMQ 具有消息消费确认机制,如果发布一条消息,还没有消费者消费该队列,那么这条消息将一直存放在队列中,直到有消费者消费了该条消息,以此可以保证消息的可靠消费。

实时性

Redis 实时性高,Redis 作为高效的缓存服务器,所有数据都存在在服务器中,所以它具有更高的实时性。

消费者负载均衡

RabbitMQ 队列可以被多个消费者同时监控消费,但是每一条消息只能被消费一次,由于 RabbitMQ 的消费确认机制,因此它能够根据消费者的消费能力而调整它的负载。

Redis 发布订阅模式,一个队列可以被多个消费者同时订阅,当有消息到达时,会将该消息依次发送给每个订阅者。

持久性

Redis 的持久化是针对于整个 Redis 缓存的内容,它有 RDB 和 AOF 两种持久化方式,可以将整个 Redis 实例持久化到磁盘,以此来做数据备份,防止异常情况下导致数据丢失。

RabbitMQ 队列,消息都可以选择性持久化,持久化粒度更小,更灵活。

队列监控

RabbitMQ 实现了后台监控平台,可以在该平台上看到所有创建的队列的详细情况,良好的后台管理平台可以方面我们更好的使用。

Redis 没有所谓的监控平台。

总结

Redis 轻量级,低延迟,高并发,低可靠性。

RbbitMQ 重量级,高可靠,异步,不保证实时。

RbbitMQ 是一个专门的 AMQP 协议队列,他的优势就在于提供可靠的队列服务,并且可做到异步,而 Redis 主要是用于缓存的,Redis 的发布订阅模块,可用于实现及时性,且可靠性低的功能。

Redis Repository

使用 Redis 存储库可让您在 Redis 哈希中无缝转换和存储域对象、应用自定义映射策略并使用二级索引。

好处:根据各种属性作为查询条件、更加面向对象的方式去 Redis。

限制:只能针对对象 POJO(实体类)转哈希操作。

image-20221211155426717

java
@Bean
public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    template.setEnableTransactionSupport(false);
    return template;
}

启动 Repository

在配置类上添加 @EnableRedisRepositories,并扫描对应的 Mapper 层。

java
@Configuration
@ComponentScan("com.tuling")
@EnableRedisRepositories("cn.youngkbt.repository")
public class RedisConfig {

    // 1. redis的连接工厂
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {

        // 设置单机redis远程服务地址
        RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration("127.0.0.1", 6379);

        // 连接池相关配置 (过期)
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大连接数(根据并发量估算)
        jedisPoolConfig.setMaxTotal(100);
        // 最大连接数= 最大空闲数
        jedisPoolConfig.setMaxIdle(100);
        // 最小空闲连接数
        jedisPoolConfig.setMinIdle(10);
        // 最长等待时间 0无限等待 解决线程堆积问题 最好设置
        jedisPoolConfig.setMaxWaitMillis(2000);

        // 在SDR2.0+ 建议使用JedisClientConfiguration 来设置连接池
        // 默认设置了读取超时和连接超时为2秒
        JedisClientConfiguration clientConfiguration =
                JedisClientConfiguration.builder()
                        .usePooling()
                        .poolConfig(jedisPoolConfig).build();


        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration, clientConfiguration);
        return jedisConnectionFactory;
    }

    // 2. redis模板类(工具类)
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // StringRedisSerializer 只能传入String类型的值(存入redis之前就要转换成String)
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        // 改成Jackson2JsonRedisSerializer   推荐
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
        return redisTemplate;
    }
}

添加 Redis Hash 的操作实体

java
@RedisHash("people")
public class Person {

    @Id String id;
    String firstname;
    String lastname;
    Address address;
}

添加一个 Mapper 继承 CrudRepository 或者 PagingAndSortingRepository

java
/***
 *
 * CrudRepository 提供基本 CRUD
 * PagingAndSortingRepository  在基本 CRUD 还提供分页还排序
 *
 * 实现机制:JDK 动态代理,调用对应 Jedis 命令
 */
public interface UserRepository extends PagingAndSortingRepository<User,Integer> {

}

操作

java
@SpringJUnitConfig(classes = RedisConfig.class)
public class RedisRepositoryTest {

    @Autowired
    UserRepository repository;

    @Test
    public void testAdd(){
        User user = new User();
        user.setId(1);
        user.setUsername("可乐");
        repository.save(user); // 执行自带的方法
    }

    @Test
    public void testSelect(){
        System.out.println(repository.findById(1));
    }

    @Test
    public void testDel(){
        repository.deleteById(1);
    }
}

这样 Redis 就有了 User 对象的信息。

可以理解为此时把 Redis 当作了一个关系型数据库存储对象信息。

RedisTemplate 原理

其实就在 Jedis 的基础上进行了封装,不管是连接池、还是执行命令,最终都是通过 Jedis,只不过通过 RedisTemplate 消除了样板代码。

  1. 当通过 RedisTemplate 执行任何命令的 API 的时候,都会执行 execute 方法
  2. execute 在里面就创建 JedisConnetion,就是通过 Jedis 原生 API 来进行创建
  3. 当使用连接池就会从 Jedis 的 pool 获得 Jedis 对象,如果没有使用连接池就是 new 一个 Jedis
  4. 所以当执行对应的命令的 API,其实就是调用 Jedis 对应的 API
  5. 最终调用 jedis.close 关闭连接

总结,RedisTemplate 就是通过封装 Jedis 相关 API,来消除我们操作 Jedis 的时候这些重复的样板代码。

SpringBoot 的 SpringDataRedis

前面也说过,先介绍 Spring 的 Redis,再介绍 Spring Boot,两者的区别在于配置,那些 API 都是一样的。

SpringBoot 会通过自动配置类帮我们配置 RedisTemplate 和 RedisConnectionFactory,部分源码:

java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    // ...
}

但是 RedisTemplate 它使用的是默认的序列化器,所以需要自己定义 RedisTemplate 覆盖。

这里提供一个我目前在用的配置文件(没有消息订阅配置,如果需要,和上面的配置一样):

java
@AutoConfiguration
@ConditionalOnClass(RedisAutoConfiguration.class)
public class RedisTemplateConfig {

    private final RedisConnectionFactory redisConnectionFactory;

    public RedisTemplateConfig(RedisConnectionFactory redisConnectionFactory) {
        this.redisConnectionFactory = redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplateString() {
        return initRedisTemplate(new StringRedisTemplate());
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        return initRedisTemplate(new RedisTemplate<>());
    }

    public <T> RedisTemplate<String, T> initRedisTemplate(RedisTemplate<String, T> redisTemplate) {

        redisTemplate.setConnectionFactory(redisConnectionFactory);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

        // 解决 Redis 无法存入 LocalDateTime 等 JDK8 的时间类
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        /*
         * 新增 LocalDateTime 序列化、反序列化规则
         */
        javaTimeModule
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePatternPlus.NORM_DATETIME_FORMATTER)) // yyyy-MM-dd HH:mm:ss
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME)) // HH:mm:ss
                .addSerializer(Instant.class, InstantSerializer.INSTANCE) // Instant 类型序列化
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePatternPlus.NORM_DATETIME_FORMATTER)) // yyyy-MM-dd HH:mm:ss
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)) // yyyy-MM-dd
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME)) // HH:mm:ss
                .addDeserializer(Instant.class, InstantDeserializer.INSTANT);// Instant 反序列化

        objectMapper.registerModules(javaTimeModule);

        // 使用 Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);

        /*
         * 设置 value 的序列化规则和 key 的序列化规则
         * RedisTemplate 默认序列化使用的 jdkSerializable, 存储到 Redis 会变成二进制字节码,有风险!
         * 所以官网建议转成其他序列化
         */
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

}

只需要配置这个类,就可以直接使用 RedisTempalte 了。

SpringBoot 的 RedisConnectionFactory 默认使用的是 Luttuse,性能比 Jedis 好些。

前面介绍的都是 Jedis,如果想使用 Jedis 可以在 Maven 依赖排除 Luttuse,引入 Jedis 依赖即可:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring‐boot‐starter‐data‐redis</artifactId>
</dependency>

使用 @Resource 自动注入!,因为使用 @Autowire 注意会导致无法启动项目。

最近更新