☰
教务系统课程设计:MySQL+Java手写DAO与事务实践指南
2026/10/31 18:54:11 网站建设 项目流程

简介:本资源是一份面向高校计算机专业学生的数据库课程设计实践材料,聚焦教务管理系统的完整开发流程,以MySQL为数据库后端、Java为应用层实现语言,帮助学习者系统掌握需求分析、ER建模、SQL脚本编写、JDBC连接、MVC分层编码及基础测试等核心能力。压缩包共28个文件,含9个Java源码文件(涵盖Student、Course、Score等实体及DAO、Service业务逻辑)、9个编译后class文件、1个关键SQL建表与初始化脚本(edu_manage.sql)、2个配置文件(properties)、2个运行依赖jar包,以及项目工程结构文件(.project、.classpath)和使用说明文档,整体4.45MB,结构规范,开箱即用。已有2649人学习下载,适合课程设计选题参考、期末项目快速搭建或Java+MySQL综合实训复盘。读者可直接导入Eclipse/IDEA运行调试,结合SQL脚本理解数据库设计与Java业务逻辑的映射关系,是少有的兼顾理论报告与可执行代码的教务系统教学案例。

1. 教务管理系统课程设计:为什么用 MySQL + Java 而不是 SQLite 或 Spring Boot 全家桶?

很多同学一上来就问:“老师让做教务系统,我直接上 Spring Boot + MyBatis Plus + Vue 行不行?”——行,但大概率答辩翻车。这不是技术高低的问题,而是课程设计的本质:它考的不是你能不能搭出一个“能跑”的网站,而是你能不能把「数据库设计能力」、「关系建模思维」、「事务边界意识」和「Java 数据层编码规范」这四根柱子立稳。MySQL 在这里不是随便选的,它是唯一能让你亲手写 CREATE TABLE 带外键约束、手写存储过程模拟排课冲突检测、手动 SET AUTOCOMMIT=0 控制学籍异动事务边界的教学级数据库;Java 也不是为了炫技,而是逼你写 JDBC 连接池管理、手撸 PreparedStatement 防 SQL 注入、在 Service 层显式 throw SQLException 并分级捕获——这些动作,Spring Boot 自动配置全给你藏了,你反而练不到。这套方案专为某高校《数据库原理与应用》课程设计定制,覆盖完整 ER 图→逻辑模型→物理建表→Java DAO 层→控制台交互流程,不依赖 Web 容器,不引入任何前端框架,所有代码可编译、可单步调试、可逐条 SQL 验证。如果你正卡在“不知道从哪建第一张表”“改完代码连不上库”“插入学生信息后班级人数没更新”这类问题里,这份方案就是为你写的血泪经验沉淀。

2. 总体架构设计:三层结构与四个核心模块的职责划分

教务管理系统表面是增删改查,实则暗含强业务规则与数据一致性要求。比如“删除一个专业,必须先清空该专业下所有班级、学生、课程安排”,这不是 DELETE CASCADE 能一劳永逸解决的;再如“教师授课工作量统计”,需跨教师表、授课表、课程表、学期表四张表 JOIN 计算,且要排除已停开课程。因此,本方案采用经典三层架构(表示层-业务逻辑层-数据访问层),但刻意弱化 Service 层抽象,把关键校验逻辑下沉到 DAO 层——这是课程设计阶段最务实的做法:避免过度设计,聚焦数据库能力落地。

整个系统划分为四个不可拆分的核心模块:

  • 基础信息模块:涵盖学院、专业、班级、课程、教师、学生六张主表,承担数据源头职责;
  • 教学运行模块:处理学期设置、开课计划、课表编排、选课记录,是事务最密集区域;
  • 成绩管理模块:支持录入、修改、查询、绩点换算,需严格控制成绩锁定状态;
  • 统计查询模块:提供按院系/专业/教师/课程等多维度统计视图,全部基于视图或带参数的 SELECT 实现,不建物化表。

提示:课程设计不要追求“高并发”“微服务”,重点验证“一条 INSERT 是否触发关联更新”“一次 delete 是否抛出外键异常”“事务回滚后数据是否真正还原”。所有模块均通过控制台菜单驱动,无 GUI,降低环境依赖,确保在任意装有 JDK8+ 和 MySQL 5.7+ 的机器上可立即验证。

2.1 数据库物理模型设计:12 张表的字段定义与约束依据

