Springboot-redis命令行封装
前言
Redis(Remote Dictionary Server),即远程字典服务,是一个开源的使用ANSI C语言编写的、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis也是现在最受欢迎的NoSQL数据库之一,其中的NoSQL是“Not Only SQL”的缩写,泛指非关系型数据库。
redis的常用使用场景,可以做缓存,分布式锁,自增序列等,使用redis的方式和我们使用数据库的方式差不多,首先我们要在自己的本机电脑或者服务器上安装一个redis的服务器,通过我们的java客户端在程序中进行集成,然后通过客户端完成对redis的增删改查操作。redis的Java客户端类型还是很多的,常见的有jedis, redission,lettuce等,所以我们在集成的时候,我们可以选择直接集成这些原生客户端。但是在springBoot中更常见的方式是集成spring-data-redis,这是spring提供的一个专门用来操作redis的项目,封装了对redis的常用操作,里边主要封装了jedis和lettuce两个客户端。相当于是在他们的基础上加了一层门面。
本篇文章我们就来重点介绍,springBoot通过集成spring-data-redis使用对于redis的常用操作。
本篇博客SpringBoot版本为2.6.13
,请注意版本兼容问题
SpringBoot配置redis
一、pom文件中引入redis依赖
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
直接引入上述依赖后,点进去会发现,里面包含了spring-data-redis和 lettuce-core两个核心包,这就是为什么说我们的spring-boot-starter-data-redis默认使用的就是lettuce这个客户端了。
如果我们想要使用jedis客户端怎么办呢?就需要排除lettuce这个依赖,再引入jedis的相关依赖就可以了。
那么为什么我们只需要通过引入不同的依赖就能让spring-data-redis可以自由切换客户端呢,这其实就涉及到了springBoot的自动化配置原理,为大家简述一下。
springBoot这个框架之所以可以通过各种starter无缝融合其他技术的一大主要原因就是springBoot本身的自动化配置功能。所谓自动化配置就是springBoot本身已经预先设置好了一些常用框架的整合类。然后通过类似于ConditionOn这样的条件判断注解,去辨别你的项目中是否有相关的类(或配置)了,进而进行相关配置的初始化。
springBoot预设的自动化配置类都位于spring-boot-autoconfigure这个包中,只要我们搭建了springBoot的项目,这个包就会被引入进来。
这个包下就有一个RedisAutoConfiguration这个类,顾名思义就是Redis的自动化配置。在这个类中,会引入LettuceConnectionConfiguration 和 JedisConnectionConfiguration 两个配置类,分别对应lettuce和jedis两个客户端。
LettuceConnectionConfiguration配置类中,通过@ConditionalOnClass 和 ConditionalOnProperty进行条件判断,是否允许自动装配。
@ConditionalOnClass({RedisClient.class});只有当
RedisClient
这个类在类路径上可用时,带有这个注解的bean或配置才会被创建。换句话说,如果RedisClient
类不存在于类路径中(例如,你没有包含相关的依赖),那么任何使用这个注解的bean或配置都不会被Spring Boot创建或应用。在Spring Boot Data Redis的上下文中,
RedisClient
类是Lettuce连接工厂的一个关键部分,因此这个条件确保只有在Lettuce客户端库可用时,相关的自动配置才会生效。@ConditionalOnProperty()允许你基于
application.properties
或application.yml
文件中的属性来决定是否创建bean。只有当application.properties
或application.yml
文件中有一个属性spring.redis.lettuce.enabled
并且它的值为true
时,带有这个注解的bean或配置才会被创建。
JedisConnectionConfiguration 中也通过类似的条件判断注解进行判定是否自动装配
由于我们的项目通过redis starter 自动引入了lettuce-core,而没有引入jedis相关依赖,所以LettuceConnectionConfiguration这个类的判断成立会被加载,而Jedis的判断不成立,所以不会加载。进而lettuce的配置生效,所以我们在使用的使用, 默认就是lettuce的客户端。
二、yml文件中进行基本的配置
spring:
redis:
host: localhost
password: #your password
port: 6379
database: 0 #default database 0
#连接池
lettuce:
pool:
max-active: 8 #最大连接池
max-idle: 4 #连接池中的最大空闲连接
min-idle: 2 #连接池中的最小空闲连接
但是有的时候我们想要给我们的redis客户端配置上连接池。就像我们连接mysql的时候,也会配置连接池一样,目的就是增加对于数据连接的管理,提升访问的效率,也保证了对资源的合理利用。那么我们如何配置连接池呢,这里大家一定要注意了,很多网上的文章中,介绍的方法可能由于版本太低,都不是特别的准确。 比如很多人使用spring.redis.pool来配置,这个是不对的(不清楚是不是老版本是这样的配置的,但是在springboot-starter-data-redis中这种写法不对)。首先是配置文件,由于我们使用的lettuce客户端,所以配置的时候,在spring.redis下加上lettuce再加上pool来配置。
此外连接池配置还需要加入一个依赖
<!-- 对象池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
三、项目中使用
我们的配置工作准备就绪以后,我们就可以在项目中操作redis了,操作的话,使用spring-data-redis中为我们提供的 RedisTemplate 这个类,就可以操作了。我们先举个简单的例子,插入一个键值对(值为string)
@RestController
@RequestMapping("/redis")
public class BasicController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("save")
public String save(String key, String value) {
redisTemplate.opsForValue().set(key, value);
return "ok";
}
}
但是运行之后发送请求,在成功存储后,通过命令查看redis 数据库0,获取keys的时候,会发现一个比较难受的东西,我们发现存进去的key明明是"a",但是查询得到的key确是这么一段玩意儿,而且通过它获取value也不会获取成功。
“\xac\xed\x00\x05t\x00\x01a”
问题出在了这里:
当你在Spring Boot中使用
redisTemplate.opsForValue().set(a, 1);
来设置一个值到Redis时,实际上你存储的是一个序列化后的对象。默认情况下,Spring Boot中的RedisTemplate
使用Java序列化来存储对象。当你从Redis命令行工具执行
keys *
命令时,你看到的是序列化后的数据的二进制表示。这就是为什么你看到\xac\xed\x00\x05t\x00\x01a
这样的输出,这是Java对象序列化后的字节表示。如果你希望在Redis命令行中看到更友好的数据表示,你可以考虑以下几种方法:
- 使用字符串表示:
如果你只是存储简单的字符串或数字,你可以直接使用StringRedisTemplate
而不是RedisTemplate
。这样,数据就不会被序列化,而是直接以字符串形式存储。- 自定义序列化器:
你可以为RedisTemplate
配置自定义的序列化器,例如使用JSON序列化器。这样,存储的数据将是JSON格式的,你可以在命令行中更容易地查看。- 反序列化数据:
如果你确实需要查看或操作存储在Redis中的数据,你可以从Spring Boot应用中读取它,并反序列化回原始对象。
那就自定义一下序列化叭
package com.jerry.springbootredis.conf;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @version 1.0
* @Author jerryLau
* @Date 2024/4/8 14:32
* @注释
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 创建RedisTemplate<String, Object>对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(connectionFactory);
// 定义Jackson2JsonRedisSerializer序列化对象
Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会报异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
StringRedisSerializer stringSerial = new StringRedisSerializer();
// redis key 序列化方式使用stringSerial
template.setKeySerializer(stringSerial);
// redis value 序列化方式使用jackson
template.setValueSerializer(jacksonSeial);
// redis hash key 序列化方式使用stringSerial
template.setHashKeySerializer(stringSerial);
// redis hash value 序列化方式使用jackson
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
}
再次通过api接口保存(“a”,“b”),通过可视化工具查看,获得a的值为”b“
四、工具类封装
我们在前面的代码中已经通过RedisTemplate成功操作了redis服务器,比如set一个字符串,我们可以使用:
redisTemplate.opsForValue().set(key, value);
来put一个String类型的键值对。而redis中可以支持 string, list, hash,set, zset五种数据格式,这五种数据格式的常用操作,都在RedisTemplate这个类中进行了封装。 操作string类型就是用opsForValue,操作list类型是用listOps, 操作set类型是用setOps等等。
尝试通过自定义工具类的方式进行一些操作的封装,在之后的操作中可以直接注入工具类,进行使用
封装:
package com.jerry.springbootredis.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @version 1.0
* @Author jerryLau
* @Date 2024/4/8 14:42
* @注释 封装redis 操作命令
*/
@Component
public class RedisUtils {
@Autowired
private RedisTemplate redisTemplate;
/***
* 为键所对应的值设置过期时间
* @param key
* @param timeout 毫秒数
* @return
*/
public boolean expire(String key, long timeout) {
return redisTemplate.expire(key, timeout, TimeUnit.MILLISECONDS);
}
/***
* 根据key 获取键值对过期时间
* @param key
* @return
*/
public long getValueTimeOut(String key) {
return redisTemplate.getExpire(key);
}
/***
* 查找是否包含某个键
* @param key
* @return
*/
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/***
* 移除指定某个key的时间
* @param key
* @return
*/
public boolean persist(String key) {
return redisTemplate.boundValueOps(key).persist();
}
//------------------String 操作--------------
/***
* 按照key值取
* @param key
* @return
*/
public Object get(String key) {
return key.isEmpty() ? null : redisTemplate.opsForValue().get(key);
}
/***
* 按照key进行存
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/***
* 按照key设置过期时间存
* @param key
* @param value
* @param timeOut ms
*/
public void set(String key, String value, Long timeOut) {
if (timeOut > 0) {
redisTemplate.opsForValue().set(key, value, timeOut, TimeUnit.MILLISECONDS);
} else {
redisTemplate.opsForValue().set(key, value);
}
}
/**
* 批量添加 key (重复的键会覆盖)
*
* @param keyAndValue
*/
public void batchSet(Map<String, String> keyAndValue) {
redisTemplate.opsForValue().multiSet(keyAndValue);
}
/**
* 批量添加 key-value 只有在键不存在时,才添加
* map 中只要有一个key存在,则全部不添加
*
* @param keyAndValue
*/
public void batchSetIfAbsent(Map<String, String> keyAndValue) {
redisTemplate.opsForValue().multiSetIfAbsent(keyAndValue);
}
/**
* 对一个 key-value 的值进行加减操作,
* 如果该 key 不存在 将创建一个key 并赋值该 number
* 如果 key 存在,但 value 不是长整型 ,将报错
*
* @param key
* @param number
*/
public Long increment(String key, long number) {
return redisTemplate.opsForValue().increment(key, number);
}
/**
* 对一个 key-value 的值进行加减操作,
* 如果该 key 不存在 将创建一个key 并赋值该 number
* 如果 key 存在,但 value 不是 纯数字 ,将报错
*
* @param key
* @param number
*/
public Double increment(String key, double number) {
return redisTemplate.opsForValue().increment(key, number);
}
//------------------无序集合 set类型 操作--------------
/**
* 将数据放入set缓存
*
* @param key 键
* @return
*/
public void sSet(String key, String value) {
redisTemplate.opsForSet().add(key, value);
}
/**
* 获取变量中的值
*
* @param key 键
* @return
*/
public Set<Object> members(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 随机获取set中key变量中指定个数的value
*
* @param key 键
* @param count 个数
* @return key=key key对应的value值中随机取count个返回
*/
public List randomMembers(String key, long count) {
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* 随机获取变量中的元素
*
* @param key 键
* @return key=key key对应的value值中随机取1个返回
*/
public Object randomMember(String key) {
return redisTemplate.opsForSet().randomMember(key);
}
/**
* 弹出无序列表中的元素
* 全部弹出后列表会被删除,继续弹出会报错空指针
* @param key 键
* @return
*/
public Object pop(String key) {
return redisTemplate.opsForSet().pop(key);
}
/**
* 获取变量中值的长度
*
* @param key 键
* @return
*/
public long size(String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 检查给定的元素是否在变量中。
*
* @param key 键
* @param obj 元素对象
* @return
*/
public boolean isMember(String key, Object obj) {
return redisTemplate.opsForSet().isMember(key, obj);
}
/**
* 转移变量的元素值到目的变量。
*
* @param key 键
* @param value 元素对象
* @param destKey 元素对象
* @return
*/
public boolean move(String key, String value, String destKey) {
return redisTemplate.opsForSet().move(key, value, destKey);
}
/**
* 批量移除set缓存中元素
*
* @param key 键
* @param values 值
* @return
*/
public void remove(String key, Object... values) {
redisTemplate.opsForSet().remove(key, values);
}
/**
* 通过给定的key求2个set变量的差值
*
* @param key 键
* @param destKey 键
* @return
*/
public Set<Set> difference(String key, String destKey) {
return redisTemplate.opsForSet().difference(key, destKey);
}
------------------hash类型 操作--------------
/**
* 加入缓存
*
* @param key 键
* @param map 键
* @return
*/
public void add(String key, Map<String, String> map) {
redisTemplate.opsForHash().putAll(key, map);
}
/**
* 获取 key 下的 所有 hashkey 和 value
*
* @param key 键
* @return
*/
public Map<Object, Object> getHashEntries(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 验证指定 key 下 有没有指定的 hashkey
*
* @param key
* @param hashKey
* @return
*/
public boolean hashKey(String key, String hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**
* 获取指定key的值string
*
* @param key redis的键
* @param key2 存储数据map的键
* @return
*/
public String getMapString(String key, String key2) {
return redisTemplate.opsForHash().get(key, key2).toString();
}
/**
* 获取指定的值Int
*
* @param key redis的键
* @param key2 存储数据map的键
* @return map中可以转化为int的value值
*/
public Integer getMapInt(String key, String key2) {
return Integer.valueOf( redisTemplate.opsForHash().get(key, key2).toString());
}
/**
* 弹出元素并删除
*
* @param key 键
* @return
*/
public String popValue(String key) {
return redisTemplate.opsForSet().pop(key).toString();
}
/**
* 删除指定 hash 的 HashKey
*
* @param key
* @param hashKeys
* @return 删除成功的 数量
*/
public Long delete(String key, String... hashKeys) {
return redisTemplate.opsForHash().delete(key, hashKeys);
}
/**
* 给指定 hash 的 hashkey 做增减操作
*
* @param key
* @param hashKey
* @param number
* @return
*/
public Long increment(String key, String hashKey, long number) {
return redisTemplate.opsForHash().increment(key, hashKey, number);
}
/**
* 给指定 hash 的 hashkey 做增减操作
*
* @param key
* @param hashKey
* @param number
* @return
*/
public Double increment(String key, String hashKey, Double number) {
return redisTemplate.opsForHash().increment(key, hashKey, number);
}
/**
* 获取 key 下的 所有 hashkey 字段
*
* @param key
* @return
*/
public Set<Object> hashKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 获取指定 hash 下面的 键值对 数量
*
* @param key
* @return
*/
public Long hashSize(String key) {
return redisTemplate.opsForHash().size(key);
}
//---------------------list类型---------------------
/**
* 在变量左边添加元素值
*
* @param key
* @param value
* @return
*/
public void leftPush(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
/**
* 获取集合指定位置的值。
*
* @param key
* @param index
* @return
*/
public Object index(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
/**
* 获取指定区间的值。
*
* @param key
* @param start
* @param end
* @return
*/
public List<Object> range(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 把最后一个参数值放到指定集合的第一个出现中间参数的前面,
* 如果中间参数值存在的话。
*
* @param key
* @param pivot
* @param value
* @return
*/
public void leftPush(String key, String pivot, String value) {
redisTemplate.opsForList().leftPush(key, pivot, value);
}
/**
* 向左边批量添加参数元素。
*
* @param key
* @param values
* @return
*/
public void leftPushAll(String key, String... values) {
redisTemplate.opsForList().leftPushAll(key, values);
}
/**
* 向集合最右边添加元素。
*
* @param key
* @param value
* @return
*/
public void leftPushAll(String key, String value) {
redisTemplate.opsForList().rightPush(key, value);
}
/**
* 向左边批量添加参数元素。
*
* @param key
* @param values
* @return
*/
public void rightPushAll(String key, String... values) {
redisTemplate.opsForList().rightPushAll(key, values);
}
/**
* 向已存在的集合中添加元素。
*
* @param key
* @param value
* @return
*/
public void rightPushIfPresent(String key, Object value) {
redisTemplate.opsForList().rightPushIfPresent(key, value);
}
/**
* 向已存在的集合中添加元素。
*
* @param key
* @return
*/
public long listLength(String key) {
return redisTemplate.opsForList().size(key);
}
/**
* 移除集合中的左边第一个元素。
*
* @param key
* @return
*/
public void leftPop(String key) {
redisTemplate.opsForList().leftPop(key);
}
/**
* 移除集合中左边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
*
* @param key
* @return
*/
public void leftPop(String key, long timeout, TimeUnit unit) {
redisTemplate.opsForList().leftPop(key, timeout, unit);
}
/**
* 移除集合中右边的元素。
*
* @param key
* @return
*/
public void rightPop(String key) {
redisTemplate.opsForList().rightPop(key);
}
/**
* 移除集合中右边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
*
* @param key
* @return
*/
public void rightPop(String key, long timeout, TimeUnit unit) {
redisTemplate.opsForList().rightPop(key, timeout, unit);
}
}
使用:
package com.jerry.springbootredis.demos;
import com.jerry.springbootredis.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/redis")
public class BasicController {
@Autowired
private RedisUtils redisUtils;
// @GetMapping("save")
// public String save(String key, String value) {
// redisTemplate.opsForValue().set(key, value);
// return "ok";
// }
@GetMapping("sMap")
public String sMap(String key, String hashKey, String hashVal) {
Map<String, String> map = new HashMap<>();
map.put(hashKey, hashVal);
redisUtils.add(key, map);
return "ok";
}
@GetMapping("getMapString")
public String getMapString(String key, String hasKey) {
String mapString = redisUtils.getMapString(key, hasKey);
return mapString;
}
@GetMapping("getMapInt")
public String getMapInt(String key, String hasKey) {
Integer mapInt = redisUtils.getMapInt(key, hasKey);
return mapInt.toString();
}
//.....
}
代码demo地址github🤖
鸣谢:稀土掘金:一缕82年的清风(SpringBoot教程(十四) | SpringBoot集成Redis(全网最全))
总结
以上就是Redis的命令行基本命令介绍,希望对你有所帮助。如果想了解更多关于Redis的内容,可以参考Redis官方文档。