简介:基于Java与SSM框架的健身房俱乐部管理系统毕业设计项目,是一套完整的高分源码包,适合计算机软件工程、人工智能等专业学生用于毕业设计、课程设计或SSM框架进阶学习。压缩包共包含622个文件,大小约19.06MB,以Java源代码、JSP动态页面、XML配置文件、CSS/JS样式脚本和数据库SQL脚本为主,同时附有150个GIF操作演示及图片材料,可直观了解系统运行流程。该套项目代码已通过Windows/macOS等多系统运行测试,功能稳定,覆盖健身房会员管理、课程安排、订单处理等核心模块,配合使用文档与数据库文件,可快速部署并深入理解SSM分层开发思路。目前已有118人学习下载,适合需要完整毕设方案、课设作业或作为初期项目演示的读者参考,也可在现有代码基础上进行二次开发与功能扩展。
1. 基于 Java+SSM 的健身房俱乐部管理系统,听起来像上个时代的课程设计,却是 Java Web 毕业设计里最完整的练手场景
会员档案、私教排课、课程预约、器材维护,几条业务线全都要和数据库打交道,每一步都在逼着你把 Spring、SpringMVC、MyBatis 串起来用。它解决的是一个挺实际的问题:健身房怎么从手动登记表变成一套网页后台,也顺带解决了新人的能力训练——面对一套源码包,能不能读懂分层结构、改得动字段、跑得起服务、讲得清流程。适合正在做毕设的本科生,也适合想通过 SSM 项目补框架知识的 Java 开发新人。这套系统的难点不在功能多少,而在“整合”:框架之间谁管谁、事务怎么回滚、SQL 映射为什么失效,任何一环断了,登录页就弹不出来。下面我按技术选型、运行步骤、核心实现和排坑的顺序,把这条链路完整拆开。
2. 技术选型和数据库设计:SSM 在健身房管理系统里的分工,为什么这五张表就够了
2.1 SSM 三层架构:Controller 收请求、Service 算业务、Mapper 管数据库
SSM 不是新东西,但它的分层方式对一个需要“讲清楚”的毕设项目来说足够舒服。Spring 的 IoC 容器负责把 Dao、Service、Controller 的依赖关系通过注解或 XML 管理起来;SpringMVC 负责把 URL 映射到 Java 方法,再把返回值渲染成 JSP 或 JSON;MyBatis 把 SQL 写在 Mapper XML 里,避免在 Java 代码里拼字符串。这套组合在国内的旧项目和面试题里出现频率极高,学会之后再看 Spring Boot + MyBatis 的工程,心智模型几乎是无缝迁移。
用健身房场景举例:一个会员点击“预约私教课”,请求先到 Controller 的某个方法,方法里只处理参数绑定和调用 Service;Service 拿到请求后查会员状态、查课程剩余名额,再调用 Mapper 写入预约记录;Mapper 就是一条 INSERT 和一条 UPDATE。如果最后的数据库更新失败了,Service 上的事务注解会把前面已执行的 SQL 全部回滚,不会出现“预约记录有了、课程余量没减”的脏数据。这个例子会贯穿整篇文章。
那为什么不用 SSH?Struts 2 的配置比 SpringMVC 繁琐,历史漏洞也多,拿它做新项目纯属给自己添堵。为什么不用纯 Servlet?Controller 里塞满 request.getParameter 和 response.getWriter,代码没有边界,答辩时也很难讲出架构感。为什么不上 Spring Boot?如果这是毕业设计,评审老师大概率更想看到你能把配置讲明白,而 Spring Boot 帮你藏掉了很多细节。SSM 还有一个现实价值:不少健身房、小商户的内部管理系统至今还是 SSM 维护的,能读懂它,就具备接手这类老项目的能力。
2.2 功能模块拆解:会员、私教、课程、器材、系统管理五条线
健身房俱乐部的日常运营可以拆成五条线。会员线负责注册、办卡、续费、停卡、退卡;私教线负责教练档案、等级、可约时间段;课程线负责团操和私教预约;器材线负责设备入库、保养、维修、报废;系统管理线负责后台账号和角色权限。每一线在代码里不是散落的,常见工程结构是:controller、service、dao、entity、mapper_xml 各建一个包,比如 com.gym.controller 下面有 MemberController、CoachController、CourseController、EquipmentController、AdminController,对应的 Service 接口和实现类各一个,对应的 Mapper 接口一个、XML 一个。
新手拿到这类源码包,第一件事不是急着跑起来,而是把 package 结构画一遍,然后挑一条最完整的业务链路走出来。例如“会员预约课程”:前端点按钮 -> JavaScript 发 AJAX -> SpringMVC 的 ReservationController -> ReservationServiceImpl -> ReservationMapper 的 insert -> 返回 JSON -> 页面刷新。能说清这条链路的每一步,答辩时比背十页概念都管用。
模块之间有一些约定要提前说明。会员和课程之间是多对多,中间用预约记录表 reservation 解耦;私教和课程实际上是“教练上某节课”的关联,所以课程表里通常带 coach_id。器材表相对独立,但一定有一个状态字段,表示正常、维修中还是报废。系统管理线要单独建 admin 表,不要和 member 表混在一起,登录逻辑可以共用同一个 session,但数据表必须分开,因为管理员的角色和会员字段完全不同。
2.3 数据库表设计:会员、预约、课程的核心表与字段规范
核心表通常不超过六张:admin、member、coach、course、reservation、equipment。我按自己的建表习惯列一份常见字段设计,字段用下划线命名,Java 实体用驼峰命名,MyBatis 打开自动映射之后可以免去大量 resultMap。
| 表名 | 关键字段 | 类型 | 说明 |
|---|---|---|---|
| admin | id, username, password, role | int/varchar | 后台登录账号,密码存 MD5 |
| member | id, card_no, name, phone, sex, status | int/varchar/tinyint | card_no 唯一,status 表示正常/停卡 |
| coach | id, name, phone, major, level, intro | int/varchar | level 表示初/中/高级教练 |
| course | id, name, type, coach_id, start_time, remain_num | int/varchar/datetime | type 区分团操/私教 |
| reservation | id, member_id, course_id, appoint_time, status | int/datetime | member_id + course_id 做联合索引 |
| equipment | id, name, count, state, buy_time | int/varchar | state 表示器材状态 |
建表脚本有几个容易踩的点。第一,字符集用 utf8mb4 而不是 utf8,会员备注或昵称一旦带特殊字符,utf8 会直接报Incorrect string value。第二,外键可以不用,建议只保留逻辑外键,因为大量外键约束会在初始化数据时把自己绊倒。第三,时间字段尽量用 datetime 而不是 timestamp,既避免 2038 年问题,也减少时区干扰。后面我会专门讲时区导致的连接失败。
索引不要贪多,每一张表三五个足够。会员表的 card_no 建唯一索引,预约表给 member_id 和 course_id 建普通索引,单表数据量上去之前,这些索引足够应付。下面这段建表脚本可以当作初始模板:
CREATE TABLE `member` ( `id` INT NOT NULL AUTO_INCREMENT, `card_no` VARCHAR(32) NOT NULL, `name` VARCHAR(50) NOT NULL, `phone` VARCHAR(20) DEFAULT NULL, `sex` TINYINT DEFAULT 1 COMMENT '1男 2女', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `status` TINYINT DEFAULT 1 COMMENT '1正常 2停卡', PRIMARY KEY (`id`), UNIQUE KEY `uk_card_no` (`card_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表';这段脚本里 card_no 是业务唯一键,建唯一索引后,重复卡号会在数据库层直接被挡下,比 Java 代码里先 SELECT 再 INSERT 可靠得多。create_time 用 DATETIME 加默认值,插入时不用手动赋值。status 用 TINYINT 而不是字符串,减小数据体积,也方便在 Java 里用一个整数做状态映射。工程里记得在 MyBatis 配置中打开 mapUnderscoreToCamelCase,否则 card_no 字段查出来,Java 属性的 cardNo 会一直是 null。
3. 项目导入与本地运行:从压缩包到浏览器出现登录页,十五分钟的操作顺序
3.1 解压之后先别点开 IDE:先看这三个文件
拿到一个带“源码+数据库+使用文档”的压缩包,第一步不是 IDEA 打开,而是先解压看目录。常见结构是一个 Maven 工程,里面有 pom.xml;一个 sql 脚本,大概率叫 gym.sql、club.sql 或 fitness.sql;一份使用文档,可能是 Word 或 Markdown。我习惯用文本编辑器直接打开 sql 脚本看三层信息:第一,有没有 CREATE DATABASE;如果没有,需要自己在 MySQL 里先建库;第二,字符集是不是 utf8mb4;第三,有没有 DROP TABLE,这决定脚本能不能重复执行。
接着看 pom.xml,确认几个关键依赖是否有引入:spring-webmvc、mybatis、mybatis-spring、mysql-connector-java,以及连接池用的是 druid、c3p0 还是 dbcp。这个判断直接决定后面的配置文件怎么写。网上很多教程默认 druid,但实际毕设项目使用 dbcp 的也不少见,参数名对不上,启动就会失败。
3.2 导入 IDEA 并用 Maven 整理依赖
选择 IDEA 的File -> New -> Project from Existing Sources,选中 pom.xml,让 IDEA 以 Maven 方式导入。导入后先执行一次清理和编译:
mvn clean install -DskipTests这段命令会把整个工程编译一遍,并把依赖拉到本地 Maven 仓库。第一步执行可能很慢,如果等了很久还在下载框架包,检查一下 Maven 的 settings.xml 是否配置了国内镜像。-DskipTests表示跳过测试代码,很多毕设工程没有完整的 test,但保留这个参数更保险。
编译通过后,注意 target 目录里是否生成了 class。如果发现只有 jar 没有编译产物,多半是 pom.xml 里的 maven.compiler.source 和 maven.compiler.target 跟本机 JDK 版本不一致。我见过环境是 JDK 17、工程配置却是 1.8,直接编译报错的情况,最后统一改成与 JDK 17 一致才通过。第一次跑这种源码包,版本匹配比代码本身更值得花时间。
3.3 数据库初始化与 jdbc 配置:字符集、驱动、连接池参数一次说清
数据库版本上,这个工程用 MySQL 5.7 最稳,8.0 也可以,但驱动必须换成 8.0.x,并且 URL 要带时区参数。执行 SQL 脚本时,字符集要显式指定:
mysql -u root -p --default-character-set=utf8mb4 source /home/user/workspace/gym.sql--default-character-set=utf8mb4是为了让脚本里的中文备注和初始数据正常写入。如果你用 Navicat 或 DataGrip,也要先选中目标数据库,再右键“运行 SQL 文件”,不要直接新建查询把整个脚本粘进去。因为脚本里如果有 USE 语句,执行完会跳到另一个库,后面连接时就会一直提示表不存在。
脚本执行完,用两个命令确认表结构和索引是否就位:
SHOW TABLES; SHOW CREATE TABLE member;SHOW CREATE TABLE能直接看到表的字符集和索引信息,比 SELECT 更直观。确认没问题后,修改工程里的数据库连接文件,一般叫 jdbc.properties,内容类似:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/gym?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456 druid.initSize=5 druid.maxActive=10 druid.minIdle=2 druid.maxWait=60000前四行是必改项,用户名密码改成自己本机的。URL 里useSSL=false是必须的,MySQL 8.0 对 SSL 处理更严格,不开容易报连接警告;serverTimezone=Asia/Shanghai解决之前提到的时区问题;characterEncoding=utf8保持即可,数据库表用的是 utf8mb4,JDBC 连接参数里写 utf8mb4 反而会被部分老驱动当成未知字符集。后四行是 druid 连接池参数,initialSize 是启动时初始化连接数,maxActive 是最大活跃连接数,minIdle 是最小空闲数,maxWait 是等待连接的最大毫秒数。毕设场景并发量不大,initSize 设 5、maxActive 设 10 就够,调太大反而会把本机 MySQL 的连接数占满。
3.4 配置 Tomcat 启动:war 包与登录页验证
如果工程是 war 包方式,IDEA 里可以走Run -> Edit Configurations -> Tomcat Server -> Local -> Deployment -> Artifact。但我习惯先不用 IDEA 的 Tomcat 集成,直接用命令行打一次包:
mvn clean package -DskipTests然后把 target 目录里生成的 .war 文件复制到 Tomcat 的 webapps 目录下,再启动 Tomcat。这种方式能避开 IDEA 的 Tomcat 插件偶尔抽风的问题,排查起来也简单。启动成功后 Tomcat 会自动解压 war 包,访问路径是http://localhost:8080/<war包名>。比如 war 包叫 gym.war,那登录页就是http://localhost:8080/gym/login。
启动日志里重点看两行:Deployment of web application archive ... has finished和Tomcat started on port(s): 8080。如果出现 404,先看控制台有没有 Java 异常,不要急着改代码。登录页一般会提供一个默认管理员账号,有些工程把初始账号写在 SQL 脚本里,可以用SELECT * FROM admin;查出来;如果密码字段是 MD5 加密,就执行UPDATE admin SET password = MD5('123456') WHERE username = 'admin';,把初始密码固定下来。
提示:执行 SQL 脚本前先备份当前库,避免脚本里自带的 DROP TABLE 把已有数据清掉。
4. 核心业务做厚:会员办卡与课程预约背后的 SSM 常用注解
4.1 Controller 层:@RequestMapping、@ResponseBody 怎么落到预约接口上
Controller 层是整条请求链的入口。下面这段代码是一个标准的预约接口:
@Controller @RequestMapping("/reservation") public class ReservationController { @Autowired private ReservationService reservationService; @RequestMapping(value = "/book", method = RequestMethod.POST) @ResponseBody public Map<String, Object> book(@RequestParam("memberId") Integer memberId, @RequestParam("courseId") Integer courseId) { Map<String, Object> result = new HashMap<>(); try { reservationService.book(memberId, courseId); result.put("code", 0); result.put("msg", "预约成功"); } catch (Exception e) { e.printStackTrace(); result.put("code", 1); result.put("msg", e.getMessage()); } return result; } }SpringMVC 收到 POST 请求后,按 @RequestParam 把请求参数绑定到 memberId 和 courseId;方法返回一个 Map,@ResponseBody 再通过 Jackson 把它转成 JSON 输出给前端。用 Map 而不是自定义 Result 类是很多毕设工程的做法,省一个类。但代码里的 e.printStackTrace() 只适合开发期,答辩演示时如果报错,控制台会刷大段堆栈,换成 logger.error 会更体面。
参数说明不复杂:class 级别上的 @RequestMapping("/reservation") 和方法上的 @RequestMapping("/book") 会拼接成/reservation/book。method = RequestMethod.POST 限制了请求方式,防止课程预约这种写操作被 GET 直接触发。如果你习惯用新式注解,可以写 @PostMapping("/book"),效果一样,但完整写法更便于理解原理。
4.2 Service 层:@Transactional 让“扣余量+写预约”保持一致
预约业务不能只 INSERT 一条记录,还要把课程表的剩余名额减一。这两个数据库操作必须在一个事务里,否则可能出现预约记录写进去了、余量却没减的情况。下面是 Service 层的核心方法:
@Service public class ReservationServiceImpl implements ReservationService { @Autowired private MemberMapper memberMapper; @Autowired private CourseMapper courseMapper; @Autowired private ReservationMapper reservationMapper; @Override @Transactional(rollbackFor = Exception.class) public void book(Integer memberId, Integer courseId) { Member member = memberMapper.selectByPrimaryKey(memberId); if (member == null || member.getStatus() == null || member.getStatus() != 1) { throw new RuntimeException("会员不存在或已停卡"); } Course course = courseMapper.selectByPrimaryKey(courseId); if (course == null || course.getRemainNum() <= 0) { throw new RuntimeException("课程无效或已满员"); } Reservation reservation = new Reservation(); reservation.setMemberId(memberId); reservation.setCourseId(courseId); reservation.setStatus(0); reservation.setAppointTime(new Date()); reservationMapper.insert(reservation); courseMapper.decreaseRemainNum(courseId); } }@Transactional 声明方法内所有数据库操作归属于同一个事务。注意 rollbackFor = Exception.class 是必须的,Spring 默认只对 RuntimeException 回滚,如果这里抛的是受检异常,不加 rollbackFor 就不会回滚。这也是 Java 面试题里经常考的细节。
courseMapper.decreaseRemainNum(courseId) 对应的 SQL 必须带条件,保证不会减成负数:
<update id="decreaseRemainNum"> UPDATE course SET remain_num = remain_num - 1 WHERE id = #{id} AND remain_num > 0 </update>带AND remain_num > 0的更新语句,在并发场景下靠数据库行锁保护,可以避免超卖。毕设项目里能用事务把流程讲清楚已经够用,但如果你想把“防止超卖”写进论文,这条 SQL 会是一个亮点。要是 Mapper 接口没有被 @MapperScan 或 @Mapper 扫描到,Spring 启动时就会报No qualifying bean of type ReservationMapper,这也是新手最常见的装配失败原因。
4.3 Mapper 层:动态 SQL 与 #{} 预编译,还有 mapUnderscoreToCamelCase
查询课程列表时,通常要支持按教练、按课程类型、按开始时间筛选。同一套查询,用户可能不填任何条件,也可能只填一个。用动态 SQL 最合适:
<select id="selectCourseByCondition" parameterType="map" resultType="com.gym.entity.Course"> SELECT * FROM course WHERE 1 = 1 <if test="coachId != null"> AND coach_id = #{coachId} </if> <if test="courseType != null and courseType != ''"> AND type = #{courseType} </if> <if test="startTime != null"> AND start_time >= #{startTime} </if> ORDER BY start_time ASC </select>这里的 parameterType 是 Map,Mapper 接口方法一般写成List<Course> selectCourseByCondition(Map<String, Object> params)。<if>标签动态拼 SQL,coachId 为空时不拼对应条件,这样同一个方法既能查全部课程,也能精确筛选。>=是 XML 里对大于号的转义,因为 XML 解析阶段不允许裸的大于号。#{}是预编译占位符,能防止 SQL 注入;如果这里图省事用${},用户传进来的值会被直接拼进 SQL,这是个很严重的隐患。
还有一个字段映射的坑。数据库字段是 course_id,Java 属性是 courseId,如果 MyBatis 没开启驼峰映射,查询结果里 courseId 会一直是 null。解决办法是在 mybatis-config.xml 中加入:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>这个设置全局生效,开启后数据库的 course_id 自动对应 Java 的 courseId,省掉大量手写 resultMap。注意 XML 的 mapper 文件名字必须跟 Mapper 接口全限定名对应,id 必须跟方法名对应,否则运行时会报Invalid bound statement,这一点在第 5 章还会展开。
4.4 前端配合:一个 ajax 请求和一个约定好的返回结构
课程列表页点击“预约”按钮,通常是用 jQuery 发异步请求,代码大概是这个样子:
$.ajax({ url: contextPath + "/reservation/book", type: "POST", data: { memberId: currentMemberId, courseId: courseId }, dataType: "json", success: function (res) { if (res.code === 0) { alert(res.msg); location.reload(); } else { alert(res.msg); } }, error: function () { alert("网络异常"); } });contextPath 是项目根路径,从 JSP 的${pageContext.request.contextPath}取,千万不要在 JS 里写死路径。success 回调里判断 res.code,0 表示成功,1 表示业务失败。这里的关键是前后端必须约定好返回结构,Controller 返回 Map 里包含 code 和 msg,前端就按这两个字段解析。如果某个接口不小心返回了 String 类型并且写死了 JSON 字符串,那么这里 res.code 就会是 undefined,页面会永远走错误分支。这类问题在毕设联调阶段非常常见,排查时先看浏览器 Network 面板里的响应体长什么样,再决定改前端还是改后端。
5. 排查手册:SSM 健身房系统启动失败、数据乱码、预约不生效的六条踩坑记录
5.1 Tomcat 启动报 ClassNotFoundException:war 包缺依赖
现象:Tomcat 启动后控制台出现 ClassNotFoundException,常见的找不到 org.springframework.web.context.ContextLoaderListener,页面 404 打不开。原因:war 包里没有把 Maven 依赖打进去,常见于别人电脑上编译好的 target 目录被直接拷过来用,里面没有 lib 目录。解决:执行mvn clean package,确认生成的 .war 文件里 WEB-INF/lib 下有 Spring 相关 jar 包。如果用的是 IDEA 的 Artifact 部署,检查 Artifact 配置里是否添加了 lib 目录。我的习惯是直接跑mvn clean package -DskipTests,把 war 丢到 Tomcat 里验证,这样最容易复现问题。
5.2 MySQL 连接超时:The last packet sent successfully to the server was 0 milliseconds ago
现象:系统跑了一会儿,刷新页面突然报数据库连接失败,重启 Tomcat 又恢复正常。原因:本地 MySQL 的 wait_timeout 默认是 8 小时,连接池里的连接长期空闲,被服务端断开了,但应用并不知道,继续拿着旧连接去执行 SQL。解决:在 druid 连接池配置里加连接检查参数。配置项加在 jdbc.properties 中,设置druid.testWhileIdle=true和druid.validationQuery=SELECT 1,借出连接前先探测一次。如果是 c3p0,参数名不同,但思路一样:定期探活,剔除死连接。
5.3 中文乱码满天飞:表、连接、页面三层统一字符集
现象:新增会员或课程后,页面显示“???”,MySQL 里查出来也是问号。原因可能出现在三个环节:数据库表字符集不是 utf8mb4、JDBC URL 没有 characterEncoding、JSP 页面头部缺失 charset=UTF-8。解决:先执行SHOW CREATE TABLE member;看表结构,确认 CHARSET=utf8mb4;再查 jdbc.properties 里 URL 是否带 useUnicode=true&characterEncoding=utf8;最后确认 JSP 页面有没有<%@ page contentType="text/html;charset=UTF-8" %>。还有一种隐藏情况是 sql 脚本文件本身是 GBK 编码,导入时又没指定 utf8mb4,所以脚本文件一律用 UTF-8 保存,开头加SET NAMES utf8mb4;。
5.4 Mapper 方法报 Invalid bound statement:XML 没有被 Spring 扫描
现象:调用预约查询时抛org.apache.ibatis.binding.BindingException: Invalid bound statement (not found),但 Mapper 接口和 XML 文件看着都在。原因:MyBatis 的 XML 没有被 Spring 扫描到,或者 XML 没有打进 target/classes 目录。解决:检查 Spring 配置里 mapperLocations 有没有指向classpath:mapper/*.xml,再打开 target/classes 看里面的资源和源目录是否一致。Maven 工程如果出现源目录里的 XML 没被复制到 classes 下,需要在 pom.xml 里显式声明 resources 目录。我的经验是:Mapper XML 放在 src/main/resources 下最省事,不要和 Java 源文件混在一起。
5.5 连接池拒绝连接:too many connections
现象:本地启动系统后,MySQL 客户端和系统页面同时报Too many connections。原因:连接池 maxActive 设置过大,或之前调试时多个进程没释放连接,数据库的连接数被占满。解决:先执行SHOW PROCESSLIST;,看是否有大量 Sleep 连接挂着。如果全是本项目连接,就把 maxActive 调回 10,并把 ensure 连接池的 removeAbandoned 打开,设置druid.removeAbandoned=true和druid.removeAbandonedTimeout=300,超过五分钟未归还的连接会被强制回收。这条配置很适合初学者,它能掩盖掉一部分因为忘关连接导致的泄漏问题。
5.6 改了数据库表结构但代码没同步:SQL 列不存在
现象:为了给会员表加余额字段,我直接在 Navicat 里执行 ALTER TABLE 加了一列,结果开启某个查询时报Unknown column 'balance' in 'field list'。原因:源码包里给的 SQL 脚本和 Java 代码不是同一个版本,代码里 Mapper 已经写了 balance 字段,数据库脚本里却没有。解决:以代码为准去补数据库,而不是以数据库为准改代码。正确做法是先对比SHOW CREATE TABLE member;和实体类、Mapper XML 里的字段清单,把缺失的字段补充到表里,再把ALTER TABLE语句回写进 gym.sql,保证源码包里的 SQL 脚本永远是新的。否则换一台电脑重新导入,问题会再出现一次。
6. 答辩和演示前,把“能跑”升级成“会讲”:我用五个小动作避免当场翻车
演示不是把系统点一遍就行,而是要控制在三分钟之内讲清楚“谁会用它、流程怎么走、数据怎么变”。我一般会先准备一套最小演示数据,清空预约表,只保留三个会员、两个教练、五条课程记录,这样每一步操作的结果都可预期,不会出现一页几十条数据让老师不知道看哪里。
第二个小动作是准备两个浏览器窗口,普通窗口登录管理员,无痕窗口登录会员端。SSM 靠 Session 区分登录状态,同一个浏览器的 session 会互相覆盖,两个角色来回切非常容易把登录状态搞混,这也是演示时最常见的翻车点。
第三,把管理员密码改成一段可见的 SQL 更新语句,像UPDATE admin SET password = MD5('123456') WHERE username = 'admin';,然后把这条 SQL 写进演示文档。老师看到数据库里不是明文密码,会认为你注意到了最基本的密码安全。
第四,在动手二次开发之前,先给原版源码建立一个 Git 仓库并提交一个初始版本。这样改崩了任何东西都可以随时回滚,而不是到了答辩前夜用“复制一份备份”这种笨办法救场。
git init git add . git commit -m "baseline before refactor"第五,验证事务和异常流程。在演示时故意把一个课程的剩余名额设为 0,然后去点击预约,页面应当提示“课程无效或已满员”,数据库里也不会插入新记录。这一个动作就能把 Service 层事务的讲解落地,比口头描述“我有事务控制”有说服力得多。
我曾经在答辩前夜直接把 member 表改名成 t_member,结果所有 Mapper 里的 SQL 全部失效,第二天演示时全场看着我调数据库连接,最后靠前一晚的 Git 提交才救回来。从那以后我养成一个习惯:任何结构改动,先把原版源码和数据库脚本备份一份,再按小节逐步推进。希望帮到你。
本文还有配套的精品资源,点击获取