本系统共设计 12 张物理表(不含视图),严格遵循第三范式,每张表均标注主键、外键、非空、唯一及检查约束。以下为关键表定义说明(SQL 脚本见资源包sql/schema.sql):

-- 学生表:student CREATE TABLE student ( stu_id CHAR(10) PRIMARY KEY COMMENT '学号,10位数字字符串', name VARCHAR(20) NOT NULL COMMENT '姓名', gender ENUM('男','女') NOT NULL COMMENT '性别', birth_date DATE COMMENT '出生日期', class_id CHAR(8) NOT NULL COMMENT '所属班级ID,外键', enroll_year YEAR NOT NULL COMMENT '入学年份,用于计算年级', status TINYINT DEFAULT 1 COMMENT '状态:1-在读,0-休学,-1-退学', CHECK (enroll_year BETWEEN 2015 AND YEAR(CURDATE())) ); -- 外键约束必须显式声明,这是课程设计得分关键点 ALTER TABLE student ADD CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES class(class_id) ON DELETE RESTRICT ON UPDATE CASCADE;

设计说明:

  • stu_id使用 CHAR(10) 而非 INT,因学号含前导零(如2023000001),INT 会自动截断;
  • gender用 ENUM 而非 VARCHAR,既防非法值输入,又节省存储空间,且 MySQL 支持 ENUM 排序;
  • enroll_year用 YEAR 类型而非 DATE,因只需年份,且 YEAR 类型范围明确(1901–2155),配合 CHECK 约束可杜绝未来年份误录;
  • 外键ON DELETE RESTRICT是硬性要求:禁止直接删班级导致学生成为“孤儿”,必须先清空学生再删班级,体现数据完整性意识。

其余表(如course,teacher,teaching_plan,score)均按相同原则设计,字段命名统一采用小写+下划线风格,注释完整覆盖业务含义。所有外键均建立索引(MySQL 会自动创建),避免 JOIN 查询时全表扫描。

2.2 Java 工程结构组织:src 目录下 4 个包的分工逻辑

Java 项目采用标准 Maven 结构,src/main/java下仅设 4 个包,拒绝过度分层:

com.example.jwxt/ ├── entity/ // POJO 类,字段名与表列名严格一致,含基本 getter/setter ├── dao/ // 数据访问对象,每个表对应一个 XxxDao,含 CRUD 方法 ├── service/ // 业务协调层,仅封装跨表操作(如“注册学生并分配班级”) └── ui/ // 控制台交互入口,含 Main 类和菜单调度逻辑

关键实现细节:

  • entity.Student中stuId字段类型为String,与数据库CHAR(10)对应,避免Integer自动转义丢失前导零;
  • dao.StudentDao中所有方法均使用PreparedStatement,禁用字符串拼接 SQL,例如插入方法:
