SpringBoot2.6.x整合Redis(RedisTemplate操作五大常用数据类型)

示例代码:

官方文档:Spring Data Redis

简介

之前一篇文章SpringBoot2.6.x缓存介绍以及整合Redis仅介绍了如何使用Redis作为缓存。

如果想要直接操作 RedisSpringBoot 提供了 RedisTemplate 类用来直接操作数据

配置

POM依赖

spring-boot-starter-data-redis 使用的Redis客户端已经从 jedis 更改为 lettuce

<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- Fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.78</version>
</dependency>

yml配置文件

根据Redis服务的模式不同,对应的配置文件也不同

单机模式

spring:
  redis:
    ## 单机模式
    host: 127.0.0.1 # 地址
    port: 6379 # 端口
    # 通用配置
    username: # 用户名
    password: 123 # 密码
    database: 0 # 指定数据库序号
    ssl: false # 是否启用SSL
    connect-timeout: 1000 # 连接超时时间(毫秒)
    timeout: 1000 # 操作超时时间(毫秒)
    client-name: # 客户端名称(不知道干嘛用的)
    client-type: lettuce # 驱动类型
    # 连接池配置
    lettuce:
      pool:
        min-idle: 1 # 最小空闲连接(默认0)
        max-idle: 8 # 最大空闲连接(默认8)
        max-active: 16 # 最大连接数(默认8,使用负值表示没有限制)
        max-wait: -1ms # 最大阻塞等待时间(默认-1,负数表示没限制)

哨兵模式(主从复制)

spring:
  redis:
    ## 哨兵模式(主从复制)
    sentinel:
      master: master # 哨兵的sentinel.conf配置文件中的主节点名称
      password: 123 # 哨兵的密码
      nodes: 192.168.220.101:26379,192.168.220.102:26379,192.168.220.103:26379 # 哨兵节点
    # 通用配置
    username: # 用户名
    password: 123 # 密码
    database: 0 # 指定数据库序号
    ssl: false # 是否启用SSL
    connect-timeout: 1000 # 连接超时时间(毫秒)
    timeout: 1000 # 操作超时时间(毫秒)
    client-name: # 客户端名称(不知道干嘛用的)
    client-type: lettuce # 驱动类型
    # 连接池配置
    lettuce:
      pool:
        min-idle: 1 # 最小空闲连接(默认0)
        max-idle: 8 # 最大空闲连接(默认8)
        max-active: 16 # 最大连接数(默认8,使用负值表示没有限制)
        max-wait: -1ms # 最大阻塞等待时间(默认-1,负数表示没限制)

集群模式

spring:
  redis:
    ## 集群模式
    cluster:
      nodes: 192.168.220.101:6379,192.168.220.102:6379,192.168.220.103:6379,192.168.220.101:6380,192.168.220.102:6380,192.168.220.103:6380
    # 通用配置
    username: # 用户名
    password: 123 # 密码
    database: 0 # 指定数据库序号
    ssl: false # 是否启用SSL
    connect-timeout: 1000 # 连接超时时间(毫秒)
    timeout: 1000 # 操作超时时间(毫秒)
    client-name: # 客户端名称(不知道干嘛用的)
    client-type: lettuce # 驱动类型
    # 连接池配置
    lettuce:
      pool:
        min-idle: 1 # 最小空闲连接(默认0)
        max-idle: 8 # 最大空闲连接(默认8)
        max-active: 16 # 最大连接数(默认8,使用负值表示没有限制)
        max-wait: -1ms # 最大阻塞等待时间(默认-1,负数表示没限制)
      cluster:
        refresh:
          period: 1000 # 集群模式!需要设置群集拓扑刷新周期(毫秒)

RedisConfig配置文件自定义 RedisTemplate

默认情况下, org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration 会生成一个默认的 RedisTemplate ,且序列化时使用 JdkSerializationRedisSerializer ,详见 org.springframework.data.redis.core.RedisTemplate

然而 Jdk 的序列化在Redis中可读性不好,我们需要自定义 Json 格式的序列化转换,这里我们使用阿里巴巴的 Fastjson

RedisAutoConfiguration 同时还提供了一个 StringRedisTemplate ,详见 org.springframework.data.redis.core.StringRedisTemplate

import java.nio.charset.StandardCharsets;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
/**
 * Redis操作配置
 */
