Spring Boot 集成redis

news/2024/4/26 8:43:41/文章来源:https://blog.csdn.net/wujianyouhun/article/details/129149843

应用背景

将一些经常展现和不会频繁变更的数据,存放在存取速率更快的地方。 缓存就是一个存储器,在技术选型中,常用 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

  1. ValueOperations:简单K-V操作

  1. SetOperations:set类型数据操作

  1. ZSetOperations:zset类型数据操作

  1. HashOperations:针对map类型的数据操作

  1. 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比较:

  1. jedis使直接连接redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个jedis实例增加物理连接 ;

lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,StatefulRedisConnection是线程安全的,所以一个连接实例可以满足多线程环境下的并发访问,当然这也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

  1. Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

总结:

  1. 优先使用Lettuce,如果需要分布式锁,分布式集合等分布式的高级特性,添加Redisson结合使用,因为Redisson本身对字符串的操作支持很差。

  1. 在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者 产生唯一的一个递增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下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_72366.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

笔记本cpu温度多少正常?温度过高的4个常见原因

电脑CPU指的是中央处理器&#xff0c;它与电脑运行速度的快慢存在很大关系。如果电脑的处理器温度过高&#xff0c;就会影响我们电脑的运行速度&#xff0c;甚至出现蓝屏、卡顿的情况。 那么&#xff0c;对于电脑来说&#xff0c;笔记本cpu温度多少正常&#xff1f;有什么原因…

macOS Big Sur 11.7.4(20g1220)With OpenCore 0.8.9正式版 and winPE双引导分区原版镜像

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。镜像特点完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引导分区&#xff08;如有需要&#xff0c;可以自行直接替换…

KT1025A蓝牙音频芯片_立讯KC认证FCC测试现场整改记录

目录 一、问题说明简介 测试机构立讯反馈&#xff0c;客户寄的样板进行无线KC【韩国】测试不过&#xff0c;体现在如下两点 蓝牙部分接收杂散不过 蓝牙的发射功率偏低 2.1 单独只给蓝牙部分供电的测试图片--OK 2.2 单独给整板供电--但是使用电池供电 2.3 单独给整板供电-…

关于机器人坐标系变换的笔记

ROS TFros中&#xff0c;可以通过TF Tree来进行获取 机器人不同坐标系之间的转换关系&#xff0c;命令如下&#xff1a;rosrun tf tf_echo base_link head_link1意思为&#xff0c;从源坐标系base_link&#xff0c;到目标坐标系head_link1的变换关系&#xff0c;结果如下所示。…

Crafting interpreters 中文翻译,持续修正

本书在线地址 http://craftinginterpreters.com/ 感谢作者 作者用近 4 年的时间持续创作和改进本书&#xff0c;并把其 Web 版本公开在网上。这本纸质书于今年 7 月出版&#xff0c;立刻在 Hacker News 等网络媒介上引起关注和讨论。 书中作者首先定义了一个动态类型的语言 …

棋牌类游戏测试用例怎么写?我敢打赌你绝对不知道

目录 一&#xff0e;登陆 二&#xff0e;大厅 三&#xff0e;小游戏 四&#xff0e;银行功能 五&#xff0e;其他按钮 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配套学习资料和视频教学 一&#xff0e;登陆 1&#xff0e…

使用拦截器实现登录状态检测(以及在注册拦截器类时要使用ioc中的拦截器类)

拦截器 preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在请求处理之前被调用。该方法在 Interceptor 类中最先执行&#xff0c;用来进行一些前置初始化操作或是对当前请求做预处理&#xff0c;也可以进行一些判断来决定请求是否…

【MyBatis】源码学习 04 - 从 MapperMethod 简单分析一条 SQL 的映射操作流程

文章目录前言参考目录学习笔记1、测试代码说明2、binding 包的主要功能3、获取 Mapper 接口实例过程4、SQL 语句执行流程4.1、方法调用器4.2、MapperMethod 绑定方法4.2.1、SqlCommand4.2.2、MethodSignature4.3、MapperMethod#execute前言 本文内容对应的是书本第 13 章的内容…

循环、函数、对象——js基础练习

目录 一、循环练习 1.1 取款机案例 1.2 九九乘法表 1.3 根据数据生成柱形图 1.4 冒泡排序 1.6综合大练习 二、函数 2.1 转换时间案例 三、对象 1. 遍历数组对象 2. 猜数字游戏 3. 生成随机颜色 4. 学成在线页面渲染案例 一、循环练习 1.1 取款机案例 // 准备一个…