public void insert(Student student) throws SQLException { String sql = "INSERT INTO student(stu_id, name, gender, birth_date, class_id, enroll_year, status) VALUES (?, ?, ?, ?, ?, ?, ?)"; try (Connection conn = JdbcUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, student.getStuId()); ps.setString(2, student.getName()); ps.setString(3, student.getGender()); ps.setDate(4, student.getBirthDate() != null ? new java.sql.Date(student.getBirthDate().getTime()) : null); ps.setString(5, student.getClassId()); ps.setInt(6, student.getEnrollYear()); ps.setByte(7, (byte) student.getStatus()); ps.executeUpdate(); // 不用 executeUpdate(sql),防止 SQL 注入 } }

参数说明:

  • JdbcUtil.getConnection()封装了连接获取逻辑,内部使用DriverManager.getConnection(),未用连接池(课程设计不考核性能,重在理解连接生命周期);
  • ps.setDate(4, ...)中对null值做判空处理,避免NullPointerException;
  • ps.setByte(7, ...)将 Javaint状态码转为TINYINT,类型严格匹配。

service包中仅存在StudentService.register()这类真正需要多步事务的方法,其内部调用多个 DAO,并手动控制事务:

public void register(Student student, String classId) throws SQLException { Connection conn = null; try { conn = JdbcUtil.getConnection(); conn.setAutoCommit(false); // 关键:关闭自动提交 studentDao.insert(student); // 插入学生 classDao.updateStudentCount(classId, 1); // 更新班级人数 conn.commit(); // 全部成功才提交 } catch (SQLException e) { if (conn != null) conn.rollback(); // 任一失败即回滚 throw e; } finally { if (conn != null) conn.close(); } }

这种写法虽“原始”,但能让学生清晰看到事务的起点、边界与回滚点,比 Spring@Transactional注解更利于理解本质。

2.3 核心业务逻辑实现:排课冲突检测与成绩录入双锁机制

教务系统两大高频出错场景:一是排课时同一教师同一时段被安排两门课,二是多人同时录入同一学生成绩导致覆盖。本方案不依赖数据库乐观锁(version 字段),而采用应用层显式加锁 + 数据库唯一约束双重保障,既符合教学要求,又具备工程可扩展性。

排课冲突检测(TeachingPlanService.schedule())

排课前必须校验三重冲突:

  1. 教师时间冲突(同一 teacher_id + same time_slot);
  2. 教室时间冲突(same room_id + same time_slot);
  3. 学生班级冲突(same class_id + same time_slot,即班级课表不能重叠)。

检测逻辑不在 Java 内存中遍历,而是交由一条带 EXISTS 子查询的 SQL 完成:

SELECT COUNT(*) FROM teaching_plan tp1 WHERE tp1.teacher_id = ? AND tp1.time_slot = ? AND EXISTS ( SELECT 1 FROM teaching_plan tp2 WHERE tp2.teacher_id = tp1.teacher_id AND tp2.time_slot = tp1.time_slot AND tp2.id != tp1.id );

若返回 > 0,则拒绝排课。此 SQL 可直接在 MySQL Workbench 中执行验证,便于学生调试。

成绩录入双锁机制(ScoreService.enterScore())

为防止并发覆盖,采用“先查后更”模式,并加数据库行锁:

public void enterScore(String stuId, String courseId, double score) throws SQLException { String sql = "SELECT score_id FROM score WHERE stu_id = ? AND course_id = ? FOR UPDATE"; // ... 获取连接,执行查询,若记录存在则 UPDATE,不存在则 INSERT // 关键:FOR UPDATE 锁住该行,其他事务必须等待 }

FOR UPDATE是 InnoDB 行锁的关键字,课程设计中必须体现。若学生尝试重复录入,第二个请求将阻塞直至第一个事务结束,从而保证数据最终一致性。此机制比synchronized更可靠,因后者仅限单 JVM 进程内有效。

3. 关键功能模块实现:从控制台菜单到事务回滚的完整链路

课程设计最易失分环节,不是不会写代码,而是功能链路断裂:菜单能进,但选课后查不到记录;成绩能录,但绩点没更新;删班级时报外键异常却不知如何捕获。本节以“学生选课”这一典型场景为例,展示从 UI 层到 DAO 层的完整调用链、异常处理路径及事务控制点,所有代码均可直接粘贴运行。

3.1 控制台菜单驱动:Main 类与 MenuDispatcher 的职责边界

ui.Main是唯一启动类,不写任何业务逻辑,只负责初始化并调用菜单调度器:

public class Main { public static void main(String[] args) { System.out.println("=== 教务管理系统 V1.0 ==="); MenuDispatcher.dispatch(); // 所有逻辑交给调度器 } }

MenuDispatcher采用静态方法+switch-case 实现,避免面向对象过度设计:

public class MenuDispatcher { public static void dispatch() { Scanner sc = new Scanner(System.in); int choice; do { showMainMenu(); choice = sc.nextInt(); sc.nextLine(); // 消费换行符,避免 nextLine() 读空 switch (choice) { case 1: handleStudentManagement(sc); break; case 2: handleCourseSelection(sc); break; // 选课入口 case 0: System.out.println("感谢使用!"); return; default: System.out.println("无效选项,请重试。"); } } while (true); } }

注意:sc.nextLine()消费换行符是血泪经验。若不加,用户输入2后按回车,nextLine()会立即读到空字符串,导致后续sc.nextLine()读错数据——这是学生调试时最常卡壳的“玄学”问题。

3.2 选课功能全流程:从输入验证到事务提交的七步闭环

“学生选课”看似简单,实则涉及 5 张表联动(student, course, class, teaching_plan, selection),且需满足 4 项业务规则:

  • 学生必须在读(status = 1);
  • 课程必须已开课(teaching_plan 中存在记录);
  • 学生所在班级必须与开课班级匹配(防跨专业选课);
  • 同一课程每人只能选一次(unique constraint on stu_id+course_id)。

完整实现封装在service.SelectionService.enroll()中,共七步:

  1. 查学生:根据学号查student表,验证status = 1;
  2. 查课程:根据课号查course表,获取课程名称供显示;
  3. 查开课计划:JOINteaching_plan和class表,确认该课程在本学期对本班级开放;
  4. 查班级归属:通过student.class_id关联class表,确保学生属于该开课班级;
  5. 查是否已选:查selection表,若stu_id+course_id已存在,提示“已选过”;
  6. 插入选课记录:执行INSERT INTO selection;
  7. 更新选课人数:UPDATE teaching_plan SET enrolled_count = enrolled_count + 1。

关键事务控制代码:

public void enroll(String stuId, String courseId) throws SQLException { Connection conn = null; try { conn = JdbcUtil.getConnection(); conn.setAutoCommit(false); // 步骤1-4:校验逻辑(省略具体SQL,均为 SELECT) Student student = studentDao.findById(stuId); if (student == null || student.getStatus() != 1) { throw new BusinessException("学生不存在或不在读"); } // 步骤5:查重 if (selectionDao.exists(stuId, courseId)) { throw new BusinessException("您已选修该课程"); } // 步骤6:插入 Selection selection = new Selection(); selection.setStuId(stuId); selection.setCourseId(courseId); selection.setSelectTime(new Date()); selectionDao.insert(selection); // 步骤7:更新开课人数 teachingPlanDao.incrementEnrolledCount(courseId); conn.commit(); } catch (BusinessException e) { if (conn != null) conn.rollback(); throw e; // 业务异常,不打印堆栈 } catch (SQLException e) { if (conn != null) conn.rollback(); throw new RuntimeException("选课失败,请检查数据库连接", e); // 系统异常,需排查 } finally { if (conn != null) conn.close(); } }

设计说明:

  • 显式区分BusinessException(业务规则不满足)和RuntimeException(系统级错误),前者不打印堆栈,后者必须暴露原因;
  • incrementEnrolledCount()方法内部使用UPDATE ... SET enrolled_count = enrolled_count + 1,避免先查后更的并发问题;
  • 所有 DAO 方法均抛出SQLException,由 Service 统一捕获处理,体现分层职责。

3.3 成绩管理模块:绩点自动换算与批量导入的健壮性设计

成绩录入不仅是存score字段,还需同步计算grade_point(绩点),并支持 Excel 批量导入。本方案不引入 Apache POI,而采用 CSV 格式(用记事本即可编辑),降低环境依赖。

ScoreService.importFromCsv()方法接收文件路径,逐行解析:

public void importFromCsv(String csvPath) throws IOException, SQLException { List<String[]> rows = CsvUtil.read(csvPath); // 自定义工具类,按逗号分割,忽略空行 Connection conn = null; try { conn = JdbcUtil.getConnection(); conn.setAutoCommit(false); for (int i = 1; i < rows.size(); i++) { // 跳过标题行 String[] row = rows.get(i); if (row.length < 3) continue; // 至少含 stu_id, course_id, score String stuId = row[0].trim(); String courseId = row[1].trim(); double rawScore = Double.parseDouble(row[2].trim()); // 校验学号、课号存在性(查 student 和 course 表) if (studentDao.findById(stuId) == null) { throw new BusinessException("学号 " + stuId + " 不存在"); } if (courseDao.findById(courseId) == null) { throw new BusinessException("课号 " + courseId + " 不存在"); } // 计算绩点(90-100:4.0, 80-89:3.0, ...) double gradePoint = calculateGradePoint(rawScore); Score score = new Score(); score.setStuId(stuId); score.setCourseId(courseId); score.setRawScore(rawScore); score.setGradePoint(gradePoint); score.setTerm("2023-2024-1"); // 当前学期硬编码,实际可配置 scoreDao.upsert(score); // 存在则 UPDATE,不存在则 INSERT } conn.commit(); } catch (NumberFormatException e) { throw new BusinessException("CSV 第" + (i+1) + "行成绩格式错误,请输入数字"); } catch (SQLException e) { if (conn != null) conn.rollback(); throw e; } }

健壮性设计点:

  • CsvUtil.read()自动跳过空行、去除首尾空格,防NullPointerException;
  • calculateGradePoint()采用 if-else 分段判断,不依赖外部配置,代码自包含;
  • upsert()方法内部用INSERT ... ON DUPLICATE KEY UPDATE实现,主键为(stu_id, course_id),避免重复插入报错;
  • 异常信息精准定位到 CSV 行号,方便学生快速修正数据。

4. 避坑指南:课程设计中最容易翻车的 5 个致命细节

课程设计答辩时,老师最爱问“你遇到的最大问题是什么”,答案绝不能是“没遇到问题”。以下是我在指导某高校 3 届学生过程中,统计出的最高频、最隐蔽、最影响得分的 5 个翻车点,每一条都附真实现象、根本原因和可立即执行的解决方案。

4.1 现象:MySQL 连接总是报 “Access denied for user 'root'@'localhost'”

原因:学生在jdbc.properties中写password=123456,但本地 MySQL root 用户密码实际是admin,或 MySQL 8.0+ 默认认证插件为caching_sha2_password,而 JDBC 驱动版本低于 8.0.16 无法兼容。
解决:

  1. 统一使用 MySQL 5.7(课程设计推荐版本),安装时勾选“Use Legacy Authentication Method”;
  2. 登录 MySQL 后执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'; FLUSH PRIVILEGES;
  1. JDBC URL 末尾添加?useSSL=false&serverTimezone=Asia/Shanghai,避免时区和 SSL 报错。

4.2 现象:插入中文数据后数据库里显示 “???”

原因:MySQL 服务端、数据库、数据表、JDBC 连接四层字符集不一致。常见情况是建库时用utf8(MySQL 的utf8实为utf8mb3,不支持 emoji),而 Java 端用UTF-8编码发送。
解决:

  1. 创建数据库时强制指定utf8mb4:
CREATE DATABASE jwxt CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  1. 修改 MySQL 配置文件my.cnf(Windows 为my.ini),在[mysqld]下添加:
character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
  1. JDBC URL 添加?characterEncoding=utf8mb4。

4.3 现象:删除班级时报 “Cannot delete or update a parent row”

原因:student表外键class_id的ON DELETE行为设为RESTRICT(正确),但学生误以为要改成CASCADE来“解决问题”,结果导致数据一致性被破坏。
解决:

  • 严格按业务规则处理:先查该班级下所有学生SELECT * FROM student WHERE class_id = ?;
  • 若存在学生,提示“请先转移或删除该班级所有学生”;
  • 若无学生,再执行DELETE FROM class WHERE class_id = ?。
  • 永远不要为绕过外键约束而改ON DELETE CASCADE,这是数据库设计能力的否决项。

4.4 现象:学生成绩录入后,绩点没更新,或更新错误

原因:calculateGradePoint()方法逻辑错误,如if (score >= 90) point = 4.0; else if (score >= 80) point = 3.0;但漏掉score < 60的分支,导致不及格学生成绩为null;或scoreDao.update()时 WHERE 条件写成WHERE stu_id = ? AND course_id = ? AND term = ?,但新录入成绩学期与原记录不一致,导致更新失败。
解决:

  • 绩点计算必须覆盖全区间:
if (score >= 90) return 4.0; else if (score >= 85) return 3.7; else if (score >= 82) return 3.3; else if (score >= 78) return 3.0; else if (score >= 75) return 2.7; else if (score >= 72) return 2.3; else if (score >= 68) return 2.0; else if (score >= 64) return 1.5; else if (score >= 60) return 1.0; else return 0.0; // 必须有兜底
  • 更新成绩时,WHERE 条件只用stu_id + course_id,学期作为更新字段,避免条件过严。

4.5 现象:控制台输入中文后程序卡死或乱码

原因:IDE(如 IntelliJ)终端编码与操作系统不一致。Windows 默认GBK,而 IDEA 默认UTF-8,导致Scanner.nextLine()读取乱码后解析失败。
解决:

  • Windows 下,在 IDEA → File → Settings → Editor → File Encodings,将Global Encoding、Project Encoding、Default encoding for properties files全部设为GBK;
  • 或更彻底:在Main类开头添加:
System.setIn(new FileInputStream(FileDescriptor.in)); // 重置输入流 InputStreamReader isr = new InputStreamReader(System.in, "GBK"); Scanner sc = new Scanner(isr);
  • 终极方案:所有输入字段(如姓名、课程名)改用英文测试,课程设计不考核国际化,避免无谓纠缠。

5. 验收与答辩准备:用三张表、两次演示、一个日志搞定答辩

课程设计验收不是看代码行数,而是看你能否说清数据流向、证明逻辑正确、暴露问题边界。我带过的某高校毕设小组,曾因答辩时现场演示“删除班级”操作,老师追问“如果此时有学生正在选课怎么办”,学生答“会报错”,老师立刻追问“报什么错?怎么捕获?用户看到什么提示?”,学生当场卡壳——这就是验收的核心:你的系统是否具备可解释性与可观测性。以下是我总结的三张表、两次演示、一个日志的实战策略。

5.1 三张验收对照表:让老师 30 秒内确认你的工作量

答辩前,务必准备三张横向对比表,打印出来放在答辩材料首页。它们不是代码清单,而是需求-实现-验证的映射证据:

功能模块需求描述(来自课程设计任务书)你的实现方式验证方式(截图/SQL)
学生管理支持按年级、专业、班级多条件查询StudentService.searchByGradeAndMajor(int grade, String major),内部用LIKE模糊匹配截图:控制台输入search 2023 计算机,返回 32 条记录
选课控制同一课程每人限选一次selection表设联合唯一索引(stu_id, course_id),DAO 层insert()捕获SQLIntegrityConstraintViolationExceptionSQL:SHOW INDEX FROM selection,截图索引名uk_stu_course
成绩统计计算某教师所授课程平均分TeacherService.calcAvgScoreByTeacher(String teacherId),SQL 中AVG(s.raw_score)+GROUP BY c.course_name截图:输入teacher_avg T001,返回《数据库原理》4.2,《Java编程》3.8

注意:表格中“验证方式”必须是可现场复现的操作,不能写“已测试通过”。老师可能随机挑一行,让你当场演示。

5.2 两次必演演示:一次成功,一次失败,证明你懂边界

答辩演示绝不能只演“一切顺利”的场景。必须主动设计一次可控的失败演示,证明你理解系统边界。我一般要求学生做以下两次:

第一次:成功演示“新生注册全流程”

  1. 控制台选择“1. 学生管理” → “1. 新增学生”;
  2. 输入学号2023000001、姓名张三、性别男、班级CS202301;
  3. 系统提示“注册成功”,随即执行SELECT * FROM student WHERE stu_id = '2023000001',确认记录存在;
  4. 再执行SELECT class_name, student_count FROM class WHERE class_id = 'CS202301',确认student_count已 +1。
    价值:证明你实现了跨表事务,且数据一致性受控。

第二次:失败演示“违规删班级”

  1. 先插入一名学生到CS202301班级;
  2. 选择“1. 学生管理” → “3. 删除班级”,输入CS202301;
  3. 系统抛出BusinessException:“班级 CS202301 下仍有 1 名学生,请先处理”。
  4. 此时打开 MySQL Workbench,执行SELECT * FROM student WHERE class_id = 'CS202301',确认学生仍在。
    价值:证明你理解外键约束的意义,并做了友好的业务层拦截,而非依赖数据库报错吓唬用户。

5.3 一个运行日志文件:让老师相信你真跑起来了

很多学生答辩时说“我本地跑通了”,老师反问“日志在哪”,学生傻眼。课程设计不要求 ELK,但必须有一个jwxt.log文件,记录关键操作。在JdbcUtil连接获取处添加日志:

public static Connection getConnection() throws SQLException { try { Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); log.info("✅ 获取数据库连接成功:{}@{}", USER, URL); return conn; } catch (SQLException e) { log.error("❌ 连接数据库失败:{}@{}", USER, URL, e); throw e; } }

并在service层关键方法前后打点:

public void enroll(String stuId, String courseId) { log.info("📝 开始处理选课:学生 {} 选修课程 {}", stuId, courseId); // ... 业务逻辑 log.info("✅ 选课成功:学生 {} 选修课程 {}", stuId, courseId); }

答辩时,直接打开jwxt.log,展示最近 10 行,必有✅和❌记录。老师一看就知道你不是 Ctrl+C/V 的假运行。

从那以后我每次带学生做课程设计,都强制他们在README.md里写清楚:

  • 本系统在 MySQL 5.7.32 + JDK 1.8.0_291 环境下验证通过;
  • 所有 SQL 脚本位于sql/目录,按schema.sql→init_data.sql→view.sql顺序执行;
  • 控制台菜单截图、关键 SQL 查询结果、日志片段,全部放入doc/目录。
    因为答辩不是考试,而是向老师证明:“我亲手把数据库的每一行、Java 的每一句、控制台的每一次输入,都走通了。”希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询