@Configuration
public class RedisTemplateConfig {
    /**
     * 自定义RedisTemplate,使用fastjson格式化value
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // key序列化
        template.setKeySerializer(new StringRedisSerializer(StandardCharsets.UTF_8));
        // value序列化(这里使用阿里巴巴的Fastjson格式化工具)
        template.setValueSerializer(new GenericFastJsonRedisSerializer());
        // hash序列化
        template.setHashKeySerializer(new StringRedisSerializer(StandardCharsets.UTF_8));
        template.setHashValueSerializer(new GenericFastJsonRedisSerializer());
        // 启用事务支持
        template.setEnableTransactionSupport(true);
        return template;
    }
}

使用

一般情况下,有如下两个常用对象

/**
 * RedisTemplate操作通用对象
 */
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
 * 操作String类型的对象
 */
@Autowired
private StringRedisTemplate stringRedisTemplate;

RedisTemplate 又可以通过 opsForXxx 生成对应的类型的操作对象,例如

// RedisTemplate 可以获取对应数据类型的 XxxOperations
ValueOperations<String, Object> objectValueOperations = redisTemplate.opsForValue();
ListOperations<String, Object> objectListOperations = redisTemplate.opsForList();
SetOperations<String, Object> objectSetOperations = redisTemplate.opsForSet();
HashOperations<String, String, Object> objectHashOperations = redisTemplate.opsForHash();
ZSetOperations<String, Object> objectZSetOperations = redisTemplate.opsForZSet();

简单点,若后面要获取 XxxOperations 可以直接使用 @Resource 注解获取,且获取时可以将 Object 对象根据值类型换成具体的对象。注意,值是 String 类型时 name 需要使用 stringRedisTemplate

/**
 * 值是String类型的字符串
 *
 * 自定义的RedisTemplate在格式化String时会多出引号
 *
 * 需要使用Spring内置的StringRedisTemplate
 */
@Resource(name = "stringRedisTemplate")
private ValueOperations<String, String> stringValueOperations;
/**
 * 值是对象类型的字符串
 *
 * 使用自定义的RedisTemplate,将值格式化成JSON
 */
@Resource(name = "redisTemplate")
private ValueOperations<String, User> userValueOperations;
/**
 * 值是数字类型的字符串
 *
 * 自定义的RedisTemplate可以格式化Integer
 */
@Resource(name = "redisTemplate")
private ValueOperations<String, Integer> integerValueOperations;

RedisTemplate 基础操作

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
/**
 * Keys 键相关
 */
@SpringBootTest
public class BaseOperationsTest {
    /**
     * RedisTemplate操作通用对象
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 操作String类型的对象
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Test
    void test() {
        // RedisTemplate 可以获取对应数据类型的 XxxOperations
        ValueOperations<String, Object> objectValueOperations = redisTemplate.opsForValue();
        ListOperations<String, Object> objectListOperations = redisTemplate.opsForList();
        SetOperations<String, Object> objectSetOperations = redisTemplate.opsForSet();
        HashOperations<String, String, Object> objectHashOperations = redisTemplate.opsForHash();
        ZSetOperations<String, Object> objectZSetOperations = redisTemplate.opsForZSet();
        // 准备数据
        ValueOperations<String, String> stringValueOperations = stringRedisTemplate.opsForValue();
        stringValueOperations.set("key1", "hello1");
        stringValueOperations.set("key2", "hello2");
        stringValueOperations.set("key3", "hello3");
        stringValueOperations.set("key4", "hello4");
        // KEYS 查找键
        Set<String> keys = redisTemplate.keys("*");
        assertNotNull(keys);
        assertEquals(4, keys.size());
        // EXISTS 键是否存在
        Boolean hasKey = redisTemplate.hasKey("key1");
        assertTrue(hasKey);
        Long existingKeys = redisTemplate.countExistingKeys(Arrays.asList("key1", "key2"));
        assertEquals(2, existingKeys);
        // TYPE 查看键的类型
        assertEquals(DataType.STRING, redisTemplate.type("key1"));
        // DEL 删除键
        Boolean deleteFalse = redisTemplate.delete("key");
        assertFalse(deleteFalse);
        Boolean deleteSuccess = redisTemplate.delete("key1");
        assertTrue(deleteSuccess);
        Long batchDelete = redisTemplate.delete(Arrays.asList("key", "key2"));
        assertEquals(1, batchDelete);
        // UNLINK 删除键(异步)
        Boolean unlink = redisTemplate.unlink("key3");
        assertTrue(unlink);
        Long batchUnlink = redisTemplate.unlink(Arrays.asList("key1", "key2"));
        assertEquals(0, batchUnlink);
        Long expire = redisTemplate.getExpire("key4");
        // TTL 查看键剩余生成秒
        assertEquals(-1, expire);
        // EXPIRE 设置键到期秒
        redisTemplate.expire("key4", 10, TimeUnit.SECONDS);
        redisTemplate.expire("key4", Duration.ofSeconds(10));
        assertEquals(10, redisTemplate.getExpire("key4"));
        // PERSIST key 设置键永不过期
        redisTemplate.persist("key4");
        assertEquals(-1, redisTemplate.getExpire("key4"));
        // 删除数据
        assertTrue(redisTemplate.delete("key4"));
    }
}

ValueOperations String 字符串

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import com.maxqiu.demo.entity.User;
/**
 * String 字符串
 */
@SpringBootTest
public class StringOperationsTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 值是String类型的字符串
     *
     * 自定义的RedisTemplate在格式化String时会多出引号
     *
     * 需要使用Spring内置的StringRedisTemplate
     */
    @Resource(name = "stringRedisTemplate")
    private ValueOperations<String, String> stringValueOperations;
    /**
     * 值是对象类型的字符串
     *
     * 使用自定义的RedisTemplate,将值格式化成JSON
     */
    @Resource(name = "redisTemplate")
    private ValueOperations<String, User> userValueOperations;
    /**
     * 值是数字类型的字符串
     *
     * 自定义的RedisTemplate可以格式化Integer
     */
    @Resource(name = "redisTemplate")
    private ValueOperations<String, Integer> integerValueOperations;
    /**
     * GET
     */
    @Test
    @Order(1)
    void get() {
        // 获取不存在的键
        assertNull(stringValueOperations.get("nonexistent"));
        // 获取String类型的数据
        stringValueOperations.set("key", "hello");
        assertEquals("hello", stringValueOperations.get("key"));
        // 获取对象类型的数据
        User user = new User(1, "tom", new BigDecimal(18));
        userValueOperations.set("user", user);
        assertEquals(user, userValueOperations.get("user"));
        integerValueOperations.set("num", 1);
        assertEquals(1, integerValueOperations.get("num"));
        redisTemplate.delete(Arrays.asList("user", "key", "num"));
    }
    /**
     * SET
     * 
     * SETEX
     * 
     * SETNX
     * 
     * GETSET
     */
    @Test
    @Order(2)
    void set() {
        /// SET 设置键与值
        // 简单设置值
        stringValueOperations.set("key1", "hello");
        assertEquals("hello", stringValueOperations.get("key1"));
        // 如果键存在才设置值
        Boolean flag1 = stringValueOperations.setIfPresent("key1", "world");
        assertTrue(flag1);
        assertEquals("world", stringValueOperations.get("key1"));
        Boolean flag2 = stringValueOperations.setIfPresent("key2", "world");
        assertFalse(flag2);
        assertNull(stringValueOperations.get("key2"));
        // 同上,并设置超时时间
        Boolean flag3 = stringValueOperations.setIfPresent("key1", "hello", Duration.ofSeconds(10));
        assertTrue(flag3);
        // stringOperations.setIfPresent("key2", "world", 10, TimeUnit.SECONDS);
        assertEquals(10, redisTemplate.getExpire("key1"));
        // SETEX 设置键与值并设置超时时间
        stringValueOperations.set("key2", "world", Duration.ofSeconds(10));
        // stringOperations.set("key2", "world", 10, TimeUnit.SECONDS);
        assertEquals(10, redisTemplate.getExpire("key2"));
        // SETNX 键不存在则设置键与值
        Boolean flag4 = stringValueOperations.setIfAbsent("key3", "hello");
        assertTrue(flag4);
        assertEquals("hello", stringValueOperations.get("key3"));
        // SET ... NX 键不存在则设置键与值(同时设置时间)
        Boolean flag5 = stringValueOperations.setIfAbsent("key4", "world", Duration.ofSeconds(10));
        // stringOperations.setIfAbsent("key4", "world", 10, TimeUnit.SECONDS);
        assertTrue(flag5);
        assertEquals("world", stringValueOperations.get("key4"));
        // GETSET 获取值并设置一个新值
        String s = stringValueOperations.getAndSet("key4", "redis");
        assertEquals(s, "world");
        assertEquals("redis", stringValueOperations.get("key4"));
        assertEquals(4, redisTemplate.delete(Arrays.asList("key1", "key2", "key3", "key4")));
    }
    @Test
    @Order(3)
    void other() {
        // APPEND 拼接字符串
        Integer append1 = stringValueOperations.append("key", "hello");
        assertEquals(5, append1);
        Integer append2 = stringValueOperations.append("key", " world");
        assertEquals(11, append2);
        // STRLEN 获取字符串长度
        Long size = stringValueOperations.size("key");
        assertEquals(11, size);
        // GETRANGE 截取字符串
        String a1 = stringValueOperations.get("key", 0, 3);
        assertEquals("hell", a1);
        String a2 = stringValueOperations.get("key", -3, -1);
        assertEquals("rld", a2);
        String a3 = stringValueOperations.get("key", 0, -1);
        assertEquals("hello world", a3);
        String a4 = stringValueOperations.get("key", 20, 100);
        assertEquals("", a4);
        // SETRANGE 修改字符串
        stringValueOperations.set("key", "redis", 6);
        assertEquals("hello redis", stringValueOperations.get("key"));
        // MSET 批量设置值
        stringValueOperations.multiSet(Map.of("key1", "v1", "key2", "v2"));
        assertTrue(redisTemplate.hasKey("key1"));
        // MGET 批量获取值
        List<String> list = stringValueOperations.multiGet(Arrays.asList("key1", "key2"));
        assertNotNull(list);
        assertEquals(2, list.size());
        // MSETNX 批量设置值(仅当键不存在)
        Boolean flag = stringValueOperations.multiSetIfAbsent(Map.of("key1", "v1", "key3", "v3"));
        assertFalse(flag);
        integerValueOperations.set("num", 1);
        // INCR 加一
        Long num1 = integerValueOperations.increment("num");
        assertEquals(2, num1);
        // DECR 减一
        Long num2 = integerValueOperations.decrement("num");
        assertEquals(1, num2);
        // INCRBY 加N
        Long num3 = integerValueOperations.increment("num", 15);
        assertEquals(16, num3);
        // DECRBY 减N
        Long num4 = integerValueOperations.decrement("num", 6);
        assertEquals(10, num4);
        redisTemplate.delete(Arrays.asList("key", "key1", "key2", "num"));
    }
}

ListOperations List 列表

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import com.maxqiu.demo.entity.User;
/**
 * List 列表
 */
@SpringBootTest
public class ListOperationsTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 值是String类型的列表
     * 
     * 自定义的RedisTemplate在格式化String时会多出引号
     * 
     * 需要使用Spring内置的StringRedisTemplate
     */
    @Resource(name = "stringRedisTemplate")
    private ListOperations<String, String> stringListOperations;
    /**
     * 值是对象类型的列表
     * 
     * 使用自定义的RedisTemplate,将值格式化成JSON
     */
    @Resource(name = "redisTemplate")
    private ListOperations<String, User> userListOperations;
    /**
     * 值是数字类型的列表
     *
     * 自定义的RedisTemplate可以格式化Integer
     */
    @Resource(name = "redisTemplate")
    private ListOperations<String, Integer> integerListOperations;
    /**
     * LRANGE 按指定范围查看列表的值
     */
    @Test
    @Order(1)
    void range() {
        // 插入元素
        stringListOperations.rightPushAll("list", "a", "b", "c", "d");
        List<String> list = stringListOperations.range("list", 0, -1);
        assertNotNull(list);
        assertEquals(4, list.size());
        assertEquals("a", list.get(0));
        redisTemplate.delete("list");
    }
    /**
     * RPUSH 在列表右侧插值
     * 
     * RPUSHX 仅key存在时在列表右侧插值
     * 
     * LPUSH 在列表左侧插值
     * 
     * LPUSHX 仅key存在时在列表左侧插值
     */
    @Test
    @Order(2)
    void push() {
        // RPUSH 在列表右侧插入单个元素
        stringListOperations.rightPush("list1", "a");
        assertEquals(1, stringListOperations.size("list1"));
        // RPUSH 在列表右侧插入多个元素
        stringListOperations.rightPushAll("list1", "b", "c", "d");
        // stringListOperations.rightPushAll("list1", Arrays.asList("b", "c", "d"));
        assertEquals(4, stringListOperations.size("list1"));
        // RPUSHX 仅key存在时在列表右侧插值
        stringListOperations.rightPushIfPresent("list1", "e");
        assertEquals(5, stringListOperations.size("list1"));
        // LPUSH 在列表左侧插入单个元素
        stringListOperations.leftPush("list2", "a");
        assertEquals(1, stringListOperations.size("list2"));
        // LPUSH 在列表左侧插入多个元素
        stringListOperations.leftPushAll("list2", "b", "c");
        // stringListOperations.leftPushAll("list2", Arrays.asList("d", "e"));
        assertEquals(3, stringListOperations.size("list2"));
        // LPUSHX 仅key存在时在列表左侧插值
        stringListOperations.leftPushIfPresent("list2", "d");
        assertEquals(4, stringListOperations.size("list2"));
        // LINSERT 插入元素
        // 在指定元素左侧插入
        stringListOperations.leftPush("list1", "b", "hello");
        assertEquals("hello", stringListOperations.index("list1", 1));
        // 在指定元素右侧插入
        stringListOperations.rightPush("list2", "c", "world");
        assertEquals("world", stringListOperations.index("list2", 2));
        // 插入的类型需要相同,对应类型使用对应的Operations
        // 例如:int类型
        integerListOperations.rightPushAll("nums", 1, 2, 3);
        assertEquals(3, integerListOperations.size("nums"));
        // 例如:User类型
        User user = new User(1, "tom", new BigDecimal("165.3"));
        userListOperations.rightPush("users", user);
        User user2 = userListOperations.rightPop("users");
        assertEquals(user, user2);
        // 清空数据
        redisTemplate.delete(Arrays.asList("list1", "list2", "nums", "users"));
    }
    /**
     * RPOP 从列表末尾获取元素并删除
     * 
     * LPOP 从列表头部获取元素并删除
     */
    @Test
    @Order(3)
    void pop() {
        stringListOperations.rightPushAll("list", "a", "b", "c", "d");
        // RPOP 从列表末尾获取元素并删除
        String s1 = stringListOperations.rightPop("list");
        assertEquals("d", s1);
        // LPOP 从列表头部获取元素并删除
        String s2 = stringListOperations.leftPop("list");
        assertEquals("a", s2);
        // 清空数据
        redisTemplate.delete("list");
    }
    @Test
    @Order(4)
    void other() {
        Long size1 = stringListOperations.rightPushAll("list", "a", "b", "c", "d", "e");
        // LLEN 列表长度
        Long size2 = stringListOperations.size("list");
        assertEquals(size1, size2);
        // LINDEX 获取指定索引的值
        String first = stringListOperations.index("list", 0);
        assertEquals("a", first);
        // LSET 修改指定索引的值
        stringListOperations.set("list", 2, "world");
        assertEquals("world", stringListOperations.index("list", 2));
        // LREM 删除指定元素
        Long remove = stringListOperations.remove("list", 1, "b");
        assertEquals(1, remove);
        // LTRIM 裁剪列表
        stringListOperations.trim("list", 1, 2);
        assertEquals(2, stringListOperations.size("list"));
        // RPOPLPUSH 从source列表右边吐出一个值,插到destination列表左边
        String move = stringListOperations.rightPopAndLeftPush("list", "list2");
        assertEquals("d", move);
        assertEquals(1, stringListOperations.size("list"));
        assertEquals(1, stringListOperations.size("list2"));
        // 清空数据
        redisTemplate.delete(Arrays.asList("list", "list2"));
    }
}

