
简介基于Java Swing与MySQL实现的学生选课管理系统课程设计资源面向Java初学与课程设计人群提供一套可直接运行、复用的完整项目方案。系统囊括系统管理、数据管理、数据查询三大核心模块覆盖用户登录与密码设置、学生/课程/选课信息的新增删除修改、以及多维查询等典型功能运行于Eclipse/IDEA、JDK8、MySQL5.7以上环境功能链路完整适合理解Swing界面开发与MySQL数据库交互。资源包共3个文件总大小3.34MB包含项目源码压缩包、数据库初始化SQL脚本及一份课程设计实训报告docx按“源码—数据—报告”组织结构清晰便于对照使用。已有268人学习下载读者可借助完整源码快速部署调试参考报告梳理论文框架与设计思路高效完成课程作业或毕业设计。1. 学生选课管理系统Java Swing 与 MySQL 的典型落地组合如果你在学校或培训机构待过大概率见过这种场景教务老师还在用 Excel 维护几百人的选课名单学生要发邮件或者跑到办公室才能改选一次课。这套基于 Java Swing MySQL 的学生选课管理系统目标就是把“课程信息维护、学生选课、退课、成绩录入、统计查询”这几件高频事收敛到一个桌面程序里。它是 Java 课程设计里最经典的题目之一难点不在业务逻辑而在你把 Swing 界面事件、JDBC 事务、数据库表关系这三样东西缝在一起时是否足够稳。这套方案适合两类人一是准备做毕业设计或课程设计的学生需要一份能跑通、能答辩、有实训报告支撑的完整项目二是刚学完 Java SE想找一个“不依赖 Web 容器、双击就能跑”的桌面项目来锻炼 JDBC 和 GUI 编程的开发者。它的价值在于选课系统天然包含一对多、多对多的表关系你能在一百来行 SQL 里把外键约束、级联删除、事务回滚这些概念全部实战一遍比刷十道练习题都管用。我对这套组合的评价是技术栈虽然不算新但它稳定、可控、容易调试作为一个练手和交付物都合适的项目依然是当下课程设计里最稳妥的选择之一。2. 技术选型为什么是 Swing MySQL桌面端 JDBC 项目的经典组合2.1 Swing 和 JavaFX 的选择稳定优先于新潮很多人在做桌面选课系统时会纠结用 Swing 还是 JavaFX我的经验是除非你有明确的动画、图表或富媒体需求不然 Swing 依然是课程设计里更安全的选择。原因有三点第一Swing 是 JDK 自带的 GUI 工具包不需要额外引入运行时和构建插件放在普通 JDK 环境里就能编译运行第二Swing 的资料沉淀极多遇到 “JTable 刷新不生效”“JFrame 关闭后进程不退出”这类问题随便搜一下就有现成答案第三Swing 的组件模型足够完成选课系统里所有的需求包括表格展示课程列表、下拉框选择院系、文本域录入反馈意见。JavaFX 虽然界面更现代但它需要独立 SDK、可能在 IDEA 里需要额外配置 VM 参数这对一个以“快速跑通、顺利答辩”为目标的实训项目来说是多余的复杂度。你交的是“学生选课管理系统”不是“交互动效展示平台”Swing 的平庸恰恰是它的优势。我现在做这类桌面工具时一般先用 Swing 做原型如果后续确实需要更精致的界面再考虑迁移到 JavaFX 或者干脆改成 Web 版但那是后话了。2.2 MySQL 5.7 还是 8.0先看你手头的环境数据库方面这个项目用 MySQL 是标配。你需要先做一个决定用 MySQL 5.7 还是 8.0。我建议你在自己机器上装哪个方便用哪个但要注意驱动版本。如果你用 8.0JDBC 驱动类名是com.mysql.cj.jdbc.DriverURL 需要加serverTimezoneAsia/Shanghai否则连数据库时会报时区错误。如果你用 5.7驱动类名是com.mysql.jdbc.DriverURL 则不需要带时区参数但两者在代码里混用会导致 ClassNotFoundException 或者奇怪的通信异常。另一个常见坑是 MySQL 8.0 的加密规则。如果你在连接时遇到 “Public Key Retrieval is not allowed” 的错误在 JDBC URL 后追加allowPublicKeyRetrievaltrueuseSSLfalse就能解决。这个参数不写在驱动代码里而是写在连接字符串里。下面是一个兼容 MySQL 5.7 和 8.0 的 JDBC 配置模板我一般会写在db.properties文件里方便以后修改jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/course_select?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456这段配置里characterEncodingutf8是为了避免中文乱码serverTimezoneAsia/Shanghai只对 MySQL 8.0 有效useSSLfalse是因为本地开发不需要加密连接能省去证书配置的麻烦。注意allowPublicKeyRetrievaltrue这个参数如果你用的驱动是 8.0 且 MySQL 用户秘钥是 caching_sha2_password不加这个会在第一次连接时报错。2.3 IDEA 里的项目结构一个 Module 就够别搞多 Module这个项目我建议用 IDEA 新建一个普通的 Java 项目不要用 Maven也不要用 Gradle除非你后续打算把项目转成 Web 服务。原因还是在“可控”两个字上不引入构建工具你只需要把 mysql-connector.jar 导入到项目的 lib 目录然后在 Project Structure 里把它添加为 Library 即可。如果用 Maven虽然依赖管理方便但很多新手会在坐标版本、私服配置上浪费时间这些都不是选课系统本身的关注点。IDEA 里建议的项目结构是course-selection-system/ ├── src/ │ ├── com.course.ui/ # Swing 界面类 │ │ ├── LoginFrame.java │ │ ├── StudentFrame.java │ │ └── AdminFrame.java │ ├── com.course.dao/ # JDBC 数据访问层 │ │ ├── StudentDao.java │ │ ├── CourseDao.java │ │ └── SelectionDao.java │ ├── com.course.model/ # 实体类 │ │ ├── Student.java │ │ ├── Course.java │ │ └── Selection.java │ ├── com.course.util/ # 工具类 │ │ ├── DBUtil.java │ │ └── TableModelUtil.java │ └── com.course.MainApp.java ├── lib/ │ └── mysql-connector-java-8.0.30.jar └── sql/ └── init.sql这里我把MainApp.java放到com.course包下作为程序入口只负责调用LoginFrame。不要把 main 方法写在某个界面类里否则后续做单元测试和代码审查都会别扭。另外我建议把init.sql放在项目根目录下的sql文件夹中这样实训报告里可以直接引用建表语句答辩时也方便演示。3. 数据库设计与初始化从三张表到两个核心事务3.1 表结构设计学生表、课程表、选课表再多一张就是冗余选课系统的核心数据模型是三张表学生表student、课程表course、选课关系表selection。其中选课表是连接前两张表的多对多关系表它只存student_id和course_id两个外键以及选课时间、成绩这两个业务字段。为什么要单独拆出来而不是在学生表里加一个course_ids字段因为关系型数据库的设计原则是“一行只表达一个事实”你在学生表里塞一个逗号分隔的课程 ID 列表后续统计“每个课程选了多少人”的时候就只能靠字符串分割既慢又容易出错这种反模式在面试里也是典型的扣分项。下面是初始化 SQL我在课程设计项目里惯用的写法是这样。注意我刻意用了ENGINEInnoDB和CHARSETutf8mb4并打开了外键约束-- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学号主键, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号唯一索引, name VARCHAR(30) NOT NULL COMMENT 学生姓名, password VARCHAR(64) DEFAULT 123456 COMMENT 登录密码默认123456, major VARCHAR(50) COMMENT 专业, class_name VARCHAR(50) COMMENT 班级, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生表; -- 课程表 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 课程主键, course_no VARCHAR(20) NOT NULL UNIQUE COMMENT 课程编号, name VARCHAR(50) NOT NULL COMMENT 课程名称, teacher VARCHAR(30) COMMENT 授课教师, credit DECIMAL(3,1) DEFAULT 2.0 COMMENT 学分, max_students INT DEFAULT 50 COMMENT 选课人数上限, selected_count INT DEFAULT 0 COMMENT 已选人数可在应用层维护 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程表; -- 选课关系表 CREATE TABLE selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, score DECIMAL(5,2) COMMENT 成绩教务录入, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_sel_student FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, CONSTRAINT fk_sel_course FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选课关系表;这里说明几个设计决策第一student_no用UNIQUE而不是直接做主键是为了让主键id在后续关联时保持稳定学号即使被教务处调整也不影响已有选课关系第二course表里的selected_count字段是冗余设计主要是为了方便在课程列表界面直接展示已选人数不用每展示一行课程就COUNT(*)一次这个字段可以通过事务保证一致第三selection表上的UNIQUE KEY uk_student_course是防重复选课的关键应用层就算代码有并发问题数据库这一层也会拦住重复插入。3.2 初始化数据与事务演示把实训报告的案例测出来建表之后建议你顺手插入几条演示数据方便后面测试登录和选课功能。这个数据集不要太大一两百个学生、二三十门课足够。插入时注意学生密码我用的是123456真实环境里应该加密存储但作为课程设计演示明文密码在实训报告里更容易讲清楚认证流程。下面这段 SQL 用于插入 5 个学生和 5 门课程并且预置一条选课记录用来验证登录后的界面能正确显示“我已选的课程”INSERT INTO student (student_no, name, major, class_name) VALUES (2023001, 张三, 计算机科学与技术, 计科2301), (2023002, 李四, 软件工程, 软工2302), (2023003, 王五, 数据科学, 数据2301), (2023004, 赵六, 计算机科学与技术, 计科2302), (2023005, 钱七, 网络工程, 网工2301); INSERT INTO course (course_no, name, teacher, credit, max_students, selected_count) VALUES (C001, Java程序设计, 陈老师, 3.0, 50, 0), (C002, 数据库原理, 刘老师, 3.5, 40, 1), (C003, 操作系统, 李老师, 3.0, 45, 0), (C004, 计算机网络, 王老师, 2.5, 35, 0), (C005, 软件工程, 赵老师, 2.0, 60, 0); -- 预置一条选课记录张三选了数据库原理 INSERT INTO selection (student_id, course_id) VALUES (1, 2);注意这里选课记录插入后course表里C002的selected_count也应该同步更新为 1。这个同步动作在真实代码里应该在同一个事务里完成。SQL 文件里如果你手动跑这两条语句需要记得把selected_count一起UPDATE掉否则界面上的已选人数不准确很容易在演示的时候被老师挑出毛病。4. 用 JDBC 写一个永不串号的学生端选课逻辑从连接池到事务4.1 数据库连接工具类别把连接参数散落在各处写 Swing 界面时最容易出现的情况是每个按钮事件里都直接写DriverManager.getConnection(url, user, password)结果换一台电脑跑项目时要全局搜索替换数据库密码。这种做法在课程设计里不算致命但会让你的代码在实训报告里显得很业余。我建议你写一个DBUtil工具类把连接管理工作收口package com.course.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/course_select?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, java.sql.Statement stmt, java.sql.ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }这个工具类里Class.forName是在注册 JDBC 驱动MySQL 8.0 的驱动在 SPI 机制下可以省略这行但写上它能让驱动加载过程更明确排查问题时也少一个变量。close方法里我把ResultSet、Statement、Connection的关系理清楚关闭顺序必须从里到外否则你可能会在日志里看到连接泄漏。这里我没有引入连接池因为 Swing 桌面应用是单用户模式并发量极低每次操作新建连接、用完即关就够了。如果后续你把它改造成多用户并发访问再考虑换成 HikariCP但现在不要过度设计。4.2 核心选课事务先查容量再插入最后更新计数下面是选课功能里最关键的一段代码。我把它写在SelectionDao.selectCourse(int studentId, int courseId)方法里这个方法要承担两个职责一是检查课程是否已满、学生是否已选过二是完成插入选课记录并更新课程已选人数。整个过程必须在一个事务里执行否则在“检查容量”和“插入记录”之间如果其他线程也执行了同样操作就可能导致超选。package com.course.dao; import com.course.util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class SelectionDao { public boolean selectCourse(int studentId, int courseId) { String checkCourseSql SELECT max_students, selected_count FROM course WHERE id ? FOR UPDATE; String checkSelectedSql SELECT id FROM selection WHERE student_id ? AND course_id ?; String insertSql INSERT INTO selection (student_id, course_id) VALUES (?, ?); String updateCountSql UPDATE course SET selected_count selected_count 1 WHERE id ?; Connection conn null; PreparedStatement checkCourseStmt null; PreparedStatement checkSelectedStmt null; PreparedStatement insertStmt null; PreparedStatement updateCountStmt null; ResultSet rs null; try { conn DBUtil.getConnection(); // 开启事务关闭自动提交 conn.setAutoCommit(false); // 第一步锁定课程行防止并发超选 checkCourseStmt conn.prepareStatement(checkCourseSql); checkCourseStmt.setInt(1, courseId); rs checkCourseStmt.executeQuery(); if (!rs.next()) { conn.rollback(); return false; // 课程不存在 } int maxStudents rs.getInt(max_students); int selectedCount rs.getInt(selected_count); if (selectedCount maxStudents) { conn.rollback(); return false; // 课程已满 } // 第二步检查是否重复选课 checkSelectedStmt conn.prepareStatement(checkSelectedSql); checkSelectedStmt.setInt(1, studentId); checkSelectedStmt.setInt(2, courseId); rs checkSelectedStmt.executeQuery(); if (rs.next()) { conn.rollback(); return false; // 已经选过这门课 } // 第三步插入选课记录 insertStmt conn.prepareStatement(insertSql); insertStmt.setInt(1, studentId); insertStmt.setInt(2, courseId); insertStmt.executeUpdate(); // 第四步更新课程已选人数 updateCountStmt conn.prepareStatement(updateCountSql); updateCountStmt.setInt(1, courseId); updateCountStmt.executeUpdate(); // 全部成功提交事务 conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ignored) {} } e.printStackTrace(); return false; } finally { DBUtil.close(conn, insertStmt, rs); } } }这段代码的逻辑说明如下。第一步里FOR UPDATE是关键它会在事务期间锁住course表里那一行其他事务要更新同一行时必须等当前事务提交或回滚。第二步的重复检查能拦截大部分重复插入但由于应用层可能并发执行数据库层的外键约束和UNIQUE索引仍然是最后的兜底防线。事务的提交或回滚都放在try-catch里任何一个步骤失败都会把所有改动回滚避免出现“选课记录插入成功但课程已选人数没更新”的数据不一致。这里有个参数值得你注意setAutoCommit(false)必须在getConnection()之后、第一条 SQL 执行之前调用如果放在finally里再改回去就没有意义了。另外这个事务里rs变量在第一步和第二步之间重复使用我每次执行完查询后就继续复用同一个ResultSet其实这是有风险的因为同一个Statement上复用ResultSet会关闭前一个结果集。好在这里第一步的rs在第二步执行前已经用完了所以没有踩坑但更规范的写法是为每一步查询单独准备Statement和ResultSet。这是我在实际开发中踩过的坑所以特别提醒你。4.3 Swing 事件线程按钮点击之后代码跑在哪里Swing 是单线程模型所有界面更新必须在事件分发线程EDT上执行。这里有个常见的做法是在按钮的事件监听器里直接调用SelectionDao.selectCourse()然后根据返回值弹出JOptionPane提示框并刷新表格模型。这个做法对于课程设计来说没有问题因为本地数据库查询很快你不会感受到界面卡顿。但如果你把项目扩展到网络数据库或者数据量变大就需要在SwingWorker里执行 JDBC 操作避免 EDT 被数据库 IO 阻塞导致界面无响应。下面这段代码是选课按钮的典型实现我把刷新表格数据的逻辑一起写进去btnSelectCourse.addActionListener(e - { int selectedRow courseTable.getSelectedRow(); if (selectedRow 0) { JOptionPane.showMessageDialog(this, 请先在表格中选择一门课程); return; } // 从表格模型取课程 ID注意这里是 model 的索引不是 view 的索引 int courseId (int) courseTableModel.getValueAt(selectedRow, 0); boolean success new SelectionDao().selectCourse(currentStudentId, courseId); if (success) { JOptionPane.showMessageDialog(this, 选课成功); loadMyCourses(); // 刷新“我的课程”表格 loadAllCourses(); // 刷新全部课程表格更新已选人数 } else { JOptionPane.showMessageDialog(this, 选课失败课程已满或已选过该课程); } });这里有一个培训时经常被忽略的细节JTable的getSelectedRow()返回的是视图模型view的行索引而表格数据我们存放在DefaultTableModel中两者在表格经过排序或筛选后可能不一致。如果课程表格只是顺序展示没有排序功能那么 view 索引和 model 索引是一致的但如果你加了TableRowSorter排序就必须用table.convertRowIndexToModel(selectedRow)转换。我在做这个系统时加过排序功能结果选课选错了课程所以在这提醒你拿到选中行以后第一件事就是做索引转换否则容易给学生选错课。5. 避坑与排查实训报告里最容易翻车的 5 个细节5.1 中文乱码不是数据库的锅是连接参数没带对现象登录界面显示中文正常但课程列表里所有中文都变成“”。原因数据库表已经是utf8mb4但 JDBC 连接字符串里没有characterEncodingutf8导致客户端和服务器之间的字符集不一致。解决在db.properties或DBUtil里把 URL 改成jdbc:mysql://localhost:3306/course_select?useUnicodetruecharacterEncodingutf8同时确认 IDEA 里项目的文件编码是 UTF-8不要用 GBK。另外如果你发现INSERT语句传中文参数乱码检查PreparedStatement的参数是否在 setString 之前就经过了正确编码一般加连接参数后这个问题会一并消失。5.2 mysql-connector 版本与 MySQL 服务器版本不匹配现象程序能编译但运行到Class.forName(com.mysql.jdbc.Driver)时报 ClassNotFoundException或者连接时报Communications link failure。原因MySQL 8.0 的驱动类名改了你用 5.1.x 驱动连 8.0 服务器会失败。解决确认你的 lib 目录下 jar 包版本。如果是 8.x驱动类名是com.mysql.cj.jdbc.Driver并且 URL 里要带serverTimezone。我一般直接在 MySQL 官网下载mysql-connector-j-8.0.30.jar然后导入 IDEA 的 Project Structure Libraries。这个坑在实训报告里值得写一段因为它是 Java 连接数据库最典型的升级兼容问题。5.3 重复选课成功应用层检查被绕过现象连续快速点击“选课”按钮两次数据库里出现两条一样的选课记录。原因并发时两个线程都通过了checkSelectedSql的检查然后先后执行了插入。解决不要只依赖应用层判断数据库里的UNIQUE KEY uk_student_course (student_id, course_id)是最后防线。当插入第二条时MySQL 会抛出DuplicateKeyException你的catch (SQLException e)会捕获它并回滚。为了让实训报告更好看我建议你在这个 catch 里判断e.getErrorCode() 1062然后提示“您已选过这门课”而不是显示“选课失败”这种模糊文案。5.4 Swing 界面卡死JDBC 操作阻塞了事件分发线程现象点击“加载全部课程”按钮后窗口变成白屏光标转圈几秒钟后才恢复。原因MainApp直接调用 DAO 查询数据库执行时间较长时阻塞了 EDT。解决最简单的做法是在查询方法里加一个Thread.sleep(100)模拟慢查询然后改用SwingWorkerVoid, Void在后台线程执行查询在done()方法里更新表格模型。不过对于本地 MySQL 和几千条数据不这样做也感觉不出来。我的建议是实训报告里可以写这个方案但代码里不必过度设计只要答辩时能解释清楚 EDT 模型就行。5.5 关闭主窗口后 Java 进程不退出现象点窗口右上角关闭按钮界面消失了但 IDEA 里程序还在运行进度条一直是绿色。原因没有设置setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)默认是HIDE_ON_CLOSE窗口隐藏了但 JVM 还在跑。解决在JFrame构造函数里加this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);。这算是最基础的 Swing 常识但几乎每届都有学生在这上面踩坑因为光标点在窗口右上角时根本没意识到只是隐藏。也有同学用DISPOSE_ON_CLOSE配合System.exit(0)效果一样但EXIT_ON_CLOSE是最直接的。6. 把项目做成能答辩的样子从报表导出到体检清单项目到了收尾阶段除了选课、退课这些核心功能外教务管理员通常需要把选课名单导出来存档。一个实用的技巧是直接在 Swing 界面里用JFileChooser选择保存路径然后用FileWriter把ResultSet的内容写成 CSV 文件。这个功能不需要引入第三方库二十行代码就能完成而且容易在答辩时演示。我只保留一个建议——先写表头再写数据行用英文逗号分隔中文内容加双引号这样用 Excel 打开时不容易乱。下面是一个简易的导出实现private void exportSelectionToCsv() { JFileChooser chooser new JFileChooser(.); chooser.setDialogTitle(导出选课名单); int result chooser.showSaveDialog(this); if (result ! JFileChooser.APPROVE_OPTION) { return; } File file chooser.getSelectedFile(); if (!file.getName().endsWith(.csv)) { file new File(file.getAbsolutePath() .csv); } String sql SELECT s.student_no, s.name, s.major, c.name AS course_name, sc.select_time FROM selection sc JOIN student s ON sc.student_id s.id JOIN course c ON sc.course_id c.id ORDER BY c.id, s.student_no; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery(); BufferedWriter writer new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), UTF-8))) { writer.write(学号,姓名,专业,课程名称,选课时间\n); while (rs.next()) { writer.write(String.format(\%s\,\%s\,\%s\,\%s\,\%s\\n, rs.getString(student_no), rs.getString(name), rs.getString(major), rs.getString(course_name), rs.getString(select_time))); } JOptionPane.showMessageDialog(this, 导出成功 file.getAbsolutePath()); } catch (Exception ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, 导出失败 ex.getMessage()); } }这里用try-with-resources把连接、语句、结果集和文件流都收口是 Java 7 以后的推荐写法即使有异常也会自动关闭资源。输出到 CSV 文件时用了OutputStreamWriter指定 UTF-8 编码防止 Excel 打开乱码。注意String.format里的占位符顺序要和writer.write的拼接保持一致一般我在开发时如果导出的表格列很多会先在一张纸上画出表头顺序再写代码这样不容易出错。答辩演示时除了导出功能我还会在实训报告里附上一份“项目运行环境清单”包括 JDK 版本推荐 8 或 11、MySQL 版本5.7 或 8.0、IDEA 版本2019.3 及以上均可、mysql-connector-java-8.0.30.jar 以及数据库初始化脚本。这个清单看起来不起眼但评委老师最常问的就是“你换一台电脑能跑起来吗”你把这几个版本信息写清楚能省掉不少追问。另外我习惯给项目根目录配一个 README.txt里面写清楚三步启动流程先用 MySQL 执行sql/init.sql再在 IDEA 导入项目最后修改DBUtil.java里的数据库密码为本地密码。这样哪怕过了一个月再回来看这个项目也能快速接手。最后说一个我自己的教训我在早期做这类课程设计时总是忍不住往项目里堆功能比如加一个“公告板”模块加一个“课程评价”模块结果数据库表越来越多代码越来越乱最后连自己都理不清外键关系。后来我带学生做项目时都会先让他们把核心流程走顺——学生登录、选课、退课、管理员审核、统计报表一个功能只能有一个界面、一个 DAO 方法以及一段事务逻辑做完这些之后其余的可选功能都只是锦上添花。Selbst当老师问起“你的项目有什么亮点”时你不必说“界面特别好看”你可以说“我的选课事务能保证数据一致”这个说服力强得多。希望这些经验和代码片段能帮到你祝你的选课管理系统一次通过答辩。本文还有配套的精品资源点击获取