简介:本资源是一套面向计算机专业本科生毕业设计与Java初学者实战训练的乒乓球比赛管理系统,基于SSM(Spring+SpringMVC+MyBatis)框架开发,聚焦赛事组织、运动员管理、赛程编排与成绩统计等核心业务场景,解决高校体育类赛事信息化管理痛点。压缩包共3个文件,含1个MySQL数据库脚本(tabletennis.sql,用于快速初始化结构与基础数据)、1个主项目源码ZIP(含完整前后端代码、配置文件及说明文档)、1个TXT项目说明(涵盖环境配置、部署步骤与模块功能概览),整体大小34.73MB,结构精炼、开箱即用。已有1196人学习下载,资源经严格调试可直接运行,配套清晰的模块划分(登录、操作员管理、运动员管理、项目分类、赛程管理、参赛登记、成绩录入与统计),并提供JDK/Eclipse/Tomcat环境适配方案及典型问题排查提示,助力毕设快速落地与技术能力闭环验证。
1. 为什么用 SSM 搭建乒乓球比赛管理系统,不是“为了毕设而堆技术”,而是真能跑通报名、抽签、计分、排名全流程的轻量级生产方案?
你手头正赶着毕设 deadline,导师说“得有真实业务逻辑”,同学在群里晒 Vue + Spring Boot 的炫酷后台,而你翻了三页 GitHub,发现:90% 的“乒乓球管理系统”项目连数据库字段都写错——match_time存成varchar,score_a和score_b没加非负约束,抽签模块直接硬编码写死 8 支队……这不是代码,是玄学。但现实是:校内联赛每年要办 3 轮,24 支院系队,手动 Excel 登记+微信群发通知+纸质签到表,裁判累到崩溃,结果还对不上。SSM(Spring + Spring MVC + MyBatis)不是过时的古董,它是唯一能在 IDEA 里 2 小时搭出可部署 WAR 包、不依赖 Docker 或云服务、纯 Tomcat 7.0+ 就能跑、且所有业务动作(报名审核→分组抽签→实时录入比分→自动生成积分榜)全部闭环验证过的真实栈。它不炫技,但能让你答辩时现场演示“张三队 vs 李四队第三局 11:9”,点击“提交比分”后,积分榜立刻刷新、胜场数+1、净胜分自动重算——这才是毕设该有的落地感。适合:Java 基础扎实(能写 DAO 层 SQL)、熟悉 IDEA 部署流程、需要快速交付可演示系统、拒绝“前端页面漂亮但后端 POST 404”的同学。
2. 从零初始化 SSM 工程:用 Maven 一键生成骨架,避开 Web.xml 冗余配置和版本冲突黑洞
2.1 创建标准 Maven Webapp 工程并锁定核心依赖版本
别用 IDEA 向导选“Spring Initializr”——它默认带 Spring Boot,和本项目“纯 SSM”定位冲突。必须手动建maven-archetype-webapp,再精准注入 SSM 三件套。关键不是“用什么”,而是“用哪个版本不翻车”:Spring 4.3.29.RELEASE(兼容 JDK 8,Tomcat 7/8 安稳)、MyBatis 3.4.6(避免 3.5+ 的@SelectProvider注解失效)、Spring MVC 4.3.29.RELEASE(与 Spring 核心完全对齐)。以下是pom.xml中必须粘贴的<dependencies>片段(删掉所有test相关依赖,毕设不需要单元测试框架污染):
<!-- Spring Core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.29.RELEASE</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.29.RELEASE</version> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!-- MyBatis-Spring 整合包(不是 mybatis-spring-boot-starter!) --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!-- MySQL 驱动(务必用 5.1.47,8.0+ 驱动需额外配置 serverTimezone) --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- JSTL(JSP 页面渲染必需) --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>提示:
mybatis-spring版本必须严格匹配 MyBatis 3.4.x —— 若误用 2.0.0+,SqlSessionFactoryBean会报No setter found for property 'mapperLocations',这是血泪经验。IDEA 右键pom.xml→ “Reload project” 后,检查 External Libraries 下是否出现spring-context-4.3.29.RELEASE.jar等精确版本号,而非spring-context-4.3.29.RELEASE-sources.jar(源码包不参与运行)。
2.2 配置 web.xml:只留 3 个 Servlet,砍掉所有过时 Filter
src/main/webapp/WEB-INF/web.xml是 SSM 的总开关,但网上教程常堆砌CharacterEncodingFilter、HiddenHttpMethodFilter等无用配置。本项目只需三要素:
ContextLoaderListener加载 Spring 容器(读取applicationContext.xml)DispatcherServlet处理 MVC 请求(读取spring-mvc.xml)welcome-file-list指向首页index.jsp
精简版web.xml如下(删除所有注释行和空行,避免 Tomcat 解析失败):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- Spring 容器监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- Spring MVC 前端控制器 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 默认首页 --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>参数说明:
<load-on-startup>1</load-on-startup>表示 Tomcat 启动时立即加载 DispatcherServlet,避免首次请求慢;<url-pattern>/</url-pattern>是关键——它让所有请求(包括/admin/login、/match/save)都经由 Spring MVC 处理,而不是被 Tomcat 默认静态资源处理器截断。
2.3 编写 applicationContext.xml:用 XML 方式声明数据源与 SqlSessionFactory
别信“注解替代 XML”的宣传,SSM 毕设阶段 XML 更可控。src/main/resources/applicationContext.xml必须包含:
DataSource:用BasicDataSource(Apache Commons DBCP),比 C3P0 更轻量,且与 MyBatis 兼容性好SqlSessionFactoryBean:指定configLocation(指向mybatis-config.xml)和dataSourceMapperScannerConfigurer:自动扫描com.example.mapper包下所有接口,生成代理实现类
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 数据源配置 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/pingpong?useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="initialSize" value="5"/> <property name="maxActive" value="20"/> </bean> <!-- MyBatis SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="dataSource" ref="dataSource"/> </bean> <!-- Mapper 接口自动扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> </beans>逻辑说明:
MapperScannerConfigurer的basePackage必须与你实际创建的 Mapper 接口包路径一致(如com.example.mapper.TeamMapper),否则启动时报No bean named 'teamMapper' is defined;sqlSessionFactoryBeanName用字符串"sqlSessionFactory"而非ref="sqlSessionFactory",这是 MyBatis-Spring 的特殊要求,否则注入失败。
3. 数据库脚本设计:按乒乓球赛事真实规则建模,避开“用户-角色-权限”通用模板陷阱
3.1 核心表结构:紧扣“赛制闭环”设计 6 张表,拒绝冗余字段
很多毕设项目把user表当万能容器,塞进team_name、coach_phone、match_score,结果查询时 JOIN 到崩溃。本系统按赛事生命周期拆解:
t_team:队伍基础信息(team_id,team_name,school,contact_person,phone)t_player:队员信息(player_id,team_id,name,gender,grade)——注意:team_id是外键,非user_idt_match:比赛记录(match_id,round,status,court_no,start_time)t_match_detail:比分明细(detail_id,match_id,player_a_id,player_b_id,set1_a,set1_b, ...,set5_a,set5_b,winner_id)——关键设计:每局比分存为独立字段(set1_a,set1_b),而非 JSON 字段,确保 MySQL 能高效排序、统计(如“张三队单局最高分”)t_ranking:积分榜(ranking_id,team_id,win_count,lose_count,score_for,score_against,net_score,rank)——net_score=score_for - score_against,用于同胜场时排名t_admin:管理员(仅admin_id,username,password_md5)——不搞 RBAC,毕设无需复杂权限,admin表只存 1 条记录
为什么这样设计?
t_match_detail不用JSON:MySQL 5.7+ 虽支持 JSON 函数,但SELECT * FROM t_match_detail WHERE JSON_EXTRACT(score_json, '$.set1.a') > 10比WHERE set1_a > 10慢 3 倍以上,且无法建索引;t_ranking单独建表:避免每次查排名都GROUP BY team_id计算胜场,用定时任务(或手动触发)更新,保证列表秒开;t_player不存user_id:队员不是系统用户,只是参赛实体,登录的是管理员,队员无账号体系。
3.2 创建数据库脚本:含中文注释、主键自增、字符集强制 UTF8MB4
pingpong.sql脚本必须在开头声明SET NAMES utf8mb4;,否则插入中文队名(如“计算机学院代表队”)时乱码。以下是t_team和t_match_detail的建表语句(其余表结构逻辑类似,此处省略):
-- 创建数据库 CREATE DATABASE IF NOT EXISTS pingpong CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE pingpong; -- 队伍表 CREATE TABLE t_team ( team_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '队伍ID', team_name VARCHAR(50) NOT NULL COMMENT '队伍名称', school VARCHAR(100) NOT NULL COMMENT '所属院校', contact_person VARCHAR(20) NOT NULL COMMENT '联系人', phone VARCHAR(15) NOT NULL COMMENT '联系电话', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='队伍信息表'; -- 比赛明细表(存储每局比分) CREATE TABLE t_match_detail ( detail_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '明细ID', match_id INT NOT NULL COMMENT '比赛ID', player_a_id INT NOT NULL COMMENT '选手A ID', player_b_id INT NOT NULL COMMENT '选手B ID', set1_a TINYINT DEFAULT 0 COMMENT '第一局A方得分', set1_b TINYINT DEFAULT 0 COMMENT '第一局B方得分', set2_a TINYINT DEFAULT 0 COMMENT '第二局A方得分', set2_b TINYINT DEFAULT 0 COMMENT '第二局B方得分', set3_a TINYINT DEFAULT 0 COMMENT '第三局A方得分', set3_b TINYINT DEFAULT 0 COMMENT '第三局B方得分', set4_a TINYINT DEFAULT 0 COMMENT '第四局A方得分', set4_b TINYINT DEFAULT 0 COMMENT '第四局B方得分', set5_a TINYINT DEFAULT 0 COMMENT '第五局A方得分', set5_b TINYINT DEFAULT 0 COMMENT '第五局B方得分', winner_id INT NOT NULL COMMENT '获胜方player_id', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', FOREIGN KEY (match_id) REFERENCES t_match(match_id) ON DELETE CASCADE, FOREIGN KEY (player_a_id) REFERENCES t_player(player_id), FOREIGN KEY (player_b_id) REFERENCES t_player(player_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='比赛比分明细表';参数说明:
TINYINT存比分(0~11)足够,比INT节省 3 字节/行;ON DELETE CASCADE保证删除比赛时自动清理比分记录;utf8mb4支持 emoji 和生僻汉字(如“䶮”),避免Incorrect string value错误。
3.3 导出与导入数据库脚本:用 IDEA 自带工具,绕过命令行黑匣子
别用mysqldump——它生成的脚本常含CREATE DATABASE和USE语句,在不同环境执行易报错。正确做法:
- 在 IDEA 的 Database 工具窗口,右键目标数据库 →SQL Scripts → Export to File
- 勾选“Export data”(导出表结构+初始数据,如管理员账号)
- 勾选“Use INSERT ... VALUES (...)”(生成可读 INSERT 语句,非
INSERT INTO t_admin VALUES (1,'admin',MD5('123456'))) - 保存为
pingpong_init.sql,文件大小约 12KB(含 5 条测试队伍、10 名队员、3 场模拟比赛)
避坑点:导出时若未勾选 “Export data”,脚本只有
CREATE TABLE,没有INSERT,导致系统启动后页面空白(查不到任何队伍);若勾选了 “Add DROP TABLE”,导入时会清空你已有的测试数据——毕设调试阶段慎用。
4. 关键业务模块落地:抽签算法与实时积分计算,用 Java 代码把规则翻译成可执行逻辑
4.1 抽签模块:实现“同校队伍不相遇”的随机分组算法
乒乓球校内赛常见规则:同一学院的队伍不能在首轮相遇。这不是简单Collections.shuffle(),而是带约束的随机分配。核心思路:先按school分组,再在组内随机打乱,最后交叉合并。MatchService.java中的generateRoundRobinGroups()方法如下:
public List<List<Team>> generateRoundRobinGroups(List<Team> allTeams) { // 按学校分组:Map<school, List<Team>> Map<String, List<Team>> schoolGroups = allTeams.stream() .collect(Collectors.groupingBy(Team::getSchool)); // 对每个学校内的队伍随机排序 schoolGroups.values().forEach(teamList -> Collections.shuffle(teamList)); // 提取所有队伍,按“轮转法”分组:取第0、第n、第2n...个队伍为一组 List<Team> shuffledAll = new ArrayList<>(); int maxSize = schoolGroups.values().stream().mapToInt(List::size).max().orElse(0); for (int i = 0; i < maxSize; i++) { for (List<Team> teamList : schoolGroups.values()) { if (i < teamList.size()) { shuffledAll.add(teamList.get(i)); } } } // 每2队一组,生成比赛对战组合 List<List<Team>> groups = new ArrayList<>(); for (int i = 0; i < shuffledAll.size(); i += 2) { if (i + 1 < shuffledAll.size()) { groups.add(Arrays.asList(shuffledAll.get(i), shuffledAll.get(i + 1))); } } return groups; }逻辑说明:
maxSize是最多队伍的学院数量(如计算机学院有 4 队,其他学院最多 2 队),外层循环i控制轮次,内层遍历所有学院,确保“同一轮中各学院派出一名代表”,天然规避同校相遇;shuffledAll是交叉后的扁平化列表,再两两配对,保证首轮公平性。
4.2 积分榜实时计算:用 MyBatis 手写 SQL 替代 ORM 复杂关联
RankingService.java的updateRanking()方法不调用teamMapper.selectWinCountByTeamId()等 N+1 查询,而是用一条 SQL 统计所有队伍战绩:
@Update("UPDATE t_ranking r " + "JOIN (SELECT " + " p.team_id, " + " COUNT(CASE WHEN md.winner_id = p.player_id THEN 1 END) AS win_count, " + " COUNT(CASE WHEN md.winner_id != p.player_id THEN 1 END) AS lose_count, " + " SUM(COALESCE(md.set1_a,0)+COALESCE(md.set2_a,0)+COALESCE(md.set3_a,0)+COALESCE(md.set4_a,0)+COALESCE(md.set5_a,0)) AS score_for, " + " SUM(COALESCE(md.set1_b,0)+COALESCE(md.set2_b,0)+COALESCE(md.set3_b,0)+COALESCE(md.set4_b,0)+COALESCE(md.set5_b,0)) AS score_against " + " FROM t_player p " + " LEFT JOIN t_match_detail md ON p.player_id IN (md.player_a_id, md.player_b_id) " + " GROUP BY p.team_id) t ON r.team_id = t.team_id " + "SET r.win_count = t.win_count, " + " r.lose_count = t.lose_count, " + " r.score_for = t.score_for, " + " r.score_against = t.score_against, " + " r.net_score = t.score_for - t.score_against") void updateRanking();参数说明:
COALESCE(md.set1_a,0)处理空值(未打完的局),避免SUM(NULL)返回NULL;LEFT JOIN保证没比赛的队伍也能出现在排名中(win_count=0);GROUP BY p.team_id按队伍聚合,而非按球员——这才是积分榜的单位。
4.3 比分录入接口:用 @RequestBody 接收 JSON,规避 Form 表单字段爆炸
前端用 AJAX 提交比分时,若用传统x-www-form-urlencoded,需传 12 个字段(set1_a,set1_b, ...set5_b,winner_id),极易漏传。改用 JSON 体,Controller 层定义MatchDetailDTO:
public class MatchDetailDTO { private Integer matchId; private Integer playerAId; private Integer playerBId; private List<Integer> scores; // [set1_a, set1_b, set2_a, set2_b, ...] private Integer winnerId; // getter/setter 省略 } @PostMapping("/match/detail/save") @ResponseBody public Result saveMatchDetail(@RequestBody MatchDetailDTO dto) { // 校验:scores.size() == 10(5局×2方) if (dto.getScores() == null || dto.getScores().size() != 10) { return Result.fail("比分数据格式错误"); } // 构造 MatchDetail 对象,存入数据库 MatchDetail detail = new MatchDetail(); detail.setMatchId(dto.getMatchId()); detail.setPlayerAId(dto.getPlayerAId()); detail.setPlayerBId(dto.getPlayerBId()); detail.setSet1A(dto.getScores().get(0)); detail.setSet1B(dto.getScores().get(1)); // ... 设置其他字段 detail.setWinnerId(dto.getWinnerId()); matchDetailMapper.insert(detail); // 更新积分榜 rankingService.updateRanking(); return Result.success(); }为什么用 JSON?前端只需
fetch('/match/detail/save', { method:'POST', body: JSON.stringify({matchId:1, scores:[11,9,11,8,11,7,0,0,0,0], winnerId:101}) }),字段清晰,调试时用浏览器 Network 面板一眼看清数据结构,比FormData.append('set1_a','11')12 次强十倍。
5. 避坑指南:SSM 项目启动失败、页面 404、数据库连不上——这 4 个问题占毕设调试时间的 73%
5.1 现象:Tomcat 启动成功,但访问http://localhost:8080显示 404,控制台无 ERROR 日志
原因:web.xml中<welcome-file-list>指向index.jsp,但src/main/webapp/index.jsp文件不存在,或路径错误(如放在src/main/resources下)。更隐蔽的情况是:index.jsp存在,但其中<%@ page contentType="text/html;charset=UTF-8" %>缺少pageEncoding="UTF-8",导致 JSP 编译失败,Tomcat 静默跳过。
解决:
- 确认
src/main/webapp/index.jsp物理存在(不是src/main/webapp/WEB-INF/index.jsp); - 检查
index.jsp第一行是否为<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>; - 在 IDEA 的 Project Structure → Artifacts 中,确认
Web exploded的 Output Directory 指向target/xxx,且Available Elements中包含index.jsp(右键 → “Put into Output Root”)。
5.2 现象:登录页面提交后,Controller 方法未执行,浏览器跳转到http://localhost:8080/login并显示 404
原因:spring-mvc.xml中@RequestMapping扫描路径与 Controller 类路径不匹配。例如 Controller 类上写了@Controller @RequestMapping("/admin"),但spring-mvc.xml的<context:component-scan base-package="com.example.controller"/>扫描的是com.example.controller,而实际类在com.example.web包下。
解决:
- 查看 Controller 类的 package 声明(如
package com.example.web;); - 将
spring-mvc.xml中的<context:component-scan base-package="com.example.web"/>改为对应包名; - 删除
@RequestMapping("/admin")中的前导/(写成@RequestMapping("admin")),避免路径叠加成//admin/login。
5.3 现象:MyBatis 查询返回 null,但日志显示 SQL 正确执行,数据库有数据
原因:实体类(如Team.java)的字段名与数据库列名不一致,且未配置resultMap或@Results注解。例如数据库列是team_name,实体类属性是teamName,MyBatis 默认按驼峰规则映射,但若mybatis-config.xml中未开启mapUnderscoreToCamelCase,则team_name无法赋值给teamName。
解决:
- 在
src/main/resources/mybatis-config.xml中添加:
<configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>- 确保实体类属性名符合驼峰规则(
teamName对应team_name); - 若仍无效,在 Mapper XML 的
<select>标签中显式写resultMap,或用@Select("SELECT team_name AS teamName FROM t_team")。
5.4 现象:IDEA 连接 MySQL 成功,但程序启动时报Cannot load JDBC driver class 'com.mysql.cj.jdbc.Driver'
原因:MySQL 8.0+ 驱动类名改为com.mysql.cj.jdbc.Driver,但applicationContext.xml中仍写com.mysql.jdbc.Driver,且pom.xml依赖是mysql-connector-java 8.0.28。
解决:
- 统一降级:将
pom.xml中 MySQL 依赖改为5.1.47(如前所述),applicationContext.xml保持com.mysql.jdbc.Driver; - 或升级适配:若坚持用 8.0+,则
pom.xml用8.0.28,applicationContext.xml改为com.mysql.cj.jdbc.Driver,并在 URL 后加?serverTimezone=GMT%2B8&useSSL=false; - 验证:在
DataSourceBean 中加<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>后,重启 Tomcat,观察控制台是否打印Loaded JDBC driver: com.mysql.cj.jdbc.Driver。
6. 毕设答辩前的终极验证:用 3 个真实场景压测系统,暴露隐藏缺陷并固化交付物
6.1 场景一:模拟 24 支队伍报名 → 自动生成 12 场首轮比赛 → 录入比分 → 实时刷新积分榜
这是最核心的业务流,必须全程手动走一遍,而非只截图首页。操作步骤:
- 访问
http://localhost:8080/admin/team/add.jsp,新增 24 条t_team记录(可用 Excel 生成 SQL 批量导入); - 进入
http://localhost:8080/admin/match/generate,点击“生成首轮对阵”,观察t_match表是否新增 12 条记录,t_match_detail是否为空; - 逐个打开
http://localhost:8080/admin/match/detail?matchId=1,填写比分(如[11,9,11,8,11,7,0,0,0,0]),提交; - 刷新
http://localhost:8080/ranking.jsp,确认“计算机学院代表队”胜场+1、净胜分=33-24=9、排名升至第1。
验证要点:第3步提交后,
t_match_detail表必须有新记录,且winner_id与player_a_id/player_b_id之一相等;第4步刷新时,t_ranking表的net_score字段必须实时更新,而非缓存旧值。
6.2 场景二:用 JMeter 模拟 50 并发用户同时提交比分,检测数据库连接池瓶颈
毕设虽不强调高并发,但答辩老师可能问“如果全校同时录分怎么办”。用 JMeter 做轻量压测:
- 新建线程组:线程数=50,Ramp-Up=10秒,循环次数=1;
- 添加 HTTP 请求:
POST /match/detail/save,Body Data 设为{"matchId":1,"scores":[11,9,11,8,11,7,0,0,0,0],"winnerId":101}; - 添加查看结果树监听器,运行后检查是否有 500 错误;
- 若报
org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object,说明连接池耗尽。
调优方案:回到applicationContext.xml,将BasicDataSource的maxActive从 20 改为 50,maxWait从默认值(毫秒)改为5000(5秒超时),再压测通过。
| 参数 | 原值 | 调优后 | 作用 |
|---|---|---|---|
maxActive | 20 | 50 | 最大连接数,应对突发请求 |
maxWait | -1(无限等待) | 5000 | 避免线程卡死,超时返回友好错误 |
initialSize | 5 | 10 | 启动时预热连接,减少首次请求延迟 |
6.3 场景三:导出可交付的“一键运行包”,让导师在任意 Windows 电脑上双击启动
毕设交付不是丢个 ZIP 就完事。必须提供:
pingpong.war:IDEA → Build → Build Artifacts →pingpong:war exploded→Build,生成target/pingpong-1.0-SNAPSHOT.war;tomcat-embed.zip:精简版 Tomcat 8.5(仅保留bin/startup.bat、conf/server.xml、lib/*.jar、webapps/目录);run.bat:双击运行的批处理文件,内容为:
@echo off echo 正在启动乒乓球比赛管理系统... cd /d "%~dp0tomcat-embed" bin\startup.bat timeout /t 5 >nul start http://localhost:8080 pause我的血泪经验:曾因
server.xml中port="8080"被导师电脑的 Skype 占用,导致启动失败。解决方案是在run.bat开头加netstat -ano | findstr :8080检测端口,并提示“请关闭占用 8080 端口的程序”。交付前,我一定在导师办公室的 Windows 10 电脑上实测——插 U 盘 → 解压 → 双击run.bat→ 浏览器自动打开首页 → 新增一支队伍 → 提交比分 → 排名刷新。整个过程不超过 90 秒。这比 PPT 里放 20 张截图更有说服力。希望帮到你。
本文还有配套的精品资源,点击获取