SetOperations Set 集合

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import com.maxqiu.demo.entity.User;
/**
 * Set 集合
 */
@SpringBootTest
public class SetOperationsTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 值是String类型的集合
     *
     * 自定义的RedisTemplate在格式化String时会多出引号
     *
     * 需要使用Spring内置的StringRedisTemplate
     */
    @Resource(name = "stringRedisTemplate")
    private SetOperations<String, String> stringSetOperations;
    /**
     * 值是对象类型的集合
     *
     * 使用自定义的RedisTemplate,将值格式化成JSON
     */
    @Resource(name = "redisTemplate")
    private SetOperations<String, User> userSetOperations;
    /**
     * 值是数字类型的集合
     *
     * 自定义的RedisTemplate可以格式化Integer
     */
    @Resource(name = "redisTemplate")
    private SetOperations<String, Integer> integerSetOperations;
    /**
     * 测试基本操作
     */
    @Test
    @Order(1)
    void normal() {
        // SADD 添加元素到集合中
        Long add = stringSetOperations.add("set", "a", "b", "c", "d", "e");
        assertEquals(5, add);
        // SCARD 集合大小
        Long size = stringSetOperations.size("set");
        assertEquals(add, size);
        // SMEMBERS 列出集合中的元素
        Set<String> set = stringSetOperations.members("set");
        assertNotNull(set);
        assertEquals(5, set.size());
        // SISMEMBER 存在指定的元素
        Boolean member = stringSetOperations.isMember("set", "a");
        assertTrue(member);
        // SREM 删除指定元素
        Long remove = stringSetOperations.remove("set", "a");
        assertEquals(1, remove);
        // SMOVE 移动指定元素
        Boolean move = stringSetOperations.move("set", "b", "set2");
        assertTrue(move);
        // SRANDMEMBER 随机取出一个或多个元素
        // 取出一个元素
        String set1 = stringSetOperations.randomMember("set");
        System.out.println(set1);
        // 取出多个元素且不重复
        Set<String> set3 = stringSetOperations.distinctRandomMembers("set", 2);
        assertEquals(2, set3.size());
        // 取出多个元素但可能有重复
        List<String> list = stringSetOperations.randomMembers("set", 2);
        assertEquals(2, list.size());
        // SPOP 随机取出一个或多个元素并删除
        String set2 = stringSetOperations.pop("set");
        System.out.println(set2);
        List<String> list2 = stringSetOperations.pop("set", 2);
        assertNotNull(list2);
        assertEquals(2, list2.size());
        // 删除数据
        assertFalse(redisTemplate.hasKey("set"));
        redisTemplate.delete("set2");
    }
    /**
     * 测试不同类型
     */
    @Test
    @Order(2)
    void type() {
        // 对象类型
        Long add = userSetOperations.add("users", new User(1, "tom", new BigDecimal(185)),
            new User(2, "tom", new BigDecimal(183)));
        Long size = userSetOperations.size("users");
        assertEquals(add, size);
        // 数字类型
        Long nums = integerSetOperations.add("nums", 1, 2, 3, 3);
        assertEquals(3, nums);
        // 删除数据
        redisTemplate.delete(Arrays.asList("users", "nums"));
    }
    /**
     * SINTER 交集
     * 
     * SUNION 并集
     * 
     * SDIFF 差集
     */
    @Test
    @Order(3)
    void aggregate() {
        // 准备数据
        stringSetOperations.add("set1", "a", "b", "c");
        stringSetOperations.add("set2", "a", "d", "f");
        // SINTER 交集
        Set<String> intersect = stringSetOperations.intersect("set1", "set2");
        // stringSetOperations.intersect(Arrays.asList("set1", "set2"));
        // stringSetOperations.intersect("set1", Arrays.asList("set2"));
        assertEquals(1, intersect.size());
        assertTrue(intersect.contains("a"));
        // SINTERSTORE 计算交集并存入目标集合
        Long intersectAndStore = stringSetOperations.intersectAndStore("set1", "set2", "set3");
        assertEquals(1, intersectAndStore);
        // SUNION 并集
        Set<String> union = stringSetOperations.union("set1", "set2");
        assertEquals(5, union.size());
        // SUNIONSTORE 计算并集并存入目标集合
        Long unionAndStore = stringSetOperations.unionAndStore("set1", "set2", "set4");
        assertEquals(5, unionAndStore);
        // SDIFF 差集
        Set<String> difference = stringSetOperations.difference("set1", "set2");
        assertEquals(2, difference.size());
        assertTrue(difference.contains("b"));
        // SDIFFSTORE 计算差集并存入目标集合
        Long differenceAndStore = stringSetOperations.differenceAndStore("set1", "set2", "set5");
        assertEquals(2, differenceAndStore);
        // 删除数据
        redisTemplate.delete(Arrays.asList("set1", "set2", "set3", "set4", "set5"));
    }
}

