当前位置: 首页 > news >正文

Day115 SpringBoot整合Redis,RedisTemplate和注解两种方式的使用

Day115 SpringBoot整合Redis,注解方式和RedisTemplate方式

1.注解方式使用 Redis 缓存

使用缓存有两个前置步骤

1.在 pom.xml 引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 整合redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- springboot测试 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
2.在启动类上加注解 @EnableCaching
@SpringBootApplication
@EnableCaching
public class RedisApplication {public static void main(String[] args) {SpringApplication.run(RedisApplication.class, args);}
}

常用的注解有以下几个@Cacheable、@CachePut、@CacheEvict

1.1 添加缓存

缓存的key就是配置在注解里面的值product::123,值是你方法的返回值,如果没有返回值,值就是NullValue

在需要加缓存的方法上添加注解 @Cacheable(cacheNames = "product", key = "123")

cacheNameskey 都必须填,如果不填 key ,默认的 key 是当前的方法名,更新缓存时会因为方法名不同而更新失败

eg:在订单列表上加缓存

    @RequestMapping(value = "/list", method = RequestMethod.GET)@Cacheable(cacheNames = "product", key = "123")public List<Product> list() {List<Product> products=new ArrayList<>();for (int i = 0; i < 3; i++) {Product product=new Product();product.setId(i+1);product.setName("第"+(i+1)+"件商品");product.setPrice((double) ((i+1)*100));products.add(product);}return products;}

可能会报错,原因是对象未序列化。让对象实现 Serializable 方法即可

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {private Integer id;private String name;private Double price;
}

重启项目访问订单列表,在 rdm 里查看 Redis 缓存,有 product::123 说明缓存成功

1.2 更新缓存

在需要更新缓存的方法上加注解: @CachePut(cacheNames = "product", key = "123")

注意

1.cacheNameskey 要跟 @Cacheable() 里的一致,才会正确更新

2.@CachePut()@Cacheable() 注解的方法返回值要一致

1.3 删除缓存

在需要删除缓存的方法上加注解:@CacheEvict(cacheNames = "product", key = "123"),执行完这个方法之后会将 Redis 中对应的记录删除

1.4 其他常用功能

1.cacheNames 也可以统一写在类上面, @CacheConfig(cacheNames = "product") ,具体的方法上就不用写啦

@RestController
@CacheConfig(cacheNames = "product")
public class ProdectController {}

2.Key 也可以动态设置为方法的参数

	@GetMapping("/detail")@Cacheable(cacheNames = "product", key = "#id")public Product detail(@RequestParam("id") Integer id){if (id==1){return new Product(1,"电冰箱",20d);}else if (id==2){return new Product(2,"洗衣机",30d);}else {return new Product(3,"彩电",40d);}}

如果参数是个对象,也可以设置对象的某个属性为 key。比如其中一个参数是 user 对象,key 可以写成 key="#user.id"

3.缓存还可以设置条件

eg:设置当 openid 的长度大于2时才缓存

	@GetMapping("/detailOnCondition")@Cacheable(cacheNames = "product", key = "#id", condition = "#id > 2")public void detail(@RequestParam("id") String id){System.out.println("id是"+id);}

还可以指定 unless 即条件不成立时缓存。#result 代表返回值,意思是当返回码不等于 0 时不缓存,也就是等于 0 时才缓存

	@GetMapping("/detailOnConditionAndUnless")@Cacheable(cacheNames = "product", key = "#id", condition = "#id > 2", unless = "#result!= 0")public Integer detailOnConditionAndUnless(@RequestParam("id") Integer id){if (id==3){return 0;}else {return 1;}}

2.RedisTemplate 使用 Redis 缓存

与使用注解方式不同,注解方式可以零配置,只需引入依赖并在启动类上加上 @EnableCaching 注解就可以使用;而使用 RedisTemplate 方式麻烦些,需要做一些配置

2.1 Redis 配置

第一步还是引入依赖和在启动类上加上 @EnableCaching 注解

然后在 application.yml 文件中配置 Redis