电商项目之Feign与Dubbo技术选型

文章目录1 问题背景2 前言3 思路4 Feign与Dubbo的区别5 总结6 真实案例1 问题背景 电商项目&#xff0c;B端以consul作为注册中心。重构了一个营销服务&#xff0c;以Nacos作为注册中心。B端需要调用营销服务。关于远程调用框架&#xff0c;营销服务用了Dubbo&#xff0c;而B端…

黑马程序员-Linux网络编程-01

目录 课程链接 协议 分层模型 网络传输数据封装流程 以太网帧和ARP请求 IP协议 TCP协议 BS与CS模型对比 套接字 网络字节序 IP地址转换函数 sockaddr地址结构 socket模型创建流程 socket()和bind() listen()和accept()​ 课程链接 03-协议_哔哩哔哩_bilibili 协…

java并发笔记

文章目录HashMapput方法resize方法ConcurrentHashMapput方法initTable方法sizectl代表什么&#xff1a;扩容计数器ConcurrentHashMap的读操作会阻塞嘛AQS唤醒线程时&#xff0c;AQS为什么从后往前遍历&#xff1f;AQS为什么要有一个虚拟的head节点AQS为什么用双向链表&#xff…

万字C语言学习笔记,带你学C带你飞(四)

文章目录单链表typedef1、基础typedef2、进阶typedef共用体枚举类型1、声明枚举类型2、定义枚举变量位域位操作文件的写入与写出C语言学习笔记&#xff0c;记录所学&#xff0c;便于复习。 由于篇幅过大&#xff0c;考虑到观感&#xff0c;准备分多篇记录。学习视频链接&#x…

Vue3.x使用Echarts绘制世界地图并进行定点

Vue3.x使用Echarts绘制世界地图并进行定点 一、需求 绘制世界地图并根据返回经纬度数据进行定点将定点数据展示在世界地图内 二、解决 绘制世界地图&#xff0c;利用Echarts图表组件时间&#xff0c;需要世界地图Geojson数据的可以在资源中下载世界地图Geojson数据-Javascr…

2022FALL嵌入式大纲

Jamslade 部分内容有遗漏&#xff0c;可结合 超文本 2022FALL《嵌入式系统原理》期末复习笔记 一起观看 文章目录嵌入式系统片上系统实时系统硬实时系统软实时系统伪指令DMA传输波特率单/半双/全双工通信&#xff1b;对齐/非对齐访问地址译码代码临界区RISCBIOSUARTSPII2CWDTRO…

2.5|shell简介|Linux支持的网络协议|Linux的网络服务

shell简介shell是一种具备特殊功能的程序&#xff0c;它是介于使用者和Unix/Linux操作系统内核间的一个接口。操作计算机需要通过命令&#xff08;command&#xff09;或是程序&#xff08;program&#xff09;&#xff1b;程序需要编译器&#xff08;compiler&#xff09;将程…

东南大学研究生英语18-19秋试卷解析

写在前面 作者&#xff1a;夏日 博客地址&#xff1a;https://blog.csdn.net/zss192 本文为东南大学研究生英语上学期18-19年期末试卷解析&#xff0c;答案来源于 ChatGPT International Conference 单选题 1.A presenter is supposed to do the following in an introdu…

【数据结构趣味多】八大排序

目录 1.直接插入排序 基本思想 代码实现&#xff1a; 直接插入排序的特性总结&#xff1a; 2.希尔排序 基本思想 代码实现 &#xff08;递归实现&#xff09; 希尔排序的特性总结 3.直接选择排序 基本思想 代码实现&#xff1a; 直接选择排序的特性总结 4.堆排序 …

Springboot 全局异常处理类

全局异常处理 在开发过程中&#xff0c;不管是Dao、Servie、Controller,层都有可能发生异常&#xff0c;对于异常处理&#xff0c;通常是try&#xff0d;catch或者直接throw&#xff0c;这会让try&#xff0d;catch的代码在代码中任意出现&#xff0c;系统的代码耦合度高&…

深入Spring底层透析bean生命周期及循环引用的醍醐灌顶篇

目录前言一.Bean的生命周期1.1 Bean的实例化阶段1.2 Bean的初始化阶段&#xff08;重点&#xff09;1.3 Bean的完成阶段二.循环引用问题(面试常问题&#xff09;三.Spring的三级缓存&#xff08;重点来了&#xff09;四.完整的Spring IoC整体总结前言 本篇是接着bean的创建基本…