尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java Swing+MySQL学生选课成绩管理系统:从表设计到事务实现的完整课程设计指南

Java Swing+MySQL学生选课成绩管理系统:从表设计到事务实现的完整课程设计指南 简介这是一个基于 Java Swing MySQL 开发的学生选课与成绩管理系统课程设计项目适合初阶与进阶学习者作为毕业设计、课程作业或工程实训参考。系统覆盖登录注册、管理员/教师/学生三类角色包含学生信息管理、课程管理、选课与成绩查询等核心模块并配有数据库 E-R 图与系统结构图便于理解整体设计。压缩包共 53 个文件核心为 16 个 Java 源码文件、1 个 SQL 数据库脚本及 lib 下 MySQL 连接驱动 jarJava 源码按模块组织SQL 脚本可直接导入 MySQL 8.0jar 驱动免去额外下载依赖。另有 20 张界面截图与 2 张 PNG 结构图辅助对照包体 8.63MB整体轻量且目录清晰。项目在 JdbcConfig 中预留了数据库账号配置位置压缩包内还含 README 与初始账号admin/tch/stu导入 SQL 后即可运行体验。目前已有 219 人学习对希望快速上手桌面数据库应用开发、完成课程设计或梳理教务系统流程的读者有较强参考价值。1. 基于Java Swing MySQL的学生选课及成绩管理系统课程设计怎么做才能既保底又拿高分期末前两周选题页上“基于Java Swing MySQL的学生选课及成绩管理系统”永远是最抢手的那一个。原因很现实它不涉及框架、不依赖Tomcat一套JDK加一个MySQL就能跑起来难度曲线恰好卡在“认真学过Java基础的人都能啃下来”的位置。但这道题也是最容易做成“学长旧代码换皮”的题目——登录窗口改个标题课程表换几张图片答辩时连数据库连接串都说不清。这篇文章想给你一条能从头敲到尾的完整路径从成绩单反推数据库表结构用Swing把界面分层再用事务把选课这个核心动作做扎实。适合准备交课程设计、又不想光靠拷贝应付的读者。你最终交出去的不是一个能跑的demo而是一套能讲清楚每一步为什么这样做的作品。2. 从成绩单反推数据库设计四张表把选课和成绩的边界画清楚课程设计的第一个决策不是“登录窗口长什么样”而是“成绩到底存在哪张表里”。很多第一次做这个题的人会把成绩作为一个字段放在课程表里理由是“一门课有成绩”。这个直觉是错的成绩是某个学生对某门课的结果它既不只属于学生也不只属于课程而是属于“学生选了这门课”这个关系本身。想通这一点表结构就清楚了一半。选了这门课系统就落在一个经典的多对多场景上一个学生能选多门课一门课能被多个学生选。多对多关系在关系型数据库里的标准解法是引入中间表这个中间表就是选课表。学生表和课程表各管各的基础信息选课表负责记录“谁在什么时间选了哪门课”而成绩的三个分数——平时成绩、期末成绩、总评成绩——作为关系的属性挂在选课表的行上。这也是为什么这个系统的表结构永远长着一张“老面孔”很常规但挑不出毛病。2.1 四张表的结构与主外键学生、课程、选课、成绩建表语句我一般会直接给出完整版本因为课程设计的评审老师几乎必看数据库设计文档。下面这段SQL包含了学生表、课程表、选课表以及必要的索引和外键约束可以直接在MySQL里执行CREATE DATABASE IF NOT EXISTS course_system DEFAULT CHARACTER SET utf8mb4; USE course_system; CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号同时作为登录名, password VARCHAR(64) NOT NULL COMMENT 登录密码, name VARCHAR(50) NOT NULL COMMENT 姓名, class_name VARCHAR(50) DEFAULT COMMENT 班级, role VARCHAR(10) NOT NULL DEFAULT student COMMENT student或admin ) ENGINEInnoDB; CREATE TABLE course ( id INT AUTO_INCREMENT PRIMARY KEY, course_no VARCHAR(20) NOT NULL UNIQUE COMMENT 课程号, name VARCHAR(50) NOT NULL COMMENT 课程名, teacher VARCHAR(50) DEFAULT COMMENT 任课教师, credit DECIMAL(3,1) DEFAULT 0 COMMENT 学分, capacity INT NOT NULL DEFAULT 60 COMMENT 课程容量, selected INT NOT NULL DEFAULT 0 COMMENT 已选人数, semester VARCHAR(20) NOT NULL COMMENT 开课学期如2024-2025-1 ) ENGINEInnoDB; CREATE TABLE student_course ( student_id INT NOT NULL, course_id INT NOT NULL, regular_score DECIMAL(5,1) DEFAULT NULL COMMENT 平时成绩, exam_score DECIMAL(5,1) DEFAULT NULL COMMENT 期末成绩, final_score DECIMAL(5,1) DEFAULT NULL COMMENT 总评成绩, PRIMARY KEY (student_id, course_id), KEY idx_course (course_id), CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE ) ENGINEInnoDB;选课表用了联合主键student_id, course_id这是整个设计里最关键的一个约束——数据库层面直接禁止同一个学生插入同一门课的第二条记录。没有这个联合主键防重复选课就只能依赖Java代码里的“先查再插”一旦两个窗口同时操作重复数据就进去了。外键都指向表的主键id而不是学号、课程号原因是id是稳定且唯一的代理主键学号这类业务字段理论上可能被修改用id做关联在后续维护时更安全。查询时再通过JOIN把学号、课程名拼出来代价很小。2.2 主键用自增还是业务字段选课表联合主键的分量主键设计是另一个答辩必问的点。学生表的学号字段已经加了UNIQUE约束为什么还要单独放一个自增id课程表同样有唯一的课程号为什么不用它做主键我见过不少同学直接用student_no、course_no做跨表关联表面上看省了一次JOIN但有两个隐患。第一课程号的格式如果因为教务规则调整发生变化所有引用它的外键都要跟着改第二字符串主键在InnoDB的聚簇索引里比整型占用更大数据多时索引效率和写入性能都不如自增整型。课程设计的规模体现不出性能差异但“用代理主键而不是业务主键”这个设计习惯是加分项。选课表的联合主键则恰好相反——它不引入额外的自增id因为“同一个学生不能重复选同一门课”本身就是一条业务规则而联合主键正好就是这条规则的数据库实现。插入时如果违反主键约束JDBC会抛SQLIntegrityConstraintViolationException程序捕获后可以立刻提示“你已经选过这门课了”。注意这里说的是“捕获异常做提示”而不是一开始就完全依赖Java预检查——两道防线都留着容错才完整。至于成绩表这个系统里不需要单独建表选课表上的三个可为空的分数字段已经足够承载一学期一门的成绩。2.3 MySQL连接串与JDBC参数DBUtil里三个必调的参数表建好了接下来是Java和MySQL的通道。课程设计里最常见的数据库连接方式是JDBC工具类DBUtil是连接逻辑的唯一入口所有DAO都从这里拿Connection。下面是完整代码import java.sql.*; public class DBUtil { // MySQL 8.0 的驱动类名; 如果你是 MySQL 5.7, 改成 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/course_system ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }这里有两个参数值得多说一句。第一个是serverTimezoneAsia/ShanghaiMySQL 8的驱动会在连接时校验时区不配的话经常直接报异常第二个是useSSLfalse本地开发不需要加密连接加了它反而会在控制台刷一段SSL握手警告。characterEncodingutf8要和数据库的utf8mb4字符集配合少了这一项中文写入读取都可能乱码。静态块里的Class.forName在类加载时执行一次驱动注册只需要发生一次这个写法也比每次连接都查驱动更规范。密码硬编码在常量里对课程设计可以接受但你要在答辩时主动提一句“生产环境应该放到配置文件并加密”老师对你的印象会好很多。3. Swing界面分层登录、主窗体、选课面板怎么配合事件模型数据库准备就绪后界面的工程量开始浮出水面。Swing这套GUI框架看起来很老但它自带的组件和布局管理器足够完成一个管理系统而且JDK直接包含javax.swing包不需要额外引依赖这对课程设计的部署非常友好。有的同学会纠结“为什么不用JavaFX”答案很现实你所在的环境大概率装的是JDK 8学校机房和老笔记本上跑JavaFX要额外配JavaFX SDK而Swing是开箱即用的。换成JavaFX确实界面更现代但单是“让评审老师机器上能跑起来”这一条Swing就赢了大半。Swing编程的核心是事件模型用户在组件上动作系统派发事件给已注册的监听器监听器里的代码做出反应。写这个系统不需要把所有事件都背下来抓住ActionListener、ListSelectionListener两个就够。界面的结构也相对固定登录窗口验证身份主窗体承载所有业务功能通过左侧导航切换右侧面板。下面按这三层拆开讲。3.1 登录窗口到主窗口的跳转new JFrame 还是 JDialog登录窗口的实现思路是窗体上放一个用户名输入框、一个密码框和一个登录按钮点击按钮后调用数据库查询验证。验证成功把当前登录的学生id或管理员的role字段取出来传给主窗口验证失败用JOptionPane.showMessageDialog提示“用户名或密码错误”。这里有个设计选择登录窗口用JFrame还是JDialog都行关键是跳转方式——不要在一个监听器里连续new两个窗口然后让它们并列弹出来。我常用的跳转写法是JButton loginBtn new JButton(登录); loginBtn.addActionListener(e - { String no usernameField.getText().trim(); String pwd new String(passwordField.getPassword()); // 调用 StudentDAO.login(no, pwd), 返回 Student 对象或 null Student student dao.login(no, pwd); if (student ! null) { dispose(); // 关闭登录窗口 new MainFrame(student).setVisible(true); // 打开主界面 } else { JOptionPane.showMessageDialog(LoginFrame.this, 用户名或密码错误); } });注意dispose而不是setVisible(false)前者会释放窗口资源后者只是把窗口藏起来。如果只藏不关程序退出时如果只关了主窗口登录窗口还挂在后台JVM不会正常结束。这个问题在课程设计答辩中经常被当场演示出来关掉主界面后Java进程还在任务管理器里躺着。密码框用getPassword()返回char数组而不是getText()虽然在本项目里意义不大但这是一个Java开发者应该养成的习惯答辩时提一句也是加分项。3.2 主窗体布局JSplitPane加JTable搭出管理系统的标准形态主窗体是整个系统的容器。课程设计的主流做法是左边一个功能导航列表右边一个数据表中间用JSplitPane分割。左侧的JList放“可选课程”“我的选课”“成绩查询”三个入口右侧的JTable负责展示数据。下面是骨架代码你可以直接抄进MainFrame里再改字段public class MainFrame extends JFrame { private final Student student; private final DefaultTableModel model; private final JTable table; public MainFrame(Student student) { this.student student; setTitle(学生选课及成绩管理 - student.getName()); setSize(900, 550); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); String[] columns {课程号, 课程名, 教师, 学分, 已选/容量}; model new DefaultTableModel(columns, 0); table new JTable(model); JListString nav new JList(new String[]{可选课程, 我的选课, 成绩查询}); nav.addListSelectionListener(e - { if (e.getValueIsAdjusting()) return; // 避免事件触发两次 String selected nav.getSelectedValue(); switch (selected) { case 可选课程 - loadAvailableCourses(); case 我的选课 - loadMyCourses(); case 成绩查询 - loadScores(); } }); JSplitPane split new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(nav), new JScrollPane(table)); split.setDividerLocation(180); add(split); } private void loadAvailableCourses() { model.setRowCount(0); String sql SELECT course_no, name, teacher, credit, CONCAT(selected, /, capacity) FROM course; // 执行查询后逐行 model.addRow(new Object[]{...}) } }DefaultTableModel的构造方法第一个参数是列名数组第二个参数是初始行数0这样表格创建出来就只有表头没有数据。JList的监听器有一个容易踩的细节选中项变化时事件会派发两次第一次值还在调整中第二次才是最终值。上面代码里用getValueIsAdjusting()做了过滤不清这层的话导航栏每点一次查询方法会被调用两次。SwingWorker在课程设计里不是必须的但用不用都行核心是把事件处理器和数据库读写分开别让界面刷新逻辑和SQL混在同一个方法里揉成一团。3.3 JTable刷新为什么显示旧数据TableModel和视图的坑JTable的显示机制是Model-View-Controller的简化版JTable是视图TableModel是数据源视图不会主动去感知数据源的变化必须由你通知它“数据变了”。不少同学第一次做时踩的坑是——执行完SQL后直接调用table.repaint()以为重画就能刷新结果界面上还是旧数据。repaint只负责重绘它不重新读取TableModel的内容。正确的做法是让TableModel自我更新然后触发一个表格结构或内容变更事件。用DefaultTableModel时最简单的刷新方式是先model.setRowCount(0)清空所有行再逐行addRow()填新数据。setRowCount(0)这个操作本身会触发表数据变更JTable自动重绘不需要额外调repaint。如果你的数据需要在原行上修改比如成绩录入后某个单元格变了记得调用model.fireTableRowsUpdated(row, row)来通知视图。数据库连接和查询在EDT上直接执行对于这个数据量完全够用不用过度设计。但要警惕一件事如果查询方法里忘了关闭ResultSet连接和数据会一直占着界面也会因为连接池耗尽慢慢变卡所以每段查询代码都走DBUtil.close()收尾。4. 选课与成绩的核心业务事务、防重复、防超员这个系统里真正有技术含量的是写操作。查询只是把数据库里的行搬到JTable上难度不大选课和成绩录入涉及多步写操作稍微设计不当就会产生脏数据。以选课为例一次成功选课最少要干三件事读课程容量做判断、往选课表插一条记录、把课程表的已选人数加一。这三步里任何一步失败而其他步成功数据就处于不一致状态——最常见的表现形式是“选课表多了一条记录但已选人数没涨”或者反过来“人数涨了但选课表里查不到记录”。4.1 选课三步为什么要放在同一个事务里回滚是后悔药上面的三步必须被放进同一个数据库事务要么全部成功一起提交要么全部失败一起回滚。java.sql.Connection默认是自动提交模式每条SQL执行完就写盘必须手动关闭自动提交才能控制事务边界。下面是选课方法的完整实现也是这个系统里我最希望你读懂的一段代码public boolean selectCourse(int studentId, int courseId) { String preCheckSql SELECT selected, capacity FROM course WHERE id ?; String insertSql INSERT INTO student_course(student_id, course_id) VALUES (?, ?); String updateSql UPDATE course SET selected selected 1 WHERE id ? AND selected capacity; try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 // 第1步: 预检查, 纯粹是为了给用户友好提示 try (PreparedStatement ps conn.prepareStatement(preCheckSql)) { ps.setInt(1, courseId); ResultSet rs ps.executeQuery(); if (rs.next() rs.getInt(selected) rs.getInt(capacity)) { conn.rollback(); return false; // 课程已满 } } // 第2步: 插入选课记录 try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, studentId); ps.setInt(2, courseId); ps.executeUpdate(); } catch (SQLIntegrityConstraintViolationException e) { conn.rollback(); return false; // 联合主键挡住了重复选课 } // 第3步: 原子更新已选人数 try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, courseId); if (ps.executeUpdate() 0) { conn.rollback(); return false; // 更新行数为0, 说明此刻课程刚好满了 } } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); return false; } }这段代码有三个边界值得说透。第一第1步的SELECT结果只能用于提示不能作为最终判断因为在SELECT执行完到UPDATE执行完之间可能有其他请求插入把名额抢走真正起防超员作用的是第3步UPDATE语句里那个WHERE selected capacity条件——数据库是在当前行上加锁执行更新的它才是原子判断。第二第2步插入如果触发联合主键冲突会抛SQLIntegrityConstraintViolationException这里先rollback再返回false脏写不会有落地机会。第三第3步的executeUpdate返回值是影响行数如果为0说明条件不满足这一个分支就把“并发抢最后一个名额”的情况兜住了。项目答辩时能把这段逻辑讲清楚比界面多一个按钮有用得多。4.2 防止重复选课联合主键兜底和Java预检查各司其职防重复选课的思路和防超员类似数据库约束负责最终正确性Java预检查负责友好的用户提示。如果完全依赖联合主键用户重复选课时会看到一个白底红字的异常堆栈体验很差如果完全依赖Java预检查即先SELECT再INSERT就会留下并发窗口。所以两层都保留事务里先查一次是不是已选查到就直接返回“你已经选过这门课”查不到再走插入流程插入时如果主键冲突再兜底返回。这样既不会让异常暴露给用户又不会因为预检查的竞态而放行重复数据。4.3 成绩录入与统计一条UPDATE加一条聚合查询成绩管理的核心是更新选课表上的三个分数。录入页面拿到学生id、课程id和三个分数后执行下面的UPDATEUPDATE student_course SET regular_score ?, exam_score ?, final_score ? WHERE student_id ? AND course_id ?;总评成绩怎么算我建议在Java代码里算好再写入final_score而不是在SQL里写表达式。原因很朴素课程设计的评分规则可能随时改老师如果说“平时四期末六”你只需改一行Java代码如果把公式写死在SQL里后期改动要动数据库层维护成本更高。成绩查询的SQL是一段带JOIN的聚合查询这也是课程设计里少有的能展示你SQL基本功的地方SELECT c.course_no, c.name, sc.regular_score, sc.exam_score, sc.final_score FROM student_course sc JOIN course c ON sc.course_id c.id WHERE sc.student_id ?;按课程统计平均分、最高分、最低分时再加GROUP BY c.id。这里注意一个细节查询成绩时要确保只显示当前登录学生的数据WHERE条件里的student_id从MainFrame持有的Student对象获取不能从界面上的下拉框选否则只要改一下下拉框就能查到别人的成绩这是权限设计上的硬伤。4.4 为什么全程用PreparedStatement安全性和可读性都要整个系统的SQL执行我都推荐PreparedStatement而不是Statement加字符串拼接。直接拼接SQL存在注入风险比如把用户输入的用户名直接拼到查询语句里输入一个abc OR 11就能绕过登录。PreparedStatement用占位符先把SQL结构固定下来参数只作为值传进去不存在拼接机会。它还有一层编译缓存的好处同一条SQL重复执行时MySQL可以复用预编译的执行计划比每次重新解析快一些。课程设计的数据量测不出性能差异但代码规范性和安全性是评审老师愿意看到的亮点。5. 避坑本机跑通到答辩演示之间最容易翻车的五个细节代码写完能跑和答辩现场能顺利跑完是两个完全不同的阶段。我在带课程设计这类的项目时见过太多学生前一刻在宿舍演示正常后一刻换到教室投影仪上就翻车。下面五条是我认为出现频率最高的疑难杂症按现象、原因、解决三步写清楚希望能帮你少走一段弯路。5.1 MySQL 8驱动加载失败ClassNotFoundException到底缺在哪现象是程序一启动就报ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因基本出在两处一是mysql-connector-java的jar包没放进classpath很多情况下你在IDEA里引入了依赖但忘了把它打到最终的Artifact里换一台机器跑就找不到驱动二是你用的MySQL 5.7的驱动字符串即com.mysql.jdbc.Driver而驱动版本是8.x新版驱动已经移除了这个老类名。解决办法很简单下载与MySQL版本对应的mysql-connector-java放在项目根目录的lib文件夹下IDEA里右键Add as Library。如果是构建Artifact还要在Project Structure里把lib目录下的jar勾选进输出。这件破事我自己当年就栽过代码逻辑全对问题纯粹出在打包遗漏上。5.2 查询结果中文全是问号字符集三处必须对齐现象是JTable里课程名显示成“????”。原因基本都出在字符集不对齐数据库建库时不是utf8mb4连接串里没有characterEncodingutf8或者表结构用的latin1默认字符集。解决办法是把三处统一建库SQL里写DEFAULT CHARACTER SET utf8mb4连接串带上useUnicodetruecharacterEncodingutf8检查现有表的字符集可以用SHOW CREATE TABLE student看看是不是utf8mb4。如果表已经建了但字符集不对ALTER TABLE course CONVERT TO CHARACTER SET utf8mb4可以抢救。注意MySQL 5.7默认字符集是latin1MySQL 8默认才是utf8mb4如果你照着网上老教程建库这一步很容易漏。5.3 Swing界面假死别把重活放在事件分发线程里现象是点击“查询成绩”按钮后整个窗口拖不动标题栏出现“未响应”过几秒又恢复正常。原因是Swing的所有事件处理都运行在事件分发线程EDT上你的数据查询如果耗时较长EDT就被占住它没空处理重绘和鼠标事件界面看起来就像死了一样。课程设计的数据量小最直接的解决方法是先自查SQL有没有问题——很多“卡顿”其实是没写WHERE条件导致全表扫描或者查询里的JOIN把笛卡尔积放大了。真遇到必须长时间执行的任务可以选用javax.swing.Timer或者把任务拆小分片执行SwingWorker在你的场景下不是必须。至少别在主线程里加Thread.sleep模拟网络延迟那是给自己找麻烦。5.4 选课超员Java层的容量判断不是最终防线现象是界面上明明显示“还剩5个名额”两个同学同时选课最后选上的人超过5个。原因在于第4章提到过的竞态先SELECT再UPDATE之间存在时间窗口两个请求都能读到“剩1个名额”然后相继插入成功。解决的核心是让数据库在一步里完成原子判断UPDATE course SET selected selected 1 WHERE id ? AND selected capacity由WHERE条件决定这行数据是否可以被更新。更新影响行数是0说明最后一个名额恰好被抢走程序回滚即可。Java层的预检查保留但它只负责显示“当前剩余名额”这种提示性信息不能当作安全边界。这是整个系统里我认为最“值钱”的一个细节答辩被问“高并发下怎么保证不超员”时这段能直接答到点上。5.5 答辩现场MySQL连不上服务和端口两手查现象是java.net.ConnectException: Connection refused: connect。原因优先排查两件事MySQL服务没启动或者端口被占用。管理员的CLI里执行net start mysql可以启动服务如果提示服务名无效说明安装时服务名不叫mysql要去服务列表里查实际名字。端口排查用netstat -ano | findstr 3306看LISTENING状态对应的进程。如果你用Docker安装MySQL还要额外确认容器端口映射有没有写-p 3306:3306以及容器是否在运行。这类环境问题最怕在答辩现场临时处理我的习惯是答辩前一天把数据库用mysqldump导出成.sql文件在另一台干净机器上从零导入一遍创建表、插入测试数据、跑通登录和选课。能成功走完这套流程答辩环境基本就稳了。6. 进阶把课程设计从“能跑”推到“值得讲”课程设计答辩看的不只是功能对不对还有你对自己作品的熟悉程度和设计思考。这里给你一份可以直接用的测试用例表对照着逐条跑完既能暴露潜在bug也算提前准备答辩话术。用例编号测试操作预期结果是否通过TC-01学生登录成功后进入主界面主窗体标题显示学生姓名左侧导航可用TC-02选择一门名额充足的课程选课成功课程列表的已选人数加1TC-03重复选择同一门课弹出提示“已经选过该课程”事务回滚TC-04选最后剩余1个名额的课选课成功再选时提示已满TC-05录入成绩后立即查询成绩更新平均分统计同步变化TC-06关闭主窗口整个Java进程退出无残留窗口除了测试还有两个低成本高收益的进阶改动。第一student表已有role字段你可以让管理员角色除了查看之外还能进入“成绩录入”面板学生角色则只能查看成绩按角色隐藏按钮这个改动半小时内能完成但能让系统从“单机工具”变成“有个权限概念的系统”。第二把数据库密码从常量提出来放到根目录的config.properties里读取这只是几行代码的事但传递的工程意识很好。这两个改动不建议一起做大选一个做到位比堆十个半成品功能更划算。我自己做过一次非常尴尬的答辩演示选课成功后切到“我的选课”看新记录表格没刷新我下意识点了好几次“查询”界面还是旧数据最后重启程序才解决。后来我养成了一个习惯——所有表格写入完成后都要有明确的刷新动作而不是依赖数据库“自动”告诉界面数据变了。另一条教训是把选课的更新人数单独写在另一个类的另一个连接里出问题后排错花了两个小时从此我再没把同一事务的SQL拆到不同连接里执行。这些细节看起来小但恰恰是课程设计答辩时老师最愿意追问的地方。希望帮到你。本文还有配套的精品资源点击获取
返回列表