spring:redis:port: 6379database: 0host: 127.0.0.1password:jedis:pool:max-active: 8max-wait: -1msmax-idle: 8min-idle: 0timeout: 5000ms

然后写个 RedisConfig.java 配置类

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import java.net.UnknownHostException;@Configuration
public class RedisConfig {@Bean@ConditionalOnMissingBean(name = "redisTemplate")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();template.setConnectionFactory(redisConnectionFactory);template.setKeySerializer(jackson2JsonRedisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashKeySerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}@Bean@ConditionalOnMissingBean(StringRedisTemplate.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}
}

Redis 的配置就完成了

2.2 Redis 的数据结构类型

Redis 可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(哈希)和 Zset(有序集合)

结构类型结构存储的值结构的读写能力
字符串(String)可以是字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操作;对象和浮点数执行自增或者自减
列表(List)一个链表,链表上的每个节点都包含了一个字符串,有序,可重复从链表的两端推入或者弹出元素;根据偏移量对链表进行修剪;读取单个或者多个元素;根据值来查找或者移除元素
集合(Set)被包含的每个字符串都是无序,不可重复添加、获取、移除单个元素;检查一个元素是否存在于某个集合中;计算交集、并集、差集;从集合里卖弄随机获取元素
哈希(Hash)包含键值对的无序散列表添加、获取、移除单个键值对;获取所有键值对
有序集合(ZSet)字符串成员与浮点数分值之间的有序映射,元素的排列顺序由分值的大小决定添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素

2.3 操作

RedisTemplate 对五种数据结构分别定义了操作

操作字符串:redisTemplate.opsForValue();

操作hash:redisTemplate.opsForHash();

操作list:redisTemplate.opsForList();

操作set:redisTemplate.opsForSet();

操作有序set:redisTemplate.opsForZSet();

如果操作字符串的话,建议用 StringRedisTemplate

StringRedisTemplate 与 RedisTemplate 的区别

1.StringRedisTemplate 继承了 RedisTemplate

2.RedisTemplate 是一个泛型类,而 StringRedisTemplate 则不是

3.StringRedisTemplate 只能对 key=String,value=String 的键值对进行操作,RedisTemplate 可以对任何类型的 key-value 键值对操作

4.两者的数据是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate中 的数据

2.4 项目中使用

在需要使用 Redis 的地方,用 @Autowired 注入进来

@Autowired
RedisTemplate redisTemplate;@Autowired
StringRedisTemplate stringRedisTemplate;
用 RedisTemplate 操作 Hash
package io.redis.demo;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.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;import java.util.List;@SpringBootTest
public class RedisTemplateTest {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Testpublic void test1(){redisTemplate.opsForValue().set("name","zhangsan");String name = (String)redisTemplate.opsForValue().get("name");System.out.println(name);}@Testpublic void test2(){stringRedisTemplate.opsForValue().set("name","zhangsan");String name = stringRedisTemplate.opsForValue().get("name");System.out.println(name);}@Testpublic void test3(){redisTemplate.opsForHash().put("produce","1","电视机");redisTemplate.opsForHash().put("produce","2","冰箱");redisTemplate.opsForHash().put("produce","3","彩电");redisTemplate.opsForHash().put("produce","4","自行车");String name = (String) redisTemplate.opsForHash().get("produce", "4");System.out.println(name);}@Testpublic void test4(){redisTemplate.opsForList().leftPush("name","zhangfei");redisTemplate.opsForList().leftPush("name","liubei");redisTemplate.opsForList().leftPush("name","guanyu");List names = redisTemplate.opsForList().range("name", 0, -1);for (Object name : names) {System.out.println(name);}}
}

3.Redis 实现分布式锁

实现分布式锁之前先看两个 Redis 命令:

SETNX:将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做

返回值, 特定值:

1:如果key被设置了

0:如果key没有被设置

eg:

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 

GETSET:自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误

设计:

GETSET可以和INCR一起使用实现支持重置的计数功能。举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0

这可以通过GETSET mycounter “0”来实现:

INCR mycounter
GETSET mycounter "0"
GET mycounter

返回值:返回之前的旧值,如果之前Key不存在将返回null

eg:

redis> INCR mycounter
(integer) 1
redis> GETSET mycounter "0"
"1"
redis> GET mycounter
"0"
redis>

这两个命令在 java 中对应为 setIfAbsentgetAndSet

分布式锁的实现:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;@Component
@Slf4j
public class RedisLock {@AutowiredStringRedisTemplate redisTemplate;/*** 加锁* @param key* @param value 当前时间 + 超时时间*/public boolean lock(String key, String value){if (redisTemplate.opsForValue().setIfAbsent(key, value)){return true;}//解决死锁,且当多个线程同时来时,只会让一个线程拿到锁String currentValue = redisTemplate.opsForValue().get(key);//如果过期if (!StringUtils.isEmpty(currentValue) &&Long.parseLong(currentValue) < System.currentTimeMillis()){//获取上一个锁的时间String oldValue = redisTemplate.opsForValue().getAndSet(key, value);if (StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){return true;}}return false;}/*** 解锁*/public void unlock(String key, String value){try {String currentValue = redisTemplate.opsForValue().get(key);redisTemplate.opsForValue().getOperations().delete(key);           }catch (Exception e){log.error("【redis锁】解锁失败, {}", e);}}
}

使用:

@SpringBootTest
@RunWith(SpringRunner.class)
public class LockTest {/*** 模拟秒杀*/@AutowiredRedisLock redisLock;//超时时间10sprivate static final int TIMEOUT = 10 * 1000;@Testpublic void secKill(){String productId="1";long time = System.currentTimeMillis() + TIMEOUT;//加锁if (!redisLock.lock(productId, String.valueOf(time))){throw new RuntimeException("人太多了,等会儿再试吧~");}//具体的秒杀逻辑System.out.println("秒杀的业务逻辑");//解锁redisLock.unlock(productId, String.valueOf(time));}}
http://www.dtcms.com/a/316013.html

相关文章:

  • SQL164 删除表
  • 输入12-21V输出5V 10A电源转换芯片方案
  • 什么是模型并行?
  • C语言基础_随机数、数组、函数、指针
  • 注意点:如何使用conda创建虚拟环境并使用虚拟环境以及当安装相关库时,如何指定安装到那个环境里面 ---待看
  • Java中公用字符串工具类拿走、直接用
  • 山东省天地图API申请并加载到QGIS和ArcGIS Pro中
  • 什么是mysql的垂直分表,理论依据是什么,如何使用?
  • 无人机光伏识别误检率↓83%!陌讯多模态融合算法实战解析
  • PHP获取淘宝商品详情返回参数详解
  • K8S 性能瓶颈排查
  • 路由的类型
  • Linux驱动学习(八)设备树
  • 无人机 × 巡检 × AI识别:一套可复制的超低延迟低空视频感知系统搭建实践
  • BloodHound 8.0 首次亮相,在攻击路径管理方面进行了重大升级
  • 03-顺序表
  • Spring之【详解FactoryBean】
  • 小程序实时保存优化
  • WWDC 25 极地冰原撸码危机:InlineArray 与 Span 的绝地反击
  • Dell电脑Windows系统更新后声卡驱动无法识别插线耳机问题
  • WebRTC音视频编码模块深度解析:从编解码器到自适应码率控制(2025技术实践)
  • 【安卓][Mac/Windows】永久理论免费 无限ip代理池 - 适合临时快速作战
  • Java+Redis+SpringBoot定时器-定时发布商品
  • 使用vscode编写markdown文档(使用Markdown Preview Enhanced和markdownlint两个插件)以及若干配置
  • Patsy的dmatrix() 函数
  • Docker概述
  • MySQL主从复制部署
  • leetcode700:二叉搜索树中的搜索(递归与迭代双解法)
  • 高可用微服务架构实战:Nacos集群+Nginx负载均衡,Spring Cloud无缝对接
  • qt窗口--01