基于SpringBoot毕业设计管理系统的效率优化实战:从单体架构到高响应体验

news2026/3/27 20:22:30
最近在参与一个毕业设计管理系统的重构项目系统主要服务于师生进行选题、开题、中期检查、答辩等全流程管理。随着用户量增长原有的系统在高并发场景下暴露出了不少性能问题比如选题时页面卡顿、审核流程通知延迟、报表查询缓慢等。我们团队基于SpringBoot框架对系统进行了一系列效率优化取得了不错的效果。今天就来分享一下我们在这个过程中的实战经验和具体做法。1. 识别典型性能瓶颈从现象到根因在优化之前我们首先对系统进行了全面的压力测试和日志分析定位了几个核心的性能瓶颈选题阶段的并发冲突与锁竞争毕业设计选题通常有固定时间窗口大量学生同时操作对同一个导师的剩余名额字段进行“查询-判断-扣减”操作。原系统使用数据库行锁在高并发下大量请求排队导致接口响应时间飙升甚至出现超时和死锁。审核流程的同步阻塞指导老师审核学生开题报告、中期报告等环节系统会同步发送邮件或站内信通知。邮件服务调用耗时几百毫秒到几秒不等直接阻塞了主业务流程导致审核提交接口响应缓慢。复杂报表的低效查询管理员需要查看各类统计报表如“各学院选题情况统计”、“教师指导工作量统计”。原系统使用MyBatis编写了大量多表关联、分组聚合的复杂SQL在数据量增长后查询耗时从几百毫秒增加到数秒严重影响了管理后台的体验。热点数据的重复查询例如首页需要展示当前登录用户的待办事项数量、系统公告等。这些数据变化频率低但每次页面刷新都需要访问数据库造成了大量不必要的、完全相同的查询请求。2. 技术选型考量JPA与缓存策略的抉择针对上述瓶颈我们在技术选型上做了重点考量尤其是在数据访问层和缓存层。JPA vs MyBatis在复杂查询场景的思考 原系统使用MyBatis灵活性高但复杂的动态SQL在XML中维护成本较高且N1查询问题需要开发者手动优化。我们评估后决定在核心业务模块引入Spring Data JPA原因如下开发效率与代码简洁性JPA的Repository接口和派生查询方法能极大简化大部分单表CRUD和简单条件查询的代码。对于“审核状态更新”、“学生信息查询”等高频操作代码非常清晰。内置缓存支持JPA提供了一级缓存Session级别和二级缓存应用级别的支持。这对于我们优化“热点数据重复查询”和“减少数据库往返”的目标非常契合。复杂查询的应对我们承认对于多表关联、复杂聚合的报表查询JPA的Criteria API或Query写原生SQL在可读性和维护性上可能不如MyBatis直观。因此我们采取了混合策略高频简单操作用JPA低频复杂报表查询仍用优化后的MyBatis配合缓存。同时我们计划将最复杂的统计查询迁移到专门的数据分析服务或使用物化视图这是后话。缓存选型Caffeine本地缓存 考虑到毕业设计系统在一定时间内如一个学期很多基础数据学院、专业、教师信息和配置信息变动不频繁且系统部署规模为单机或小型集群我们首选了高性能的本地缓存库Caffeine。它提供了丰富的驱逐策略基于大小、时间、引用API友好性能卓越非常适合缓存那些“读多写少、允许短暂不一致”的数据。3. 核心优化方案实施异步、缓存与查询优化基于以上分析我们制定了并实施了三大优化方案。方案一Async异步化非核心流程核心思想将不影响主业务事务最终一致性的操作异步化快速释放请求线程。我们使用Spring的Async注解轻松实现了通知的异步发送。首先在SpringBoot配置类上启用异步支持Configuration EnableAsync public class AsyncConfig { Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix(Gd-Async-); executor.initialize(); return executor; } }然后将邮件或站内信通知服务改造为异步方法Service Slf4j public class NotificationService { Async // 声明此方法为异步执行 public void sendAuditNotify(String toUser, String title, String content) { // 模拟耗时的邮件发送或消息推送逻辑 log.info(开始异步发送通知给: {} 标题: {}, toUser, title); // ... 调用邮件服务或消息队列客户端 log.info(通知发送完成: {}, toUser); } }在审核业务代码中同步流程只更新审核状态然后异步触发通知Service Transactional public class AuditService { Autowired private NotificationService notificationService; public void approveProposal(Long proposalId, String teacherId) { // 1. 核心业务逻辑更新开题报告状态为“已通过” Proposal proposal proposalRepository.findById(proposalId).orElseThrow(...); proposal.setStatus(APPROVED); proposalRepository.save(proposal); // 2. 异步发送通知不阻塞主线程 notificationService.sendAuditNotify(proposal.getStudentId(), 您的开题报告已通过审核, 您的开题报告已被导师审核通过请查收。); // 主方法立即返回 } }这样approveProposal方法的响应时间就从“业务处理通知发送”缩短为仅“业务处理”的时间。方案二Caffeine本地缓存集成与预热目标减少对数据库的重复查询特别是基础数据和热点数据。配置与集成在pom.xml中添加Caffeine依赖并创建一个缓存配置类。Configuration public class CacheConfig { // 定义一个名为“teachers”的缓存有效期10分钟最大存储1000条 Bean public CacheString, Teacher teacherCache() { return Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(1000) .build(); } // 可以定义多个不同配置的缓存Bean Bean public CacheString, ListDepartment deptCache() { return Caffeine.newBuilder() .expireAfterWrite(30, TimeUnit.MINUTES) .maximumSize(100) .build(); } }服务层封装创建一个缓存服务封装缓存的读写逻辑并处理缓存未命中时从数据库加载的情况。Service public class TeacherCacheService { Autowired private CacheString, Teacher teacherCache; Autowired private TeacherRepository teacherRepository; public Teacher getTeacherById(String id) { // 1. 先查缓存 Teacher teacher teacherCache.getIfPresent(id); if (teacher ! null) { return teacher; } // 2. 缓存未命中查询数据库 teacher teacherRepository.findById(id).orElse(null); if (teacher ! null) { // 3. 写入缓存 teacherCache.put(id, teacher); } return teacher; } // 更新或删除教师信息时需要同步清理缓存 public void evictTeacherCache(String id) { teacherCache.invalidate(id); } }缓存预热在系统启动后通过实现CommandLineRunner或ApplicationRunner接口主动将高频访问的数据如所有在职教师信息、有效公告列表加载到缓存中。Component Slf4j public class CacheWarmUpRunner implements ApplicationRunner { Autowired private TeacherRepository teacherRepository; Autowired private CacheString, Teacher teacherCache; Override public void run(ApplicationArguments args) { log.info(开始缓存预热...); ListTeacher activeTeachers teacherRepository.findByStatus(ACTIVE); activeTeachers.forEach(teacher - teacherCache.put(teacher.getId(), teacher)); log.info(教师信息缓存预热完成共加载 {} 条记录, activeTeachers.size()); // ... 预热其他缓存 } }方案三分页查询深度优化与JPA二级缓存针对列表查询和报表我们进行了针对性优化。分页查询优化禁止使用SELECT *在JPA的Query中或MyBatis的SQL里只查询需要的字段避免不必要的网络传输和内存占用。优化count查询对于数据量巨大的表分页时的count(*)可能很慢。如果不需要精确的总数可以考虑不进行count查询Pageable.unpaged()或者使用估算值。在需要精确值的场景确保count查询的where条件与数据查询一致并走索引。使用Keyset Pagination游标分页对于无限滚动的场景放弃传统的LIMIT offset, size转而使用WHERE id lastSeenId LIMIT size。这避免了offset过大时的性能断崖式下降。JPA可以通过Id排序和条件查询实现类似效果。启用JPA二级缓存我们为Hibernate配置了EHCache作为二级缓存提供者注也可选择其他实现。在实体类上添加Cacheable和Cache注解指定缓存策略。这对于经常被关联查询的实体如Student,Teacher非常有效。当多个Proposal关联同一个Teacher时Hibernate可以从二级缓存直接获取Teacher对象无需再次查询数据库。重要提示二级缓存需要仔细配置过期策略并在数据更新时妥善处理缓存失效否则会导致脏读。4. 优化效果与安全性考量压测数据对比 我们使用JMeter对优化前后的核心接口进行了压测100线程循环100次。选题接口扣减名额通过引入Redis分布式锁或数据库乐观锁替代原有悲观锁并将名额校验逻辑前置到缓存中该接口的TPS每秒事务数提升了约300%平均响应时间从 ~450ms 降至 ~120ms。审核提交接口异步化通知后平均响应时间从 ~1200ms含邮件发送降至 ~80ms仅业务处理。教师信息查询接口接入Caffeine缓存后对于缓存命中请求平均响应时间从 ~35ms 降至 ~2msQPS提升显著。安全性考量防重复提交与数据一致性防重复提交对于选题、审核提交等关键操作我们在前端使用按钮防抖Debounce在后端为每个请求生成唯一令牌Token存入Redis并设置短有效期。处理请求前校验Token用完后立即删除有效防止了因网络延迟或用户重复点击导致的重复操作。缓存与数据库一致性这是使用缓存的最大挑战。我们的策略是读多写少的数据如学院信息采用“缓存过期失效”策略允许极短时间的不一致。写操作较频繁的数据如课题剩余名额采用“写时更新或删除缓存”策略。在更新数据库后立即删除或更新对应的缓存项。这要求更新操作必须是事务性的且缓存操作要在事务提交后执行以避免脏数据被缓存可通过TransactionalEventListener监听事务提交事件来清理缓存。5. 生产环境避坑指南在实际部署和运行中我们也踩过一些坑这里分享给大家异步任务的事务边界Async方法默认是在独立的线程中执行不会参与到调用者的事务中。如果异步任务需要操作数据库务必在其方法上声明新的事务Transactional(propagation Propagation.REQUIRES_NEW)否则可能会因为找不到会话而报错。缓存穿透应对如果查询一个不存在的教师ID每次请求都会穿透缓存打到数据库。应对方法缓存空值。在TeacherCacheService中即使数据库查不到也在缓存中放入一个代表“空”的标记如Optional.empty()并设置一个较短的过期时间如30秒。缓存雪崩预防如果大量缓存项在同一时刻过期所有请求会同时涌向数据库。解决方法给缓存过期时间加上一个随机值例如expireAfterWrite(10 random.nextInt(5), TimeUnit.MINUTES)让失效时间点分散开。线程池配置与管理Async默认使用SimpleAsyncTaskExecutor不会复用线程。务必像我们前面那样自定义一个ThreadPoolTaskExecutor并合理设置核心/最大线程数、队列容量和拒绝策略避免OOM。JPA的N1查询问题即使使用了二级缓存如果代码中遍历学生列表并频繁访问其导师属性student.getTeacher().getName()且关联关系是懒加载LAZY仍可能触发大量查询。务必在查询学生列表的Repository方法上使用EntityGraph或编写JOIN FETCH的JPQL语句一次性加载所需关联。结语与思考经过这一轮以“效率提升”为核心的优化我们的毕业设计管理系统在响应速度和并发能力上有了质的飞跃。师生们最直观的感受就是页面“变快了”操作“更流畅了”。技术优化永无止境本次实践主要聚焦于应用层和缓存层的优化。留给大家一个思考题在保障数据强一致性的前提下例如选题扣减名额必须绝对准确不能超卖我们还能通过哪些架构或技术手段进一步提升系统的整体吞吐量是引入消息队列对写请求进行削峰填谷还是采用读写分离架构将报表等复杂查询引流到只读副本或者更进一步考虑将核心的“名额扣减”这类高并发写操作通过状态机引擎和事件溯源Event Sourcing模式进行改造欢迎大家一起探讨。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…