news 2026/4/19 1:22:14

基于SpringBoot的Java毕设实战:13个表支撑比赛日程系统的架构设计与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot的Java毕设实战:13个表支撑比赛日程系统的架构设计与避坑指南


背景:13张表是怎么把人逼疯的?

做毕设时,导师一句“比赛日程系统,功能要全”,听起来简单,一拆表就傻眼:
赛事、项目、队伍、队员、裁判、场地、日程、积分、公告、报名、轮空、消息、日志——13张表瞬间到位。
大多数同学第一反应是“堆字段”,外键到处拉,结果:

  • 循环引用:A 表删不掉,B 表查不动,C 表一更新,D 表全锁行
  • N+1 查询:赛程列表接口,for 循环里顺手getTeam().getPlayers(),生产环境 200 ms 能飙到 5 s
  • 事务边界模糊:一个“生成日程”按钮,里层调了 4 个 Service,外层没加@Transactional,回滚只回一半,数据直接乱套

毕设答辩前夜,一边改 SQL 一边哭的场景,懂的都懂。

技术选型:MyBatis 还是 JPA?

多表关联场景,两条路线都能走,但踩坑姿势不同:

维度MyBatisSpring Data JPA
SQL 可控手写 XML,复杂 Join 一目了然@Query或方法名,调试要开 SQL 日志
缓存/懒加载无默认,自己写一级缓存+懒加载,一不小心 N+1
分页手写 count 查询Pageable一行代码搞定
代码量每个表 4 个文件(XML+Mapper)注解实体+Repository 接口即可
学习曲线低,但后期 SQL 爆炸前期爽,后期要懂实体状态、flush、detach

结论:毕设周期短、表关联深、导师要求“规范”,JPA 更香;若后续要极端优化,可再局部写 SQL,两者混用 Spring 也支持。

核心模型:13 张表的关系长这样

先放 ER 简图,混个眼熟:

关键关系一句话总结:
赛事 1-N 项目,项目 1-N 日程,日程 N-N 队伍(中间表 schedule_team),日程 1-1 场地,队伍 N-N 队员(中间表 team_player)……

下面用 JPA 注解落地,只贴核心片段,能跑即可。

1. 赛事实体

@Entity @Table(name = "t_event") public class Event { @Id @GeneratedValue private Long id; private String name; private LocalDate startDate; private LocalDate endDate; @OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true) private List<Item> items = new ArrayList<>(); }

2. 项目实体

@Entity @Table(name = "t_item") public class Item { @Id @GeneratedValue private Long id; private String itemName; // 如“男子篮球” @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id") private Event event; @OneToMany(mappedBy = "item") private List<Schedule> schedules = new ArrayList<>(); }

3. 日程实体(最复杂)

@Entity @Table(name = "t_schedule") public class Schedule { @Id @GeneratedValue private Long id; private LocalDateTime startTime; private LocalDateTime endTime; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") private Item item; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "venue_id") private Venue venue; @ManyToMany @JoinTable(name = "schedule_team", joinColumns = @JoinColumn(name = "schedule_id"), inverseJoinColumns = @JoinColumn(name = "team_id")) private Set<Team> teams = new HashSet<>(); }

注意:

  • 全部用LAZY,只在 Service 里写JOIN FETCH解决 N+1
  • 多对多再建中间表,避免双向List造成无限递归 JSON
  • 实体类绝不暴露toString()含关联字段,栈溢出警告

DTO 转换:不让 Entity 裸奔

Controller 直接返回 Entity,会:

  1. 把懒加载代理拖进 Jackson,秒抛LazyInitializationException
  2. 暴露内部字段,循环引用 500

套路:MapStruct 一行注解搞定

@Mapper(componentModel = "spring") public interface ScheduleMapper { ScheduleDto toDto(Schedule s); List<ScheduleDto> toDto(List<Schedule> list); }

DTO 里只留venueNameteamNames等扁平字段,前端开心,后端安全。

事务与性能:生成日程的正确姿势

需求:根据“项目+轮次”一键生成 48 条日程,涉及 4 张表写操作。
下面代码演示“事务+批量+幂等”三位一体:

@Service @RequiredArgsConstructor public class ScheduleGenService { private final ScheduleRepository scheduleRepository; private final VenueRepository venueRepository; private final IdGenerator idGenerator; // 雪花算法,保证幂等 @Transactional // 1. 事务边界 public List<Schedule> generate(Item item, int rounds){ // 2. 幂等:先查重 if(scheduleRepository.countByItemId(item.getId()) > 0){ throw new BizException("日程已存在,禁止重复生成"); } List<Venue> venues = venueRepository.findAllBySport(item.getSport()); List<Schedule> batch = new ArrayList<>(rounds * venues.size()); for(int i=0; i<rounds; i++){ for(Venue v : venues){ Schedule s = new Schedule(); s.setId(idGenerator.nextId()); // 3. 提前设主键,批量插入可走 JDBC 批处理 s.setItem(item); s.setVenue(v); s.setStartTime(calcStartTime(i, v)); batch.add(s); } } // 4. 批量保存 scheduleRepository.saveAll(batch); return batch; } }

要点:

  • 事务只加在写服务,读接口不加,减少锁范围
  • 提前分配主键,MySQL 批插入rewriteBatchedStatements=true秒级 1w+
  • 接口幂等靠业务键(itemId+round),不是单纯依赖数据库唯一索引

安全三板斧

  1. SQL 注入:JPA 只要用方法名或参数绑定:xxx,基本免疫;手写@Query也杜绝拼接
  2. 批量更新:MySQL 的on duplicate key update配合JpaRepository@Modifying注意分片,一次 500 条最稳
  3. 接口幂等:除业务判断外,前端点“生成”按钮后置灰+UUID 令牌,后端用 RedisSETNX做双重校验,防重放

生产环境避坑清单

  • 双向关联绝不写cascade = CascadeType.ALL了事,级联删除一跑,半库数据蒸发
  • 枚举字段统一@Enumerated(STRING),防止序号移位全崩
  • 逻辑删除加deleted字段,手写WHERE deleted = 0拦截,JPA 2 级缓存不会自动过滤,记得配@Where
  • 多对多中间表别加业务字段,一旦加字段就升成实体,否则后续补字段全表锁
  • 生产环境打开spring.jpa.show-sql=false,用 datasource-proxy 慢查询日志替代,别让控制台刷屏把性能吃光

可扩展思考:赛制说改就改,怎么办?

当前模型只支持“单循环+积分制”,如果导师突然说“加淘汰赛、加复活赛”,硬编码if/else直接爆炸。
留给读者的作业:

  1. 把“赛制”抽象成 Strategy 接口,提供ScheduleStrategy.generate()
  2. 项目表加strategy_type字段,与 Spring 的@Strategy自动装配联动
  3. 新建模块schedule-strategy-elimination,遵循相同 DTO,做到“热插拔”

先动手把生成日程 Service 拆成“规则引擎+执行器”,跑通单元测试,你就能在答辩时自信回答“系统支持任意赛制”——导师微笑,你稳过。


把 13 张表拆干净、事务扣稳、接口拍平,比赛日程系统就不再是“毕设噩梦”,而是简历上能吹的亮点。
代码给你了,坑也标好了,下一步要不要把“淘汰赛”模块真正写出来,就看你想不想让自己的毕设从 80 分跳到 95 分。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/12 17:35:46

KK-HF Patch模组管理完全指南:从安装到高级定制

KK-HF Patch模组管理完全指南&#xff1a;从安装到高级定制 【免费下载链接】KK-HF_Patch Automatically translate, uncensor and update Koikatu! and Koikatsu Party! 项目地址: https://gitcode.com/gh_mirrors/kk/KK-HF_Patch 您是否在使用Koikatu游戏时遇到模组冲…

作者头像 李华
网站建设 2026/4/18 5:50:13

Dify API调用延迟骤降73%:生产环境实测的8个必改配置项

第一章&#xff1a;Dify API调用延迟骤降73%的实测背景与价值洞察近期在某智能客服中台项目中&#xff0c;我们对 Dify v0.12.0 的 API 服务链路进行了全链路压测与深度调优。原始部署采用默认的同步推理模式&#xff08;/v1/chat-messages&#xff09;&#xff0c;在 50 并发、…

作者头像 李华
网站建设 2026/4/18 17:26:50

如何用epftoolbox实现电力价格精准预测:5个专业实践指南

如何用epftoolbox实现电力价格精准预测&#xff1a;5个专业实践指南 【免费下载链接】epftoolbox An open-access benchmark and toolbox for electricity price forecasting 项目地址: https://gitcode.com/gh_mirrors/ep/epftoolbox 电力价格预测是能源市场决策的核心…

作者头像 李华
网站建设 2026/4/18 13:01:08

3步搞定B站视频备份!这款免费神器让你告别在线观看限制

3步搞定B站视频备份&#xff01;这款免费神器让你告别在线观看限制 【免费下载链接】BiliDownloader BiliDownloader是一款界面精简&#xff0c;操作简单且高速下载的b站下载器 项目地址: https://gitcode.com/gh_mirrors/bi/BiliDownloader 找不到好用的视频备份工具&a…

作者头像 李华
网站建设 2026/4/18 15:56:34

3步精通Fillinger:设计师智能填充效率提升指南

3步精通Fillinger&#xff1a;设计师智能填充效率提升指南 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts 认知篇&#xff1a;Fillinger如何重塑设计填充工作流 为什么传统填充方法…

作者头像 李华