HashOperations Hash 哈希散列

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
/**
 * Hash 哈希散列
 */
@SpringBootTest
public class HashOperationsTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Resource(name = "stringRedisTemplate")
    private HashOperations<String, String, String> stringHashOperations;
    @Resource(name = "redisTemplate")
    private HashOperations<String, String, Integer> integerHashOperations;
    /**
     * HSET 给哈希散列添加字段和值
     * 
     * HSETNX 给哈希散列添加字段和值(仅字段不存在)
     */
    @Test
    @Order(1)
    void set() {
        // HSET 给哈希散列添加字段和值
        // 单个
        stringHashOperations.put("user", "id", "1");
        stringHashOperations.put("user", "name", "tom");
        // 多个
        stringHashOperations.putAll("user", Map.of("email", "tom@126.com", "age", "18"));
        // HSETNX 给哈希散列添加字段和值(仅字段不存在)
        Boolean flag1 = stringHashOperations.putIfAbsent("user", "address", "jiangsu");
        assertTrue(flag1);
        Boolean flag2 = stringHashOperations.putIfAbsent("user", "address", "jiangsu");
        assertFalse(flag2);
        // 删除数据
        redisTemplate.delete("user");
    }
    /**
     * HGET 获取哈希散列的指定字段的值
     * 
     * HMGET 获取哈希散列的多个字段的值
     * 
     * HGETALL 获取哈希散列的所有字段与值
     * 
     * HKEYS 获取哈希散列的所有字段
     * 
     * HVALS 获取哈希散列的所有值
     */
    @Test
    @Order(2)
    void get() {
        // 准备数据
        stringHashOperations.putAll("user", Map.of("id", "1", "name", "tom", "email", "tom@126.com", "age", "18"));
        // HGET 获取哈希散列的指定字段的值
        String name = stringHashOperations.get("user", "name");
        assertEquals("tom", name);
        // HMGET 获取哈希散列的多个字段的值
        List<String> list = stringHashOperations.multiGet("user", Arrays.asList("name", "age", "email", "address"));
        for (String s : list) {
            System.out.println(s);
        }
        // HGETALL 获取哈希散列的所有字段与值
        Map<String, String> map = stringHashOperations.entries("user");
        for (String s : map.keySet()) {
            System.out.println(s + "  " + map.get(s));
        }
        // HKEYS 获取哈希散列的所有字段
        Set<String> keys = stringHashOperations.keys("user");
        for (String key : keys) {
            System.out.println(key);
        }
        // HVALS 获取哈希散列的所有值
        List<String> values = stringHashOperations.values("user");
        for (String value : values) {
            System.out.println(value);
        }
        // 删除数据
        redisTemplate.delete("user");
    }
    @Test
    @Order(3)
    void other() {
        // 准备数据
        stringHashOperations.putAll("user", Map.of("id", "1", "name", "tom", "email", "tom@126.com", "age", "18"));
        // HEXISTS 哈希散列是否存在指定字段
        Boolean hasName = stringHashOperations.hasKey("user", "name");
        assertTrue(hasName);
        // HDEL 删除哈希散列的指定字段
        Long delete = stringHashOperations.delete("user", "id", "email");
        assertEquals(2, delete);
        // HLEN 哈希散列的长度
        Long size = stringHashOperations.size("user");
        assertEquals(2, size);
        // HSTRLEN 哈希散列的字段的长度
        Long length = stringHashOperations.lengthOfValue("user", "name");
        assertEquals(3, length);
        // HINCRBY 增减哈希散列中指定字段的值
        Long increment = integerHashOperations.increment("user", "age", 1);
        assertEquals(19, increment);
        // 删除数据
        redisTemplate.delete("user");
    }
}

