简介面向毕业设计的JavaSwingSqlServer学生成绩管理系统项目包定位为课程设计与毕设参考适合需要快速上手Java图形界面开发和SQL Server数据持久化的学习者。系统围绕班主任日常成绩管理场景覆盖学生成绩录入、统计计算、分析打印与可视化图表帮助老师直观掌握班级整体学情与个体差异。压缩包共176个文件包含31个Java源码、104个class编译文件、SqlServer的mdf/ldf数据库文件以及mp4讲解视频、gif界面演示图、ico图标和项目工程配置等整体大小183MB模块涵盖登录、成绩查询、系统设置等便于对照调试。讲解视频逐步演示环境搭建、数据库附加和代码运行流程可与源码、数据库文件配合进行完整复现与二次开发还可作为课程设计与毕设的完整解决方案。目前已有187人学习适合需要边看视频边动手实践的Java初学者以及正在准备毕业设计的学生参考借鉴。1. 学生成绩管理系统那个毕设题目背后的真实工程量如果你在开始准备毕业设计八成见过这么个题目基于 Java Swing SqlServer 的学生成绩管理系统。它之所以被反复选中不是因为做出来能改变世界而是这个组合覆盖了“界面怎么写、数据库怎么连、增删改查怎么做、权限怎么区分”这一整套最基础的软件工程动作老板看着熟悉学生做着不吃力答辩时又能讲出东西。它的核心是用 Swing 搭桌面窗口用 JDBC 与 SqlServer 交换数据做出学生信息维护、课程管理、成绩录入与查询这些功能。这篇文章会把一个能“跑起来并能交差”的系统拆给你看。从 JDK 版本怎么选到数据库表结构定义、连接串写法、沙雕错误排查以及答辩时怎么把“我没白做”讲出来。读者不管是完全没有头绪的新手还是想快速搭好骨架的老手按这个顺序走可以少熬几个通宵。顺便说一句这个项目不需要任何花哨框架不用 Spring、MyBatis也不涉及 Web 容器。它适合三种人准备做课程设计的本科生、想快速补 Java 基础语法的人、以及接手别人代码需要在答辩前把逻辑理顺的“临时救火员”。2. 环境与选型先把 JDK、SqlServer、IDE 三件套对齐2.1 Swing 技术的生态位与过时争议常见的认知误区是“Swing 是不是被淘汰了”。一线开发确实没人用它当主力框架做新产品但它在教学场景里反而是最合适的陪练材料。Swing 的组件体系成熟JFrame、JTable、JPanel 这些概念从几十年前延续到现在界面代码写起来直观一个 setVisible(true) 就能看到窗口弹出。更重要的是它没有 Web 项目里那层复杂的请求响应链路也不需要配置 Tomcat、Nginx。数据从数据库取出来填进一个 JTable 模型一个窗口应用就完成了。这就是毕设题目的用意在一个窗口的范围内把 Java 核心语法用明白包括事件监听、内部类、集合操作、异常处理比架空讲一百道语法题有用得多。选择 Swing 不代表必须完全手绘每个控件。实际操作里会大量使用 NetBeans 或 IDEA 自带的 GUI Designer 拖拽布局再把事件绑定代码补完整。这是常见做法也是新手能最快看到效果的方式。2.2 JDK 版本选择与 Java 环境配置隐患这个项目对 JDK 版本不敏感但为了兼容 Swing 代码和 SqlServer 驱动库建议直接用 JDK 8 或 JDK 11两者都对 Swing 支持完善。JDK 8 更稳定网上能查到的资料也最多哪怕复制代码报错都更容易搜到答案。配置环境变量时有一个常见的草率操作系统里装了多个 JDKJAVA_HOME 指向的版本和 PATH 里实际生效的版本不一致。检查方法是在命令行执行java -version看看到底调用的是哪个版本。以前遇到过 IDEA 里指定的 JDK 是 11命令行默认 JDK 是 8最后驱动包运行时报 UnsupportedClassVersionError查来查去浪费两小时。“class 文件版本号错误”这类问题可以看成是 JDK 版本没对齐的典型症状。踩坑的解决方式非常机械高版本编译的 class 在低版本 JRE 上跑必然报错要么把 JDK 版本统一要么把代码重新编译。2.3 SqlServer 版本与连接驱动匹配数据库侧选择更讲究兼容性。强调一下项目标题写的是 SqlServer但 SqlServer 的版本跨度从 2008、2012、2016 到 2019、2022行为差异很大连接驱动的加载方式也不一样。推荐优先使用 SqlServer 2012 或 2016 版本。太老如 2008 的安装包不好找太新如 2022 对硬件内存要求更高学生机上往往跑不动。另一个关键点不要在 2016 之后的新版本上用老旧的 jdbc 驱动这会引起握手协议失败或者其他玄学问题。驱动方面旧项目可能用com.microsoft.jdbc.sqlserver.SQLServerDriver新版本必须换成com.microsoft.sqlserver.jdbc.SQLServerDriverJar 包名是 mssql-jdbc-xxx.jar导入即可。数据库连接串的写法也有一个潜在陷阱。在老式写法里连接地址后面带上实例名而新版本默认实例名和端口差异会导致连接失败。一个可靠的习惯是使用端口直连方式jdbc:sqlserver://localhost:1433;DatabaseNameStudentDB这种写法不依赖实例名可以避免一堆不存在实例的错误提示。2.4 IDE 选择和本机快速自检清单Eclipse 和 IntelliJ IDEA 都比记事本有效。IDEA 社区版即可界面更现代化代码提示更强Eclipse 胜在内存占用低、老教程多。两者都能编译运行 Swing 项目不用太纠结选一个顺手能写完代码的就行不要在这上面消耗时间。动手写代码之前先把环境对齐省得后边浪费时间。比如命令行里执行java -version看 JDK 版本最好是 8 或 11用 IDE 建一个空的 Java 工程跑一个最简单的System.out.println(ok)确认输出正常再继续。SqlServer 服务管理器里确认 SQL Server 服务是“正在运行”状态不是“已停止”。未运行的话使用 SqlServer 配置管理器把它启动这一步经常被忽略人和代码都写好了却发现数据库根本没开。检查项期望值常见失败表现JDK 版本8 或 11编译错误、驱动类找不到SQL Server 服务运行中连接超时、端口拒绝连接模式混合认证登录失败 18456TCP/IP 协议已启用不到主机或实例驱动 Jar 包已导入工程ClassNotFoundException环境问题会占掉这个项目至少一半的失败率。按表格把每一项验一遍能减少大量无意义的代码报错排查。3. 数据库设计四张核心表与成绩表的唯一性陷阱3.1 表关系设计的通常做法学生成绩管理系统的实体不多核心就三个学生、课程、成绩外加一个管理员表做登录鉴权。实体关系在各方数据库里是一样的一个学生可选多门课程一门课程可被多个学生选成绩表作为中间表记录“某个学生选了某门课并且考了多少分”。设计表结构时最大的风险是学生表、课程表的数据约束过于宽松考完试的成绩单关联不起来数据一多就乱。实际用到的表如下第一张表是管理员表负责登录系统后台。字段设计只需要 id、用户名、密码就够了。密码绝不存储明文即便是毕设项目也要用摘要算法处理。可以引入 MD5 或 SHA-256 做哈希虽然后端开发里用加盐哈希更专业但毕设用SHA-256已经能让老师看到安全意识。使用一个固定盐值就能避免简单字典攻击盐值写死在代码里也无妨因为这只是项目。第二张是学生表需要存学号、姓名、性别、班级、入学年份、联系电话等。学号作为业务主键更符合校园场景。虽然内部用自增 id 做物理主键更标准化但在成绩表里关联学号比关联内部 id 直观得多。第三张是课程表包括课程编号、课程名称、学分、任课教师、上课时间。课程编号用字符串类型学期信息可以用一个字段比如 semester 来区分“2024-2025-1”方便按学期筛选成绩单。第四张表是成绩表也是整个系统的核心。字段有成绩编号、学号、课程编号、平时成绩、期末成绩、总评成绩、考试时间。总评成绩可以由平时和期末按比例计算也可以在录入后自动更新。考试时间和学期可以冗余简化查询。为什么说成绩表有唯一性陷阱因为一张成绩表里同一个学生同一门课只能有一条记录。如果你没有设置联合唯一约束UNIQUE(学号, 课程编号)一旦界面录入按钮被连点两次或者数据导入脚本重复执行就会出现两条一模一样的成绩记录。重复记录会让统计报表直接翻车平均分和排名都会被污染。这个约束需要写在 CREATE TABLE 语句里。3.2 建库建表脚本与初始化数据以下给出 SqlServer 的完整建库建表脚本。数据库名StudentDB文件路径按本机安装目录调整每次执行前确认目标实例里没有重名数据库。-- 创建数据库 CREATE DATABASE StudentDB; GO USE StudentDB; -- 管理员表 CREATE TABLE t_admin ( id INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存储 SHA-256 哈希值 real_name NVARCHAR(20) ); -- 学生表 CREATE TABLE t_student ( id INT IDENTITY(1,1) PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE, -- 学号业务主键 name NVARCHAR(50) NOT NULL, gender NVARCHAR(2) DEFAULT N男, class_name NVARCHAR(50), grade INT, -- 入学年份 phone VARCHAR(20) ); -- 课程表 CREATE TABLE t_course ( id INT IDENTITY(1,1) PRIMARY KEY, course_no VARCHAR(20) NOT NULL UNIQUE, course_name NVARCHAR(100) NOT NULL, credit DECIMAL(3,1), teacher NVARCHAR(20), semester NVARCHAR(50) -- 示例2024-2025-1 ); -- 成绩表 CREATE TABLE t_score ( id INT IDENTITY(1,1) PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, usual_score DECIMAL(5,2), -- 平时成绩 final_score DECIMAL(5,2), -- 期末成绩 total_score DECIMAL(5,2), -- 总评成绩 exam_date DATE, CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES t_student(id) ON DELETE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES t_course(id) ON DELETE CASCADE, CONSTRAINT uq_student_course UNIQUE (student_id, course_id) -- 关键唯一约束 );脚本说明IDENTITY(1,1)是 SqlServer 的自增语法从 1 开始每次加 1。成绩表没直接用学号或者课程编号作为外键而是用内部 id这样学生表或者课程表要改学号、课程编号时不需要连带更新成绩表。ON DELETE CASCADE表示学生被删除时对应成绩一并删除避免成绩表里出现孤儿数据。最后一行UNIQUE (student_id, course_id)就是前一节说的防重复机制有这一行界面代码出错重复提交时数据库会直接报错拦截。初始化数据里管理员密码需要预先把明文转成哈希。下面插入学生和课程数据其中班级、学期字段采用中文和学期表达式方便后面联表查询时看的直观。-- 初始化管理员密码 123456 对应的 SHA-256 摘要值以代码处理示例给出 INSERT INTO t_admin(username, password, real_name) VALUES (Nadmin, N此处填哈希值, N系统管理员); -- 学生示例 INSERT INTO t_student (student_no, name, gender, class_name, grade, phone) VALUES (20230001, N张伟, N男, N计算机2301, 2023, 13800000001), (20230002, N李娜, N女, N计算机2301, 2023, 13800000002), (20230003, N王强, N男, N软件2302, 2023, 13800000003); -- 课程示例 INSERT INTO t_course (course_no, course_name, credit, teacher, semester) VALUES (CS101, NJava程序设计, 4.0, N陈老师, N2024-2025-1), (CS102, N数据结构, 4.0, N刘老师, N2024-2025-1), (MA101, N高等数学, 5.5, N赵老师, N2024-2025-1);参数说明DECIMAL(5,2)可以存最大 999.99 的数值百分制的 0-100 完全够用。学号、课程编号用VARCHAR而不是INT是因为此类编号经常带前缀或补零一旦左补零按整数存储会自动丢了前缀。初始化数据中每条 INSERT 语句主干相同SqlServer 支持一次插入多行值逗号分隔方便快速生成测试数据。3.3 统计用的视图可以提前造好成绩管理系统里“排名”和“平均分”如果每次用 Java 代码去算会写不少重复的遍历逻辑。更好的做法是提前在数据库里建视图把“每门课程的平均分、及格率、最高分最低分”预计算好。CREATE VIEW v_course_stats AS SELECT c.course_name, c.course_no, COUNT(s.student_id) AS student_count, AVG(s.total_score) AS avg_score, MAX(s.total_score) AS max_score, MIN(s.total_score) AS min_score, SUM(CASE WHEN s.total_score 60 THEN 1 ELSE 0 END) * 1.0 / NULLIF(COUNT(s.student_id), 0) AS pass_rate FROM t_score s INNER JOIN t_course c ON s.course_id c.id GROUP BY c.course_no, c.course_name;视图逻辑CASE WHEN逐行判断是否及格再用SUM统计及格人数除以总人数得到及格率。* 1.0是为了把整数除法转成小数。NULLIF(count, 0)防止一门课程无人选课时除数为零。视图和 Java 代码的解耦价值在于界面层只需要SELECT * FROM v_course_stats一行调用业务统计列再变也只需改视图不用动窗口代码。4. 用 JDBC 把 Swing 窗口和 SqlServer 连接起来4.1 分层与连接工具类的搭建设计上不把数据库代码直接嵌在按钮事件里。常见做法是拆三层界面层只管布局和事件监听数据访问层DAO负责拼 SQL、执行、取结果实体层POJO承载行数据。对于这个体量的系统不用搬出各种高大上的框架三层已经足够。先建立实体类。以学生为例public class Student { private Integer id; private String studentNo; private String name; private String gender; private String className; private Integer grade; private String phone; public Student() {} public Student(String studentNo, String name, String gender, String className, Integer grade, String phone) { this.studentNo studentNo; this.name name; this.gender gender; this.className className; this.grade grade; this.phone phone; } // 每个字段的 getter/setter 省略IDE 自动生成 // 为了让 JTable 能取到字段getter 命名必须规范 }实体类不需要额外的注解Swing 的 JTable 取值依赖的方法名规范后面在表格渲染部分会用到。核心要求就是有对应的 getter字段名不重要方法名要按驼峰规则对齐。然后是连接工具类它是整个系统的通道。import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DbUtil { private static final String DRIVER com.microsoft.sqlserver.jdbc.SQLServerDriver; private static final String URL jdbc:sqlserver://localhost:1433;DatabaseNameStudentDB;characterEncodingUTF-8; private static final String USER sa; private static final String PASSWORD your_password; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) {} } } } }逻辑说明Class.forName的作用是让驱动类被类加载器加载这一步在新版 JDBC 4.0 之后并不是必须的但显式写上能查出“驱动 Jar 没导入”的问题。静态代码块保证类第一次被使用时驱动已经被加载。close方法接受可变参数把 Connection、Statement、ResultSet 三个难以收拾的资源全部传进去一次性关闭防止打开的资源泄漏。这条代码规范以后再接口子化连接池时也通用。4.2 登录校验PreparedStatement 与参数绑定的价值这个系统最容易被答辩老师提问的地方就是登录模块同时也是最容易暴露出 SQL 注入风险的部分。以下用反例说明为什么不能直接拼字符串// 反例SQL 注入风险 String sql SELECT * FROM t_admin WHERE username user AND password pass ;如果用户输入的用户名是 OR 11 --SQL 就变成了SELECT * FROM t_admin WHERE username OR 11 -- AND password...恒真条件绕过登录。这是最基础的注入攻击答辩老师只要查代码看一眼就知道是否踩了这个坑。改用PreparedStatement绑定参数public Admin login(String username, String passwordHash) { String sql SELECT id, username, real_name FROM t_admin WHERE username ? AND password ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, passwordHash); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Admin admin new Admin(); admin.setId(rs.getInt(id)); admin.setUsername(rs.getString(username)); admin.setRealName(rs.getString(real_name)); return admin; } } } catch (SQLException e) { e.printStackTrace(); } return null; }代码里的?占位符让 JDBC 驱动把输入值当作纯数据而非可执行片段这是防护手段第一步。使用 try-with-resources 语法Connection、PreparedStatement、ResultSet 全部自动关闭这是 JDK 7 之后非常实用的写法比手动在 finally 里层层关闭安全得多。这里有一段血泪经验SqlServer 的 JDBC 驱动默认在 setString 时会正确处理字符串转义不要自作聪明把单引号再包一层那反而会把正常密码搞错。4.3 Swing 表格中加载成绩数据从 ResultSet 到 TableModel登录只是前菜成绩查询界面才是系统要展示的内容。核心操作是把分数数据从数据库读出来放到 JTable 里展示。JTable 本身不直接接收 ResultSet需要填充到一个DefaultTableModel或自定义的 AbstractTableModel里。import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.sql.*; import java.util.Vector; public class ScoreDAO { public DefaultTableModel getScoreTableData(String filterClass) { // 列名 VectorString columns new Vector(); columns.add(学号); columns.add(姓名); columns.add(课程); columns.add(平时分); columns.add(期末分); columns.add(总评); DefaultTableModel model new DefaultTableModel(columns, 0); String sql SELECT s.student_no, st.name, c.course_name, sc.usual_score, sc.final_score, sc.total_score FROM t_score sc JOIN t_student st ON sc.student_id st.id JOIN t_course c ON sc.course_id c.id ; if (filterClass ! null !filterClass.trim().isEmpty()) { sql WHERE st.class_name ?; } try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { if (filterClass ! null !filterClass.trim().isEmpty()) { ps.setString(1, filterClass); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { VectorObject row new Vector(); row.add(rs.getString(student_no)); row.add(rs.getString(name)); row.add(rs.getString(course_name)); row.add(rs.getBigDecimal(usual_score)); row.add(rs.getBigDecimal(final_score)); row.add(rs.getBigDecimal(total_score)); model.addRow(row); } } } catch (SQLException e) { e.printStackTrace(); } return model; } }说明DefaultTableModel接收Vector构造这种写法在上古 Swing 代码里很常见能跑就行不需要纠结换成数组。表头在界面层决定数据在 DAO 层组装再通过返回值交还给界面。查询条件班级使用?占位符与登录模块同理防止筛选条件被注入。当需要修改表格里的成绩时有两种常见处理。一种是直接读取表格当前单元格的值再拼 UPDATE 语句另一种是在打开编辑窗口时把成绩 id 存到对应行模型中。通常是后者更稳妥表格里看不到 id就用一个隐藏的VectorObject和表格行号同步维护避免学生重名时改错记录。4.4 成绩录入与事务边界成绩录入界面是窗口里按钮最多的。点“保存”时要执行的操作可能包括校验分数是否为数字、判断分数范围、向 t_score 插入记录或更新已有记录。这里批次录入时容易忽略一个细节如果循环插入 50 条成绩第 30 条因为学号不存在而报错前 29 条已经写进数据库里了。解决办法是开启事务全部成功才提交任何一条失败就整体回滚。public boolean batchSaveScores(ListScoreItem items) { String sql INSERT INTO t_score (student_id, course_id, usual_score, final_score, total_score, exam_date) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DbUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 try (PreparedStatement ps conn.prepareStatement(sql)) { for (ScoreItem item : items) { ps.setInt(1, item.getStudentId()); ps.setInt(2, item.getCourseId()); ps.setBigDecimal(3, item.getUsualScore()); ps.setBigDecimal(4, item.getFinalScore()); ps.setBigDecimal(5, item.getTotalScore()); ps.setDate(6, item.getExamDate()); ps.addBatch(); } ps.executeBatch(); conn.commit(); // 全部成功提交 return true; } catch (SQLException e) { conn.rollback(); // 有任何异常全部回滚 e.printStackTrace(); return false; } } catch (SQLException e) { e.printStackTrace(); return false; } }事务的开启方式连接对象默认是自动提交autoCommit true每执行一条 SQL 就落地。改成false后所有语句在当前连接里变成“积压任务”只有显式 commit 才真正写入。中间任何一步报错rollback 后前面的插入全部撤销这就是“后悔药”。这段代码还需要理解addBatch的价值PreparedStatement 批量执行减少每次提交的网络往返在本地数据库上体感不明显但有 100 条数据时差异能看出来。执行executeBatch()时 SqlServer 驱动会逐条发送到服务端仍未脱离普通 JDBC 的范畴但已经可以避免循环内反复准备新语句的开销。事务边界是个老生常谈但常被轻视的点。毕设阶段的数据库学习最值得验收的能力就是这一条在何种场景下数据的一致性会被破坏如何用事务保住底线。4.5 UI 线程阻塞问题Swing 是单线程模型所有界面更新都要在事件分发线程EDT上执行。如果在按钮点击事件里直接执行数据库查询而数据量又极大界面会卡死表现为窗口拖动时像一块黑板。老经验是“感觉程序死了”其实只是阻塞了。正确做法是耗时操作放到后台线程中再通过SwingUtilities.invokeLater把结果送回界面。不过毕设数据量很小一般秒回问题不常见。但为了代码好看建议至少了解这个模型一旦答辩老师问“如果有一万条数据你的界面会怎样”你能说出应该在SwingWorker里查询就是加分回答。5. 避坑清单从连不上库到乱码的 8 个常见问题5.1 SqlServer 连不上TCP/IP 协议没启用现象程序运行时报Connection refused或The TCP/IP connection to the host localhost, port 1433 has failed但 SqlServer 服务已经启动了。原因SqlServer 安装后默认可能没有启用 TCP/IP 网络协议或者只监听命名管道。JDBC 走 TCP直接被拒。解决打开 SqlServer 配置管理器找到“SQL Server 网络配置”选择实例名对应的“协议”。把 TCP/IP 状态改为“已启用”右键“TCP/IP”进入属性在“IP 地址”选项卡里把”IPAll“的 TCP 端口设置为 1433。改完必须重启 SqlServer 服务否则不生效。这一条占了连接失败原因的半数以上。不用改任何代码和连接串服务起来后直接看到效果。5.2 登录失败 18456认证模式不对现象登录时报用户 sa 登录失败错误号 18456。原因SqlServer 默认只允许 Windows 身份认证而 JDBC 连接通常用数据库账号 sa。解决用 Windows 身份登录 SSMSSqlServer Management Studio右键实例选择“属性”“安全性”里选择“SQL Server 和 Windows 身份验证模式”。然后需要修改 sa 账号的密码并启用该账号账号状态设为启用。设置后重启 SqlServer 服务。经验教训是改完认证模式后如果只重启 SQL 服务而没重启实例配置可能没有真正生效最好重启整个服务节点。5.3 驱动类找不到ClassNotFoundException现象Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver)报ClassNotFoundException。原因mssql-jdbc 的 Jar 包没有放入 Java 工程的 classpath。IDEA 中只是导入了 jar但没有右键添加到模块依赖或在项目结构里没把它标记为 compile 依赖。解决IDEA 中右键 Jar 包选择 Add as LibraryEclipse 中右键工程 - Build Path - Configure Build Path - Add External Jars。这个属于新手第一周最容易遇到的错误和代码无关全是路径问题。5.4 中文乱码字符集设置不一致现象插入数据库的中文变成问号或者从数据库读出来乱码。原因SqlServer 的默认排序规则、JDBC 连接串的编码参数、Swing 界面编码三者不一致。解决建表时列类型使用NVARCHAR而不是VARCHAR因为前者按 Unicode 存储中文。连接串里再加characterEncodingUTF-8虽然 SqlServer 驱动对编码的处理和 MySQL 不同但写上问题更少Java 源文件保证用 UTF-8 保存。如果已经出现乱码不要反复改代码先检查插入时接收到的字符串是否已经是问号界面输入的正常只是显示有问题那就是表列类型不对。5.5 主键冲突或重复成绩现象同一学生同一课程重复录入时数据库报错Violation of UNIQUE KEY constraint或者统计里出现双倍人数。原因成绩表没有建联合唯一约束或者界面代码里执行了两次插入。解决先给表补约束。如果表里已经存在重复数据需要先把重复记录去重再加约束-- 查看重复记录 SELECT student_id, course_id, COUNT(*) FROM t_score GROUP BY student_id, course_id HAVING COUNT(*) 1;再删掉多余记录或调整具体保留规则最后执行ALTER TABLE t_score ADD CONSTRAINT uq_student_course UNIQUE (student_id, course_id);这个约束是数据层的最后一道防线界面代码防不住手滑靠它兜底。5.6 时间字段显示为时间戳现象成绩表里的 exam_date 在 JTable 中显示为2024-01-01 00:00:00.0格式不好看。原因ResultSet.getDate()拿到的是 java.sql.Date直接转成字符串时会附带时间部分。可用 SimpleDateFormat 或 DateTimeFormatter 自定义格式后再填入表格。更简单的办法是在 SQL 层就格式化CONVERT(VARCHAR(10), exam_date, 120) AS exam_date。5.7 删除学生失败外键在作怪现象删除一个学生时报错提示和被引用记录有关。原因如果 t_score 表的外键没有设置ON DELETE CASCADE那么删学生时会因成绩表里有他的记录而违反外键约束。解决前面推荐建表时直接给外键加ON DELETE CASCADE。如果是已有的表可以用 ALTER 语句重新添加外键或者应用里先删成绩再删学生。生产系统会用软删除加一个状态标记避免数据被删清无法审计毕设阶段物理删除是可以接受的但需要讲清楚取舍。5.8 数据库迁移后连接失败现象学生把项目拷到另一台电脑上代码没改连不上数据库。原因目标电脑的 SqlServer 实例名、端口、账号密码都和源电脑不一样。解决把数据库的连接参数提取到配置文件 properties 里这样换机器时只需要改文件不用重新编译。常见做法是写一个 db.propertiesjdbc.drivercom.microsoft.sqlserver.jdbc.SQLServerDriver jdbc.urljdbc:sqlserver://localhost:1433;DatabaseNameStudentDB jdbc.usernamesa jdbc.password123456代码里加载方式Properties props new Properties(); props.load(new FileInputStream(db.properties));。然后从 props 里取值拼成 URL。这样环境差异和代码逻辑彻底隔离也是以后工作后部署常规操作的一个简化版本。6. 从能交差到能讲四类进阶细节与答辩表达把系统跑通只是第一步答辩时老师看的是“你懂不懂”不是“你敲了多少行”。需要准备几个能体现你确实理解的细节。6.1 防止 SQL 注入是必备回答登录模块里用 PreparedStatement 参数绑定而不是字符串拼接这一条别看简单它直接关系到安全问题。如果用了拼接一定会被当场问住。建议在代码中保留一个注释说明为什么用占位符而不是直接拼接。在面试中这些基础扎实与否聊两句就能分辨出来。6.2 增加一个登录验证码系统加个简单的验证码图片能显著提升完成度。做法是用java.awt.image.BufferedImage在内存中绘制随机字符加入干扰噪点并把验证码文本暂存到 session 或全局变量。由于 Swing 没有 Servlet 的 session 机制常见做法是把验证码存到一个持有对象里登录按钮执行时对比。这个功能展示了你对安全流程的理解。需要思考的是验证码要是只有数字和字母的组合干扰线不要太复杂否则自己也识别不出来答辩演示时还会反被扣分。6.3 用 JFreeChart 显示成绩分布如果要找可视化增强点给课程成绩加一个柱状分布图90分以上、80-89、70-79、60-69、不及格五段用的是 JFreeChart 这个图表库。依赖包一共两个 jar导入后构造数据集和 ChartFactory 即可。UI 布局里放一个 ChartPanel往里面塞 JFreeChart 对象。这个加在统计分析界面里能直观地看出某门课程的教学效果答辩时也是很好的展示素材。6.4 做一次完整的自测用例不要到了答辩前一天才发现登录按钮没绑定事件。至少准备一组手测用例录入一个新学生给学生选一门课程并录入成绩按班级筛选成绩修改成绩数值删除有成绩记录的学生观察级联效果。每一条按“操作输入-期望结果-实际结果”核对。答辩老师喜欢听到你说“我测过哪些场景发现了哪些问题”这比直接甩源码有说服力得多。我的个人习惯是在项目文件夹里建一个 README.md把“如何配置环境、如何导入数据库脚本、如何运行、默认账号密码、测试数据说明”全部写清楚。拷贝给对方或者交给毕设存档时能省掉大量答疑也显得工程素养在线。这一步的花费时间不超过二十分钟却往往决定别人对“能不能用”的第一判断。这个方向虽然只是从 Swing 向 Web 时代过渡的一个脚印但里面每一项能力JDBC 连接管理、事务、表约束设计、界面事件处理都是后续做任何企业级 Java 开发绕不开的地基。你如果把这个做扎实后面学 Spring Boot、MyBatis 时就会觉得很多概念异常眼熟。希望这篇顺完整个环节的说明能帮你少走几段冤枉路。祝你顺利跑通希望帮到你。本文还有配套的精品资源点击获取