谷粒商城【成神路】-【10】——缓存

目录
🧂1.引入缓存的优势
🥓2.哪些数据适合放入缓存
🌭3.使用redis作为缓存组件
🍿4.redis存在的问题
🧈5.添加本地锁
🥞6.添加分布式锁
🥚7.整合redisson作为分布式锁
🚗🚗🚗1.引入缓存的优势
为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而db承担数据落盘工作。
🚗🚗🚗2.哪些数据适合放入缓存
- 即时性、数据—致性要求不高的
- 访问量大且更新频率不高的数据(读多,写少)
🚗🚗🚗3.使用redis作为缓存组件
先确保reidis正常启动
3.1配置redis
- 1.引入依赖
<!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
- 2.配置reids信息
springredis:port: 6379host: ip地址password: XXX
3.2优化查询
之前都是从数据库查询的,现在加入缓存逻辑~
/*** 使用redis缓存*/@Overridepublic Map<String, List<Catalog2Vo>> getCatalogJson() {//1.加入缓存,缓存中存的数据全都是jsonString catalogJson = redisTemplate.opsForValue().get("catalogJson");if (StringUtils.isEmpty(catalogJson)) {//2.缓存中如果没有,再去数据库查找Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();//3.将数据库查到的数据,将对象转换为json存放到缓存String s = JSON.toJSONString(catalogJsonFromDB);redisTemplate.opsForValue().set("catalogJson", s);}//4.从缓存中获取,转换为我们指定的类型Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});return result;}/*** 从数据库查询并封装的分类数据** @return*/public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {/*** 优化:将数据库查询的多次变为一次*/List<CategoryEntity> selectList = baseMapper.selectList(null);//1.查出所有1级分类List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);//2.封装数据Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {//1.每一个一级分类List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());List<Catalog2Vo> catalog2Vos = null;if (categoryEntities != null) {catalog2Vos = categoryEntities.stream().map(l2 -> {Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());//找二级分类的三级分类List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());if (categoryEntities3 != null) {List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());return catalog3Vo;}).collect(Collectors.toList());vo.setCatalog3List(collect);}return vo;}).collect(Collectors.toList());}return catalog2Vos;}));return parentCid;}
3.3测试
在本地第一次查询后,查看redis,发现redis已经存储

使用JMeter压测一下

🚗🚗🚗4.redis存在的问题
4.1缓存穿透
- 缓存穿透: 查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
- 解决方案:null结果缓存,并加入短暂过期时间
4.2缓存雪崩
- 缓存雪崩: 在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,大量请求全部转发到DB, DB瞬时压力过重雪崩。
- 解决方案:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
4.3缓存击穿
缓存击穿 :对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。
解决方案:枷锁 大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db
🚗🚗🚗5.添加本地锁

若在缓存redis中没有查到,也去数据库查询,在查询数据库时,添加本地锁,在查之前,再次判断缓存reids中是否有数据,如果有,直接返回,如果没有,在查数据库,在查完数据库后,由于延迟原因,我们查完数据库时,将数据存放到缓存中,然后在释放锁
/*** 使用redis缓存*/@Overridepublic Map<String, List<Catalog2Vo>> getCatalogJson() {//1.使用redis缓存,存储为json对象String catalogJson = redisTemplate.opsForValue().get("catalogJson");//2.判断缓存中是否有if (StringUtils.isEmpty(catalogJson)) {System.out.println("缓存没有命中~查询数据库...");//3.如果缓存中没有,从数据库Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();return catalogJsonFromDB;}System.out.println("缓存命中....直接返回");//5.如果缓存中有,转换为我们需要的类型Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});return result;}/*** 从数据库查询并封装的分类数据** @return*/public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {/*** 优化:将数据库查询的多次变为一次*///TODO 本地锁,在分布式下,必须使用分布式锁//加锁,防止缓存击穿,使用同一把锁synchronized (this) {//加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库String catalogJson = redisTemplate.opsForValue().get("catalogJson");if (!StringUtils.isEmpty(catalogJson)) {//如果缓存中有,从缓存中获取Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});return result;}System.out.println("查询了数据库~");List<CategoryEntity> selectList = baseMapper.selectList(null);//1.查出所有1级分类List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);//2.封装数据Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {//1.每一个一级分类List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());List<Catalog2Vo> catalog2Vos = null;if (categoryEntities != null) {catalog2Vos = categoryEntities.stream().map(l2 -> {Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());//找二级分类的三级分类List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());if (categoryEntities3 != null) {List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());return catalog3Vo;}).collect(Collectors.toList());vo.setCatalog3List(collect);}return vo;}).collect(Collectors.toList());}return catalog2Vos;}));//4.将从数据库中获取的数据,转换为Json存储到redisString s = JSON.toJSONString(parentCid);//设置缓存时间,方式雪崩redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);return parentCid;}}
🚗🚗🚗6.添加分布式锁
使用分布式锁 步骤
- 1.先去redis中设置一个key,为了保持原子性,同时设置过期时间
- 2.判断是否设置成功,成功则继续业务操作,失败则自选再次获取
- 3.执行业务之后,需要删除key释放锁,为了保持原子性,使用lua脚本
/*** 使用redis缓存*/@Overridepublic Map<String, List<Catalog2Vo>> getCatalogJson() {//1.使用redis缓存,存储为json对象String catalogJson = redisTemplate.opsForValue().get("catalogJson");//2.判断缓存中是否有if (StringUtils.isEmpty(catalogJson)) {System.out.println("缓存没有命中~查询数据库...");//3.如果缓存中没有,从数据库Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDBWithRedisLock();return catalogJsonFromDB;}System.out.println("缓存命中....直接返回");//5.如果缓存中有,转换为我们需要的类型Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});return result;}/*** 从数据库中获取数据,使用分布所锁** @return*/public Map<String, List<Catalog2Vo>> getCatalogJsonFromDBWithRedisLock() {//1.占分布式锁,去redis占位String uuid = UUID.randomUUID().toString();//2.设置过期时间,必须和加锁同步Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);if (lock) {System.out.println("获取分布式锁成功..." + redisTemplate.opsForValue().get("lock"));//加锁成功...执行业务Map<String, List<Catalog2Vo>> dataFromDb;try {dataFromDb = getDataFromDb();} finally {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";//删除锁,原子性Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);}return getDataFromDb();} else {//加锁失败...重试//休眠100毫秒try {Thread.sleep(2000);} catch (InterruptedException e) {}return getCatalogJsonFromDBWithRedisLock();//自旋方式}}/*** 提起方法,从数据库中获取** @return*/private Map<String, List<Catalog2Vo>> getDataFromDb() {//加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库String catalogJson = redisTemplate.opsForValue().get("catalogJson");if (!StringUtils.isEmpty(catalogJson)) {//如果缓存中有,从缓存中获取Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});return result;}System.out.println("查询了数据库~");List<CategoryEntity> selectList = baseMapper.selectList(null);//1.查出所有1级分类List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);//2.封装数据Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {//1.每一个一级分类List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());List<Catalog2Vo> catalog2Vos = null;if (categoryEntities != null) {catalog2Vos = categoryEntities.stream().map(l2 -> {Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());//找二级分类的三级分类List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());if (categoryEntities3 != null) {List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());return catalog3Vo;}).collect(Collectors.toList());vo.setCatalog3List(collect);}return vo;}).collect(Collectors.toList());}return catalog2Vos;}));//4.将从数据库中获取的数据,转换为Json存储到redisString s = JSON.toJSONString(parentCid);//设置缓存时间,方式雪崩redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);return parentCid;}
🚗🚗🚗7.整合redisson作为分布式锁
7.1引入依赖
<!--redisson作为所有分布式锁--><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.0</version></dependency>
7.2程序化配置
在配置地址时,一定要添加reds://
@Configuration
public class MyRedissonConfig {@Bean(destroyMethod = "shutdown")public RedissonClient redissonClient() throws IOException {//1.创建配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.20.130:6379");//2.根据config创建redisson实例RedissonClient redissonClient = Redisson.create(config);return redissonClient;}
}
7.3实例解析
- 1.锁的自动续期,如果业务超长,运行期间自动给锁续上新的30秒,不用担心业务超长,所自动过期被删除掉
- 2.加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30秒后自动删除
@AutowiredRedissonClient redissonClient;@ResponseBody@GetMapping("/hello")public String hello() {//1.设置redis的key获取一把锁,只要名字相同,就是同一把锁RLock lock = redissonClient.getLock("my-lock");//2.手动枷锁lock.lock();//阻塞式等待,默认30秒try {//3.执行业务System.out.println("枷锁成功!" + Thread.currentThread().getName());//4.模拟业务消耗时间Thread.sleep(20000);} catch (Exception e) {} finally {//3.释放锁System.out.println("释放锁~"+Thread.currentThread().getName());lock.unlock();//不删除,默认30秒后过期,自动删除}return "hello";}
- 3.如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
- 4.如果未指定锁的超时时间,就使用30*1000【LockWatchingTimeOut看门狗默认时间】
- 只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔(【LockWatchingTimeOut看门狗默认时间】/3)折磨长时间自动续期;
7.4读写锁
- 1.保证一定能读到最新数据,修改期间,写锁是一个排他锁,读锁是一个共享锁
- 2.读+读:相当于并发,在redis中记录好,都会读取成功
- 3.写+读:等待写锁释放
- 4.写+写:阻塞方式
- 5.读+写:有读锁,写也需要等待
@GetMapping("/write")@ResponseBodypublic String write() {RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");String word = "";RLock rLock = readWriteLock.writeLock();try {//该数据加写锁,读数据加读锁rLock.lock();word = UUID.randomUUID().toString();Thread.sleep(3000);redisTemplate.opsForValue().set("writeValue", word);} catch (InterruptedException e) {e.printStackTrace();} finally {rLock.unlock();}return word;}/*** 读写锁* @return*/@GetMapping("/read")@ResponseBodypublic String read() {String word = "";RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");//加读锁RLock rLock = readWriteLock.readLock();rLock.lock();try {word = redisTemplate.opsForValue().get("writeValue");} catch (Exception e) {e.printStackTrace();}finally {rLock.unlock();}return word;}
相关文章:
谷粒商城【成神路】-【10】——缓存
目录 🧂1.引入缓存的优势 🥓2.哪些数据适合放入缓存 🌭3.使用redis作为缓存组件 🍿4.redis存在的问题 🧈5.添加本地锁 🥞6.添加分布式锁 🥚7.整合redisson作为分布式锁 🚗…...
Facebook、亚马逊账号如何养号?
之前我们讨论过很多关于代理器的问题。它们的工作原理是什么?在不同的软件中要使用那些代理服务器?这些代理服务器之间的区别是什么?什么是反检测浏览器等等。 除了这些问题,相信很多人也会关心在使用不同平台的时代理器的选择问题。比如,为什么最好…...
Milvus的相似度指标
官网:https://milvus.io/docs/metric.md版本: v2.3.x 在 Milvus 中,相似度度量用于衡量向量之间的相似度。选择良好的距离度量有助于显着提高分类和聚类性能。下表展示了这些广泛使用的相似性指标如何与各种输入数据形式和 Milvus 索引相匹配。 一、浮…...
如何在unity中实现倒计时
public class showtime : MonoBehaviour {public TextMeshProUGUI Countdown;void Update(){if (Input.GetKeyDown(KeyCode.Space))//如果按下空格后开始倒计时{StartCoroutine(hahaha());}}IEnumerator hahaha()//声明了一个协程函数 hahaha{int time 10;Countdown.text tim…...
蓝桥杯简单题,公司名称
题目链接(需要登录) #include <iostream> #include <cstring> #include <algorithm> using namespace std; bool lanqiao(string str,int len){ sort(str.begin(),str.end());//对str按照ascii排序if(str.find("Laainoq")s…...
【linux】02 :Linux基础命令
1.掌握linux系统的目录结构 linux只有一个顶级目录,称之为:根目录。 windows系统有多个顶级目录,即各个盘符。 2.linux路径的描述方式 /在Linux中的表示:出现在开头表示根目录,出现在后面表示层级关系。 3.什么是命…...
AOP切面编程,以及自定义注解实现切面
AOP切面编程 通知类型表达式重用表达式切面优先级使用注解开发,加上注解实现某些功能 简介 动态代理分为JDK动态代理和cglib动态代理当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理JDK动态代理动态生成的代理类…...
C70600 CuNi10Fe1Mn铜合金深冲性能好
C70600 CuNi10Fe1Mn铜合金深冲性能好CW608N-R460、CW608N-H135、CuZn36Pb2As-R370、CuZn38Pb1-R460、CW607N-H120、CuZn38Pb1-H120、CW602N-H080、CW608N-H105、CuZn39Pb0.5-R460、CuZn39Pb0.5-H120、CW608N-H120、CuZn38Pb1-R470、CW607N-H080、CW607N-R470、CW607N-H105、CuZ…...
算法学习05:离散化、区间合并
算法学习05:离散化、区间合并 文章目录 算法学习05:离散化、区间合并前言需要记忆的模版:一、离散化1.例题:离散化 区间和:拓展: 二、区间合并(贪心)1.例题: 总结 前言 需要记忆的模…...
内部审计2.0时代:数字化工具和方法全面升级
文章目录 一、内部审计的发展阶段二、内部审计的逻辑架构三、内部审计数字化转型面临的问题(1)缺少内部审计数字化转型规划和方案(2)非结构化数据的采集和后续利用不足(3)依赖编程或使用新工具的数据分析能…...
五子棋小游戏(sut实验报告)
实验目的 实现人与人或人与电脑进行五子棋对弈 实验内容 启动游戏,显示游戏参数设置界面,用户输入参数后进入游戏界面,显示棋盘及双方博弈过程,游戏过程中可选择退出游戏。判定一方获胜后结束本局游戏,可选择继续下…...
图像超分辨率算法ESRGAN原理及应用
前言 图像超分辨率算法是一种用于增加图像分辨率的算法,与传统的图像缩放算法不同的是,超分算法在放大图像的同时根据原图纹理生成更多细节,确保图像在放大后仍然有清晰的纹理细节。 一、模型简介 1、模型开源地址 GitHub - xinntao/ESRGAN: ECCV18 Workshops - Enhance…...
excel 动态列导出
excel动态列,只好用poi来写了,也并不复杂,一样就这个件事情抽像为几步,就是套路了,开发效率就上去了。 1 准备空模板 导出操作与excel模板的导出一样,可以参考excel导出标准化 2 自定义SheetWriteHandler …...
Java零基础入门到精通_Day 1
01 Java 语言发展史 Java语言是美国Sun公司(StanfordUniversity Network)在1995年推出的 计算机语言Java之父:詹姆斯高斯林(ames Gosling) 重要的版本过度: 2004年 Java 5.0 2014年 Java 8.0 2018年 9月 Java 11.0 (目前所使用的) 02 J…...
Spring Cloud集成nacos配置中心
1.添加Nacos Config依赖 打开nacos-config-demo的pom.xml文件并添加以下两个依赖项 项目的配置文件中通常包括数据库连接配置项、日志输出配置项、Redis连接配置项、服务注册配置项等内容,如spring-cloud-alibaba-nacos-config-base-demo项目中就包含数据库连接配置…...
【AI视频教程】只需5步,AI作出鸡你太美视频
1.视频效果 2.准备工作 制作视频效果,需要准备下面3个条件: 准备stable diffusion的环境剪辑一段【鸡你太美】原版视频stable diffusion安装sd-webui-IS-NET-pro插件 2.1部署stable diffusion环境 这里还是建议大家用云平台部署stable diffusion&am…...
C# OpenCvSharp DNN FreeYOLO 密集行人检测
目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN FreeYOLO 密集行人检测 效果 模型信息 Inputs ------------------------- name:input tensor:Float[1, 3, 192, 320] --------------------------------------------------------------- …...
一次HW红初面试
一、描述外网打点的流程? 靶标确认、信息收集、漏洞探测、漏洞利用、权限获取。最终的目的是获取靶标的系统权限/关键数据。 在这个过程中,信息收集最为重要。掌握靶标情报越多,后续就会有更多的攻击方式去打点。比如:钓鱼邮件、…...
网络攻防中nginx安全配置,让木马上传后不能执行、让木马执行后看不到非网站目录文件、命令执行后权限不能过高
网络攻防中nginx安全配置,让木马上传后不能执行、让木马执行后看不到非网站目录文件、命令执行后权限不能过高。 0x01 Nginx介绍 nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给php解释器处理,并把结果返回给客户端。nginx一般是把请求发…...
ctfshow web入门 php特性 web146-web150
1.web146 :被过滤了,三元运算符用不了,还可以用位运算符,逻辑运算符,等,逻辑运算符要注意或运算符的短路性 eval(return 1|phpinfo()|1) eval(return 1phpinfo()|1) payload: v11&v20&v3(~%8C%86%8C%8B%9A%92…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