ZSetOperations Zset 有序集合(Sorted Sets)

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.ZSetOperations;
/**
 * ZSet 有序集合(Sorted Sets)
 */
@SpringBootTest
public class ZSetOperationsTest {
    @Autowired
    private RedisOperations<String, Object> redisOperations;
    /**
     * 值是String类型的有序集合
     *
     * 自定义的RedisTemplate在格式化String时会多出引号
     *
     * 需要使用Spring内置的StringRedisTemplate
     */
    @Resource(name = "stringRedisTemplate")
    private ZSetOperations<String, String> stringZSetOperations;
    /**
     * ZADD 将一个或多个member及source添加到有序集合中
     */
    @Test
    @Order(1)
    void add() {
        Boolean flag = stringZSetOperations.add("zset", "maths", 99);
        assertTrue(flag);
        Set<ZSetOperations.TypedTuple<String>> set = new HashSet<>();
        set.add(new DefaultTypedTuple<>("chinese", 90.0));
        set.add(new DefaultTypedTuple<>("english", 60.0));
        Long add = stringZSetOperations.add("zset", set);
        assertEquals(2, add);
        // 删除数据
        redisOperations.delete("zset");
    }
    /**
     * 根据指定规则排序取出
     */
    @Test
    @Order(2)
    void get() {
        // 准备数据
        Set<ZSetOperations.TypedTuple<String>> set = new HashSet<>();
        set.add(new DefaultTypedTuple<>("chinese", 115.0));
        set.add(new DefaultTypedTuple<>("maths", 135.5));
        set.add(new DefaultTypedTuple<>("english", 110.5));
        set.add(new DefaultTypedTuple<>("physics", 88.0));
        set.add(new DefaultTypedTuple<>("chemistry", 65.0));
        stringZSetOperations.add("zset", set);
        /// 正序
        // ZRANGE 返回排序集合中的指定范围的元素(正序,从小到大)
        Set<String> zset = stringZSetOperations.range("zset", 0, -1);
        System.out.println(zset);
        // ZRANGEBYSCORE 返回指定分数内的元素(正序,从小到大)
        Set<String> zset3 = stringZSetOperations.rangeByScore("zset", 10, 100);
        System.out.println(zset3);
        // ZRANGEBYSCORE 返回指定分数内的元素(正序,从小到大)(LIMIT 分页)
        Set<String> zset5 = stringZSetOperations.rangeByScore("zset", 0, 200, 1, 2);
        System.out.println(zset5);
        // ZRANGE xxxWithScores 返回时包含分数(正序,从小到大)其他同
        Set<ZSetOperations.TypedTuple<String>> zset2 = stringZSetOperations.rangeWithScores("zset", 0, -1);
        if (zset2 != null) {
            for (ZSetOperations.TypedTuple<String> stringTypedTuple : zset2) {
                System.out.println(stringTypedTuple.getValue() + " " + stringTypedTuple.getScore());
            }
        }
        /// 倒序
        // ZREVRANGE 返回排序集合中的指定范围的元素(倒序,从大到小)
        stringZSetOperations.reverseRange("zset", 0, -1);
        // ZREVRANGEBYSCORE 返回指定分数内的元素(倒序,从大到小)
        stringZSetOperations.reverseRangeByScore("zset", 10, 100);
        // ZREVRANGEBYSCORE 返回指定分数内的元素(倒序,从大到小)(LIMIT 分页)
        stringZSetOperations.reverseRangeByScore("zset", 0, 200, 1, 2);
        // ZREVRANGE xxxWithScores 返回时包含分数(倒序,从大到小)其他同
        stringZSetOperations.reverseRangeWithScores("zset", 0, -1);
        // 删除数据
        redisOperations.delete("zset");
    }
    @Test
    @Order(3)
    void other() {
        // 准备数据
        Set<ZSetOperations.TypedTuple<String>> set = new HashSet<>();
        set.add(new DefaultTypedTuple<>("chinese", 115.0));
        set.add(new DefaultTypedTuple<>("maths", 135.5));
        set.add(new DefaultTypedTuple<>("english", 110.5));
        set.add(new DefaultTypedTuple<>("physics", 88.0));
        set.add(new DefaultTypedTuple<>("chemistry", 65.0));
        stringZSetOperations.add("zset", set);
        // ZSCORE 返回排序集中元素的分数
        Double score = stringZSetOperations.score("zset", "chinese");
        assertEquals(115.0, score);
        // 查询元素在有序集合中的排名
        // ZRANK
        Long rank = stringZSetOperations.rank("zset", "chinese");
        assertEquals(3, rank);
        // ZREVRANK
        Long reverseRank = stringZSetOperations.reverseRank("zset", "chinese");
        assertEquals(1, reverseRank);
        // ZCARD key 返回排序集合的元素数量
        Long size = stringZSetOperations.size("zset");
        assertEquals(5, size);
        // ZCOUNT 统计指定范围内的元素数量
        Long count = stringZSetOperations.count("zset", 0, 100);
        assertEquals(2, count);
        // ZINCRBY 给指定元素增减分数
        Double incrementScore = stringZSetOperations.incrementScore("zset", "english", 18.5);
        assertEquals(129.0, incrementScore);
        // ZREM 删除元素
        Long remove = stringZSetOperations.remove("zset", "chinese", "english");
        assertEquals(2, remove);
        // 删除数据
        redisOperations.delete("zset");
    }
}

Transactions 事务

import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ValueOperations;
/**
 * 事务
 */
@SpringBootTest
public class TransactionsTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 值是数字类型的字符串
     *
     * 自定义的RedisTemplate可以格式化Integer
     */
    @Resource(name = "redisTemplate")
    private ValueOperations<String, Integer> valueOperations;
    @Test
    void test() {
        valueOperations.set("a", 100);
        valueOperations.set("b", 100);
        // 乐观锁
        // redisOperations.watch("a");
        List<Long> execute = redisTemplate.execute(new SessionCallback<>() {
            @Override
            public List<Long> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                ValueOperations<String, Long> valueOperations = operations.opsForValue();
                valueOperations.increment("a", 10);
                valueOperations.decrement("b", 10);
                return operations.exec();
            }
        });
        Assertions.assertEquals(110, execute.get(0));
        Assertions.assertEquals(90, execute.get(1));
        // 删除数据
        redisTemplate.delete(Arrays.asList("a", "b"));
    }
}

原文:SpringBoot 2.x / 3.x 整合 Redis ( RedisTemplate 操作五大常用数据类型)