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: localhostpassword: #your passwordport: 6379database: 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 {@Autowiredprivate 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 {@Beanpublic 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和publicom.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 序列化方式使用stringSerialtemplate.setKeySerializer(stringSerial);// redis value 序列化方式使用jacksontemplate.setValueSerializer(jacksonSeial);// redis hash key 序列化方式使用stringSerialtemplate.setHashKeySerializer(stringSerial);// redis hash value 序列化方式使用jacksontemplate.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 {@Autowiredprivate 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 {@Autowiredprivate 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官方文档。