SpringBoot使用RedisTemplate、StringRedisTemplate操作Redis
前言
本文实现了在SpringBoot中集成Redis,使用RedisTemplate对象操作并编写了一些常用方法的工具类。
RedisTemplate和StringRedisTemplate的区别:
1. 两者的关系是StringRedisTemplate继承RedisTemplate。
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.data.redis.core; import org.springframework.data.redis.connection.DefaultStringRedisConnection; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.RedisSerializer; public class StringRedisTemplate extends RedisTemplate { public StringRedisTemplate() { this.setKeySerializer(RedisSerializer.string()); this.setValueSerializer(RedisSerializer.string()); this.setHashKeySerializer(RedisSerializer.string()); this.setHashValueSerializer(RedisSerializer.string()); } public StringRedisTemplate(RedisConnectionFactory connectionFactory) { this(); this.setConnectionFactory(connectionFactory); this.afterPropertiesSet(); } protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) { return new DefaultStringRedisConnection(connection); } }
2. 两者的数据是不共通的,也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
3. SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认使用的序列类在在操作数据的时候,比如说存入数据会将数据先序列化成字节数组然后在存入Redis数据库,这个时候打开Redis查看的时候,你会看到你的数据不是以可读的形式展现的,而是以字节数组显示。
那么就可以得出一个结论,如果你想使用默认的配置来操作redis,则如果操作的数据是字节数组,就是用RedisTemplate,如果操作的数据是明文,使用StringRedisTemplate。
当然在项目中真实使用时,一般是自定义RedisTemplate的Bean实例,来设置具体的序列化策略,说白了就是RedisTemplate通过自定义Bean可以实现和StringRedisTemplate一样的序列化,使用起来更加灵活。
一、Redis五种基础数据结构
首先对redis来说,所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash。
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
String字符串 | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作 |
List列表 | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素 |
Set集合 | 包含字符串的无序集合 | 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 |
Hash散列 | 包含键值对的无序散列表 | 包含方法有添加、获取、删除单个元素 |
Zset有序集合 | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 |
二、RedisTemplate 概述
1、Redis 是一个缓存、消息代理和功能丰富的键值存储。Spring Boot 为 Lettuce 和 Jedis 客户端库提供基本的自动配置,并为 Spring Data Redis 提供抽象。官网传送。
2、spring-boot-starter-data-redis 启动器,整合了需要的依赖项,默认情况下,它使用 Lettuce 作为客户端。这个启动器同时处理传统应用程序和反应性应用程序(reactive applications)。
3、官方还提供了一个 spring-boot-starter-data-redis-reactive 启动器,用于与具有 reactive 支持的其他存储保持一致。
4、可以像注入任何其他 Spring Bean 一样注入一个自动配置的 RedisConnectionFactory、StringRedisTemplate 或普通的 RedisTemplate 实例。class StringRedisTemplate extends RedisTemplate
5、默认情况下,Redis 实例尝试连接本地主机(localhost)端口为 6379 的 Redis 服务器,默认使用空密码,连接 0 号数据库:RedisProperties
6、可以实现 LettuceClientConfigurationBuilderCustomizer 接口,以实现更高级的定制。如果使用的是 Jedis 客户端,则实现 JedisClientConfigurationBuilderCustomizer 接口。
三、SpringBoot通过RedisTemplate连接Redis
一、引入依赖
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2
二、基础配置
1、配置application.yml文件
配置如下:
spring: data: redis: mode: master # 地址 host: 30.46.34.190 # 端口,默认为6379 port: 6379 # 密码,没有不填 password: '' # 几号库 database: 1 sentinel: master: master nodes: 30.46.34.190 cluster: nodes: 30.46.34.190 lettuce: pool: # 连接池的最大数据库连接数 max-active: 200 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms # 连接池中的最大空闲连接 max-idle: 50 # 连接池中的最小空闲连接 min-idle: 8
2、RedisTemplate配置
代码如下:
/* *自定义Redis配置类,进行序列化以及RedisTemplate设置 */ @Configuration @EnableConfigurationProperties({RedisProperties.class}) public class RedisConfig { @Value("${spring.data.redis.mode}") private String redisMode; private final RedisProperties properties; public RedisConfig(RedisProperties properties) { this.properties = properties; } @Bean public LettuceConnectionFactory redisConnectionFactory( ObjectProvider builderCustomizers, ClientResources clientResources) { GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(this.properties.getLettuce().getPool().getMaxActive()); config.setMaxIdle(this.properties.getLettuce().getPool().getMaxIdle()); config.setMinIdle(this.properties.getLettuce().getPool().getMinIdle()); config.setMaxWait(this.properties.getLettuce().getPool().getMaxWait()); LettucePoolingClientConfiguration clientConfiguration = LettucePoolingClientConfiguration .builder() .poolConfig(config) .build(); switch (redisMode) { case "master": RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setDatabase(this.properties.getDatabase()); redisStandaloneConfiguration.setPassword(this.properties.getPassword()); redisStandaloneConfiguration.setHostName(this.properties.getHost()); redisStandaloneConfiguration.setPort(this.properties.getPort()); return new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfiguration); case "sentinel": RedisSentinelConfiguration redisSentinelConfig = new RedisSentinelConfiguration(); redisSentinelConfig.setDatabase(this.properties.getDatabase()); redisSentinelConfig.setPassword(this.properties.getPassword()); redisSentinelConfig.setMaster(this.properties.getSentinel().getMaster()); redisSentinelConfig.setSentinels( this.properties.getSentinel().getNodes().stream() .map(RedisNode::fromString).collect(Collectors.toList()) ); return new LettuceConnectionFactory(redisSentinelConfig, clientConfiguration); case "cluster": RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(); redisClusterConfiguration.setPassword(this.properties.getPassword()); redisClusterConfiguration.setClusterNodes( this.properties.getCluster().getNodes().stream() .map(RedisNode::fromString).collect(Collectors.toList()) ); return new LettuceConnectionFactory(redisClusterConfiguration, clientConfiguration); default: throw new IllegalArgumentException("无效的redis mode配置"); } } @Bean public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory){ ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(om, Object.class); RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(connectionFactory); // 使用StringRedisSerializer序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); // 设置值(value)的序列化采用Jackson2JsonRedisSerializer template.setValueSerializer(serializer); // 使用StringRedisSerializer序列化和反序列化redis hash类型的key值 template.setHashKeySerializer(new StringRedisSerializer()); // 序列化和反序列化redis hash类型的value值 template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }
3. 编写测试类
package com.zpli.web.test; /** * created at 2024/7/9 19:08 * * @author somnuszpli */ import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class RedisTemplateTest { @Autowired private RedisTemplate redisTemplate;// @Test public void test() throws Exception { redisTemplate.opsForValue().set("student:1", "zpli"); // Assertions.assertThat(redisTemplate.opsForValue().get("student:1")) .isEqualTo("kirito"); } }
引入了 RedisTemplate,这个类是 spring-starter-data-redis 提供给应用直接访问 redis 的入口。从其命名就可以看出,其是模板模式在 spring 中的体现,与 restTemplate,jdbcTemplate 类似,而 springboot 为我们做了自动的配置,具体已在上文详解。
redisTemplate 通常不直接操作键值,而是通过 opsForXxx() 访问,在本例中,key 和 value 均为字符串类型。绑定字符串在实际开发中也是最为常用的操作类型。
三、详解 RedisTemplate 的 API
RedisTemplate 为我们操作 Redis 提供了丰富的 API,可以将他们简单进行下归类。
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作 String 类型 数据 |
redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() | ListOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate | 通用的命令 |
1 常用数据操作
这一类 API 也是我们最常用的一类。
众所周知,redis 存在 5 种数据类型:
- 字符串类型(string)
- 散列类型(hash)
- 列表类型(list)
- 集合类型(set)
- 有序集合类型(zset)
而 redisTemplate 实现了 RedisOperations 接口,在其中,定义了一系列与 redis 相关的基础数据操作接口,数据类型分别与下面 API 对应:
//非绑定key操作
ValueOperations opsForValue();
HashOperations opsForHash();
ListOperations opsForList();
SetOperations opsForSet();
ZSetOperations opsForZSet();
//绑定key操作
BoundValueOperations boundValueOps(K key);
BoundHashOperations boundHashOps(K key);
BoundListOperations boundListOps(K key);
BoundSetOperations boundSetOps(K key);
BoundZSetOperations boundZSetOps(K key);
若以 bound 开头,则意味着在操作之初就会绑定一个 key,后续的所有操作便默认认为是对该 key 的操作,算是一个小优化。
2.几种数据结构操作的具体用法
操作 Redis 的 String 数据结构
设置当前的 key 以及 value 值
redisTemplate.opsForValue().set(key, value)
redisTemplate.opsForValue().set("num","123");
设置当前的 key 以及 value 值并且设置过期时间
redisTemplate.opsForValue().set(key, value, timeout, unit)
redisTemplate.opsForValue().set("num","123",10, TimeUnit.SECONDS);
- TimeUnit.DAYS //天
- TimeUnit.HOURS //小时
- TimeUnit.MINUTES //分钟
- TimeUnit.SECONDS //秒
- TimeUnit.MILLISECONDS //毫秒
将旧的 key 设置为 value,并且返回旧的 key(设置 key 的字符串 value 并返回其旧值)
redisTemplate.opsForValue().getAndSet(key, value);
在原有的值基础上新增字符串到末尾
redisTemplate.opsForValue().append(key, value)
获取字符串的长度
redisTemplate.opsForValue().size(key)
重新设置 key 对应的值,如果存在返回 false,否则返回 true
redisTemplate.opsForValue().setIfAbsent(key, value)
设置 map 集合到 redis
Map valueMap = new HashMap();
valueMap.put("valueMap1","map1");
valueMap.put("valueMap2","map2");
valueMap.put("valueMap3","map3");
redisTemplate.opsForValue().multiSet(valueMap);
如果对应的 map 集合名称不存在,则添加否则不做修改
Map valueMap = new HashMap();
valueMap.put("valueMap1","map1");
valueMap.put("valueMap2","map2");
valueMap.put("valueMap3","map3");
redisTemplate.opsForValue().multiSetIfAbsent(valueMap);
通过 increment(K key, long delta) 方法以增量方式存储 long 值(正值则自增,负值则自减)
redisTemplate.opsForValue().increment(key, increment);
批量获取值
public List multiGet(Collection keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
返回传入 key 所存储的值的类型
修改 redis 中 key 的名称
public void renameKey(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
如果旧值 key 存在时,将旧值改为新值
public Boolean renameOldKeyIfAbsent(String oldKey, String newKey) {
return redisTemplate.renameIfAbsent(oldKey, newKey);
}
判断是否有 key 所对应的值,有则返回 true,没有则返回 false
redisTemplate.hasKey(key)
删除单个 key 值
redisTemplate.delete(key)
批量删除 key
redisTemplate.delete(keys) //其中keys:Collection keys
设置过期时间
public Boolean expire(String key, long timeout, TimeUnit unit){
return redisTemplate.expire(key, timeout, unit);
}
public Boolean expireAt(String key, Date date) {
return redisTemplate.expireAt(key, date);
}
返回当前 key 所对应的剩余过期时间
redisTemplate.getExpire(key);
返回剩余过期时间并且指定时间单位
public Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
查找匹配的 key 值,返回一个 Set 集合类型
public Set getPatternKey(String pattern) {
return redisTemplate.keys(pattern);
}
将 key 持久化保存
public Boolean persistKey(String key) {
return redisTemplate.persist(key);
}
将当前数据库的 key 移动到指定 redis 中数据库当中
public Boolean moveToDbIndex(String key, int dbIndex) {
return redisTemplate.move(key, dbIndex);
}
Hash 类型
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。
获取变量中的指定 map 键是否有值,如果存在该 map 键则获取值,没有则返回 null。
redisTemplate.opsForHash().get(key, field)
获取变量中的键值对
public Map hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
新增 hashMap 值
redisTemplate.opsForHash().put(key, hashKey, value)
以 map 集合的形式添加键值对
public void hPutAll(String key, Map maps) {
redisTemplate.opsForHash().putAll(key, maps);
}
仅当 hashKey 不存在时才设置
public Boolean hashPutIfAbsent(String key, String hashKey, String value) {
return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}
删除一个或者多个 hash 表字段
public Long hashDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
查看 hash 表中指定字段是否存在
public boolean hashExists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
给哈希表 key 中的指定字段的整数值加上增量 increment
public Long hashIncrBy(String key, Object field, long increment) {
return redisTemplate.opsForHash().increment(key, field, increment);
}
public Double hIncrByDouble(String key, Object field, double delta) {
return redisTemplate.opsForHash().increment(key, field, delta);
}
获取所有 hash 表中字段
redisTemplate.opsForHash().keys(key)
获取 hash 表中存在的所有的值
public List hValues(String key) {
return redisTemplate.opsForHash().values(key);
}
获取 hash 表中字段的数量
redisTemplate.opsForHash().size(key)
匹配获取键值对,ScanOptions.NONE 为获取全部键对
public Cursor hashScan(String key, ScanOptions options) {
return redisTemplate.opsForHash().scan(key, options);
}
List 类型
通过索引获取列表中的元素
redisTemplate.opsForList().index(key, index)
获取列表指定范围内的元素(start 开始位置, 0 是开始位置,end 结束位置, -1返回所有)
redisTemplate.opsForList().range(key, start, end)
存储在 list 的头部,即添加一个就把它放在最前面的索引处
redisTemplate.opsForList().leftPush(key, value)
把多个值存入 List 中(value 可以是多个值,也可以是一个 Collection value)
redisTemplate.opsForList().leftPushAll(key, value)
List 存在的时候再加入
redisTemplate.opsForList().leftPushIfPresent(key, value)
按照先进先出的顺序来添加(value 可以是多个值,或者是 Collection var2)
redisTemplate.opsForList().rightPush(key, value)
redisTemplate.opsForList().rightPushAll(key, value)
设置指定索引处元素的值
redisTemplate.opsForList().set(key, index, value)
移除并获取列表中第一个元素(如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止)
redisTemplate.opsForList().leftPop(key)
redisTemplate.opsForList().leftPop(key, timeout, unit)
移除并获取列表最后一个元素
redisTemplate.opsForList().rightPop(key)
redisTemplate.opsForList().rightPop(key, timeout, unit)
从一个队列的右边弹出一个元素并将这个元素放入另一个指定队列的最左边
redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey)
redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey, timeout, unit)
删除集合中值等于 value 的元素(index=0, 删除所有值等于 value 的元素; index>0, 从头部开始删除第一个值等于 value 的元素; index