应用背景
将一些经常展现和不会频繁变更的数据,存放在存取速率更快的地方。 缓存就是一个存储器,在技术选型中,常用 Redis 作为缓存数据库。缓存主要是在获取资源方便性能优化的关键方面。
更新缓存模式
Cache aside
这是最常用最常用的pattern了。其具体逻辑如下:
失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从cache中取数据,取到后返回。
更新:先把数据存到数据库中,成功后,再让缓存失效。
Read through
可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。
Write through
Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)
Write behind caching
Write Behind 又叫 Write Back。类似Linux文件系统的Page Cache的算法吗?是的,你看基础这玩意全都是相通的。一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,write backg还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。
SpringBoot 中使用Redis数据库
基于springboot-data提供的redis集成方案
提供了对不同 Redis 客户端的整合(Lettuce 和 Jedis),默认是 Lettuce
提供了 RedisTemplate 统一 API 来操作 Redis
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
支持 Redis 的发布订阅模型
支持 Redis 哨兵和 Redis 集群
支持基于 Lettuce 的响应式编程
支持基于 JDK、JSON、字符串、Spring 对象的数据序列化及反序列化
支持基于 Redis 的 JDKCollection 实现
pom.xml
<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>
SpringBoot 2开始默认的Redis客户端实现是Lettuce,同时你需要添加commons-pool2的依赖。
application.yml配置
单机版
spring:redis:database: 2 #数据库编号host: 82.157.125.55 #IP地址port: 6379 #端口password: lhyg@8888 #密码lettuce:shutdown-timeout: 200mspool:max-active: 7 #连接池最大连接数(负值表示没有限制)max-idle: 7 #连接池最大空闭连接数min-idle: 2 #连接汉最小空闲连接数max-wait: -1ms
集群版
spring:redis:database: 0pool:max-active: 100 #连接池最大连接数(负值表示没有限制)max-wait: 3000 #连接池最大阻塞等待时间(负值表示没有限制)max-idle: 200 #连接池最大空闭连接数min-idle: 50 #连接汉最小空闲连接数timeout: 600 #连接超时时间(毫秒)cluster:nodes:- 192.168.75.132:6380- 192.168.75.132:6381- 192.168.75.132:6382- 192.168.75.132:6383- 192.168.75.132:6384- 192.168.75.132:6385
哨兵版
RedisTemplate
API | 返回值类型 | 说明 |
redisTemplate.opsForValue() | ValueOperations | 操作 String 类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作 Hash 类型数据 |
redisTemplate.opsForList() | ListOperations | 操作 List 类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作 Set 类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作 SortedSet 类型数据 |
redisTemplate | 通用的命令 |
基本使用
package com.study.redis;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.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;/*** @author JZB* @ClassName BaseRedisTest* @date 2023年02月16日 17:54* @Description redis基本使用*/
@SpringBootTest
public class BaseRedisTest {@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void testString () {redisTemplate.opsForValue().set("name", "xiaobai");Object name = redisTemplate.opsForValue().get("name");System.out.println(name);}// Hash 类型@Testpublic void testHash () {redisTemplate.opsForHash().put("user1", "name", "小白");redisTemplate.opsForHash().put("user1", "age", "34");Map map = redisTemplate.opsForHash().entries("user1");System.out.println(map);}// List 类型@Testpublic void testList () {redisTemplate.opsForList().leftPushAll("names", "小宝", "学习", "更努力");List<String> names = redisTemplate.opsForList().range("names", 0, 3);System.out.println(names);}// Set 类型@Testpublic void testSet () {redisTemplate.opsForSet().add("study", "每天", "进步", "一点点");Set<String> set = redisTemplate.opsForSet().members("study");System.out.println(set);}// SortedSet 类型@Testpublic void testSortedSet () {redisTemplate.opsForZSet().add("class", "xiaobai", 90);Set aClass = redisTemplate.opsForZSet().rangeByScore("class", 90, 100);System.out.println(aClass);Set<ZSetOperations.TypedTuple<String>> set = new HashSet<>();set.add(new DefaultTypedTuple<>("小白", 88.0));set.add(new DefaultTypedTuple<>("小叶", 94.0));set.add(new DefaultTypedTuple<>("小童", 84.0));set.add(new DefaultTypedTuple<>("小祥", 82.0));set.add(new DefaultTypedTuple<>("小璞", 99.0));redisTemplate.opsForZSet().add("class", set);Set aClass1 = redisTemplate.opsForZSet().range("class", 0, 6);System.out.println(aClass1);}
}
对象写入
RedisTemplate 可以接收任意 Object 作为值写入 Redis,不过在写入前会把 Object 序列化为字节形式,默认是采用 JDK 序列化。使用config配置RedisTemplate
RedisConfig.java
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.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author JZB* @ClassName RedisConfig* @date 2023年02月16日 15:26* @Description*/
@Configuration
public class RedisConfig {/*** redisTemplate 默认使用JDK的序列化机制, 存储二进制字节码, 所以自定义序列化类* @param redisConnectionFactory redis连接工厂类* @return RedisTemplate*/@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {//创建RedisTemplate对象RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();//设置链接工厂redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置 Key 的序列化 - String 序列化 RedisSerializer.string() => StringRedisSerializer.UTF_8redisTemplate.setKeySerializer(RedisSerializer.string());redisTemplate.setHashKeySerializer(RedisSerializer.string());// 设置 Value 的序列化 - JSON 序列化 RedisSerializer.json() => GenericJackson2JsonRedisSerializerredisTemplate.setValueSerializer(RedisSerializer.json());redisTemplate.setHashValueSerializer(RedisSerializer.json());// 返回return redisTemplate;}
}
注意此处只是将对象转成了String类,并没有在redis中加密存储,如若加密,当如下写
测试代码
package com.study.redis;import com.study.redis.entity.UserInfo;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.redis.core.RedisTemplate;import javax.annotation.Resource;/*** @author JZB* @ClassName ObjectRedisTest* @date 2023年02月17日 13:48* @Description redis对象序列化*/
@SpringBootTest
@ComponentScan("com.study.redis")
public class ObjectRedisTest {@Resourceprivate RedisTemplate<String,Object> redisTemplate;@Testvoid testSaveUser() {UserInfo user=new UserInfo();user.setUsername("每天进步一点点");user.setPassword("redis");user.setId(1);redisTemplate.opsForValue().set("user", user);UserInfo user1 = (UserInfo) redisTemplate.opsForValue().get("user");System.out.println(user1);//UserInfo(id=1, username=每天进步一点点, password=redis)}}
redis 中数据过期问题
有时需要监听Redis键的事件,从而在Redis事件发生时添加一些后续操作;在实际开发过程中,有这么一个需求,监听Redis中的键过期状态,从而去更改程序中数据的状态;
redis 配置
redis.conf
Redis中默认的notify-keyspace-events的配置值为空。
可以的配置值如下:
notify-keyspace-events 的参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知:
字符 | 发送的通知 |
K | 键空间通知,所有通知以__keyspace@ __ 为前缀 |
E | 键事件通知,所有通知以 keyevent@ 为前缀 |
g | DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知 |
$ | 字符串命令的通知 |
l | 列表命令的通知 |
s | 集合命令的通知 |
h | 哈希命令的通知 |
z | 有序集合命令的通知 |
x | 过期事件,每当有过期键被删除时发送 |
e | 驱逐事件,每当有键因为maxmemory政策而被删除时发送 |
A | 参数 g$lshzxe 的别名 |
输入的参数中至少要有一个K或者E,否则其余参数不会有任何的通知生效。
如果想订阅所有的通知,直接设置为AKE。
举例,我们想在键过期被删除时得到通知并且获取到别名,可以进行如下的配置:
config set notify-keyspace-events Ex
测试
1 设置一个key
setex name jxl 10
2 在Redis命令行中订阅这个频道的通知:
psubscribe __keyevent@0__:expired
3 当name值过期,被删除时,我们就可以看到如下的通知了:
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "name"
开发实现
实现方法一
1 实现MessageListener接口
package com.study.redis.listener;import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;import java.util.Date;/*** @author JZB* @ClassName RedisMessageListener* @date 2023年02月17日 17:19* @Description redis事件监听实现*/
@Component
public class RedisMessageListener implements MessageListener {@Overridepublic void onMessage(Message message, byte[] pattern) {System.out.println("监听到事件: key="+message.toString()+new Date().toString());}
}
2 在配置类中将这个事件监听添加到Redis容器中
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.study.redis.listener.RedisMessageListener;
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.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author JZB* @ClassName RedisConfig* @date 2023年02月16日 15:26* @Description*/
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {//创建RedisTemplate对象RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();//设置链接工厂redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置 Key 的序列化 - String 序列化 RedisSerializer.string() => StringRedisSerializer.UTF_8redisTemplate.setKeySerializer(RedisSerializer.string());redisTemplate.setHashKeySerializer(RedisSerializer.string());// 设置 Value 的序列化 - JSON 序列化 RedisSerializer.json() => GenericJackson2JsonRedisSerializerredisTemplate.setValueSerializer(RedisSerializer.json());redisTemplate.setHashValueSerializer(RedisSerializer.json());// 返回return redisTemplate;}@Beanvoid RedisMessageListener(){new RedisMessageListener();}@BeanRedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, RedisMessageListener myMessageListener) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);container.addMessageListener(myMessageListener,new PatternTopic("__keyevent@*__:expired"));return container;}
}
3 测试
import com.study.redis.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;@SpringBootApplication
public class SpringbootRedisApplication implements CommandLineRunner {public static void main(String[] args) {SpringApplication.run(SpringbootRedisApplication.class, args);}@Resourceprivate RedisTemplate<String ,Object> redisTemplate;@Overridepublic void run(String... args) throws Exception {UserInfo user=new UserInfo();user.setUsername("每天进步一点点");user.setPassword("redis");user.setId(1);redisTemplate.opsForValue().set("us",user,5, TimeUnit.SECONDS);System.out.println("设置成功"+new Date().toString());}
}
UserInfo是实体,此处测试也可以使用JUnit测试
4 结果
设置成功Fri Feb 17 17:53:17 CST 2023
监听到事件: key=usFri Feb 17 17:53:47 CST 2023
实现方法二
1 继承redis提供的KeyExpirationEventMessageListener类
@Component
@Slf4j
public class MyRedisKeyExpirationListener extends KeyExpirationEventMessageListener {public MyRedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}@Overridepublic void onMessage(Message message, byte[] pattern) {log.debug("键值{}",message.toString());}
}
这种方法省去了将监听器添加到容器中的步骤,因为在KeyExpirationEventMessageListener类中它已经将监听添加到容器中
private static final Topic KEYEVENT_EXPIRED_TOPIC = new PatternTopic("__keyevent@*__:expired");protected void doRegister(RedisMessageListenerContainer listenerContainer) {listenerContainer.addMessageListener(this, KEYEVENT_EXPIRED_TOPIC);
}
集成KeyExpirationEventMessageListener类无疑是一种简单的模式,但是它只能监听key失效的情况,于是我参考方法一和方法二,将Topic配置交给相应的监听器实现如下:
实现方法三
public abstract class AbstractMessageListener implements MessageListener, InitializingBean {private final RedisMessageListenerContainer listenerContainer;protected AbstractMessageListener(RedisMessageListenerContainer listenerContainer) {this.listenerContainer = listenerContainer;}public void afterPropertiesSet() throws Exception {listenerContainer.addMessageListener(this, getTopic());}protected abstract Topic getTopic();
}
通过实现上面的抽象接口从而实现监听格式和事件的注册关系
@Slf4j
public class KeyExpirationMessageListener extends AbstractMessageListener {public KeyExpirationMessageListener(RedisMessageListenerContainer listenerContainer, List<KeyHandle> keyHandleList) {super(listenerContainer);}@Overrideprotected Topic getTopic() {return new PatternTopic("__keyevent@*__:expired");}@Overridepublic void onMessage(Message message, byte[] bytes) {log.debug("键值{}",message.toString());}
}
基于jedis使用redis方案
Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等,推荐使用Jedis。
redis客户端对比 jedis、redisson、lettuce
官网
Jedis api 在线网址:http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.htmlredisson 官网地址:https://redisson.org/redisson git项目地址:https://github.com/redisson/redissonlettuce 官网地址:https://lettuce.io/lettuce git项目地址:https://github.com/lettuce-io/lettuce-core
首先,在spring boot2之后,对redis连接的支持,默认就采用了lettuce。这就一定程度说明了lettuce 和Jedis的优劣。
概念
Jedis:是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持
Redisson:实现了分布式和可扩展的Java数据结构。
Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
优点
Jedis:比较全面的提供了Redis的操作特性
Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列
Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作
可伸缩
Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作
lettuce能够支持redis4,需要java8及以上。
lettuce是基于netty实现的与redis进行同步和异步的通信。
lettuce和jedis比较:
jedis使直接连接redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个jedis实例增加物理连接 ;
lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,StatefulRedisConnection是线程安全的,所以一个连接实例可以满足多线程环境下的并发访问,当然这也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
总结:
优先使用Lettuce,如果需要分布式锁,分布式集合等分布式的高级特性,添加Redisson结合使用,因为Redisson本身对字符串的操作支持很差。
在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者 产生唯一的一个递增ID,由于web应用部署在多个机器上,简单的同步加锁是无法实现的,给数据库加锁的话,对于高并发,1000/s的并发,数据库可能由行锁变成表锁,性能下降会厉害。那相对而言,redis的分布式锁,相对而言,是个很好的选择,redis官方推荐使用的Redisson就提供了分布式锁和相关服务。
在官方网站列一些Java客户端访问,有:Jedis/Redisson/Jredis/JDBC-Redis等,其中官方推荐使用Jedis和Redisson。常用Jedis。
实现
pom.xml
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>
yml
spring:redis:port: 6379 #端口号password: 123456 #输入redis数据库密码host: #输入ip地址jedis:pool:max-idle: 6 #最大空闲数max-active: 10 #最大连接数min-idle: 2 #最小空闲数timeout: 2000 #连接超时
JedisConfig
package com.wpc.config.jedis;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;/*** @ClassName JedisConfig* @Description TODO* @Author JZB* @Version 1.0*/
@Configuration
public class JedisConfig {private Logger logger = LoggerFactory.getLogger(JedisConfig.class);@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private int port;@Value("${spring.redis.password}")private String password;@Value("${spring.redis.timeout}")private int timeout;@Value("${spring.redis.jedis.pool.max-active}")private int maxActive;@Value("${spring.redis.jedis.pool.max-idle}")private int maxIdle;@Value("${spring.redis.jedis.pool.min-idle}")private int minIdle;@Beanpublic JedisPool jedisPool(){JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();jedisPoolConfig.setMaxIdle(maxIdle);jedisPoolConfig.setMinIdle(minIdle);jedisPoolConfig.setMaxTotal(maxActive);JedisPool jedisPool=new JedisPool(jedisPoolConfig,host,port,timeout,password);System.out.println("JedisPool连接成功:"+host+"\t"+port);return jedisPool;}
}
测试
package com.study.jedis;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;import java.util.*;/*** @author JZB* @ClassName BaseJedisTest* @date 2023年02月20日 10:56* @Description*/
@SpringBootTest
public class BaseJedisTest {@Autowiredprivate JedisPool jedisPool;@Testpublic void testString() {//在连接池中得到Jedis连接Jedis jedis = jedisPool.getResource();jedis.set("haha", "你好");jedis.set("name", "wangpengcheng");//关闭当前连接jedis.close();}// Hash 类型@Testpublic void testHash() {Jedis jedis = jedisPool.getResource();Map<String, String> user = new HashMap<>();user.put("name", "hash");user.put("age", "20");//方法不添加Object类型jedis.hmset("juser", user);jedis.hset("juser", "sex", "1");System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("juser"));//return Map<String,String>System.out.println("散列hash的所有键为:"+jedis.hkeys("juser"));//return Set<String>System.out.println("散列hash的所有值为:"+jedis.hvals("juser"));//return List<String>System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("juser", "key6", 6));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("juser"));System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("juser", "key6", 3));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));System.out.println("判断hash中是否存在key2:"+jedis.hexists("hash","key2"));System.out.println("判断hash中是否存在key3:"+jedis.hexists("hash","key3"));System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key4"));}}
封装
工具类
package com.study.jedis.untils;import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;/*** @author JZB* @ClassName JedisUtils* @date 2023年02月20日 13:37* @Description*/
public class JedisUtils {@Autowiredprivate JedisPool jedisPool;/*** 获取Jedis资源*/public Jedis getJedis(){return jedisPool.getResource();}/*** 释放Jedis连接*/public void close(Jedis jedis){if(jedis!=null){jedis.close();}}
}
业务JedisServiceImpl类
package com.study.jedis.service.imp;import com.study.jedis.untils.JedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;/*** @author JZB* @ClassName JedisServiceImpl* @date 2023年02月20日 13:38* @Description*/
@Service
public class JedisServiceImpl {@Autowiredprivate JedisUtils jedisUtils;/*** 测试String* 根据key 查询value值*/public String getString(String key){Jedis jedis=jedisUtils.getJedis();String val=null;if(!jedis.exists(key)){val="南京"; jedis.set(key,val);System.out.println(key+"存入Redis中。值是:"+val);}else{val=jedis.get(key);System.out.println("redis值是:"+val);}jedisUtils.close(jedis); //释放资源return val;}
}
测试
@SpringBootTest
public class JedisTests {@Autowiredprivate JedisServiceImpl jedisService;@Testvoid t1(){String val= jedisService.getString("name");System.out.println(val);}
}
Jedis操作Hash类型
package com.study.jedis.service.imp;import com.study.jedis.entity.UserInfo;
import com.study.jedis.untils.JedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;import java.util.HashMap;
import java.util.Map;/*** @author JZB* @ClassName JedisHashServiceImppl* @date 2023年02月20日 13:45* @Description*/
@Service
public class JedisHashServiceImpl {@Autowiredprivate JedisUtils jedisUtils;public UserInfo selectBy(String id){String key="user:id"; //根据规则生成相同规范的keyUserInfo user=new UserInfo();;Jedis jedis=jedisUtils.getJedis();if(!jedis.exists(key)){Map<String,String> map=new HashMap();map.put("id",user.getId().toString());map.put("username",user.getUsername());jedis.hset(key,map);}else{Map<String,String> map= jedis.hgetAll(key);user.setId(Integer.getInteger(map.get("id")));user.setUsername(map.get("name"));}jedisUtils.close(jedis);return user;}
}
测试
@SpringBootTest
public class JedisTests {@Autowiredprivate JedisServiceImpl jedisService;@Testvoid hash(){User user= jedisService.selectBy("101");System.out.println(user);}
}
参考资料
https://blog.csdn.net/xuejue8260/article/details/90264741
https://blog.csdn.net/jiangxiulilinux/article/details/106555799
https://blog.csdn.net/weixin_42001592/article/details/124054738
https://blog.csdn.net/wenkezhuangyuan/article/details/119430545
源码下载
csdn下载