拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java+Swing+MySQL学生信息管理系统:环境配置与JDBC实战

Java+Swing+MySQL学生信息管理系统:环境配置与JDBC实战

简介:面向Java基础阶段学习者的控制台学生信息管理系统,基于Java SE + MySQL实现,单管理员模式,支持登录验证、学生信息的增删改查,可在Eclipse/MyEclipse中直接运行,常用于课程设计参考。资源为RAR压缩包,共33个文件,约37.67MB,包含完整Java源码与编译后的class文件、MySQL建表SQL脚本、两个版本MySQL连接驱动jar包,以及运行截图、技术说明文档和操作演示视频,方便从环境配置到功能运行全流程对照。当前已有66人学习下载。借助源码可掌握控制台与数据库交互的经典编程结构,配合mp4演示和文档说明,能快速理解各模块职责,减少课程设计中常见的中文乱码、驱动连接等踩坑问题,是Java入门项目练手与课设实现的实用资料。

1. 基于 java + 控件台 + mysql 的学生信息管理系统:为什么大多数课设都卡在环境上

先说一个反直觉的结论:大部分照着这个标题做学生信息管理系统的人,翻车点不在增删改查的代码逻辑,而在「java 能连上 mysql」这一步。你写好了窗口、摆好了控件、设计好了数据表,结果一跑起来报ClassNotFoundException或者Communications link failure,这时候你根本不知道是驱动没引进去、MySQL 服务没启动,还是 JDBC 连接串写错了。这类项目说白了就是一个「JDBC + Swing 控件台 + MySQL」的标准三方组合:java 负责逻辑和控件交互,mysql 负责落库,中间用 JDBC 搭桥。它的价值在于用最少的依赖把一条完整的数据链路走通——从窗口输入到数据库落盘,再从库回显到表格,适合做课设、毕设起步,或者第一次想把「会写 java 语法」升级成「能做出一个能用的系统」。

做这个项目的核心不是炫技,而是把环境基线踩平、把 JDBC 的参数吃透、把表格数据和ResultSet的映射理顺。下面按我自己做这类项目的顺序来讲:先铺环境,再连库,再写业务,然后单独把踩过的坑列出来,最后补一些能让系统拿得出手的细节。

2. 把环境基线铺平:JDK、MySQL 与驱动 jar 的选型和安装

2.1 JDK 与 MySQL 版本怎么选才不折腾

这个系统对 java 版本不敏感,JDK 8 和 JDK 11 都行,但我不建议一上来就装最新的 JDK 21 或 25。原因是很多教学环境、后续可能用到的Navicat连接工具、以及旧版mysql-connector驱动对太新的 JDK 反而会报一些奇怪的module访问限制。JDK 8 是国内课程设计的绝对主流,教材里的javax.swing控件写法、Class.forName加载驱动的老写法,在 JDK 8 下都是原样可用,遇到问题也最好搜到答案。

MySQL 的选择则要看你的使用场景。如果只是本地自己跑,MySQL 5.7 最省心:认证插件默认是mysql_native_password,和几乎所有版本的驱动、可视化工具都兼容。MySQL 8.0 也完全能做,但你必须处理两个新东西:默认认证插件caching_sha2_password、以及 JDBC 连接串里必须要带的时区参数serverTimezone。很多人的SSL 连接错误和Public Key Retrieval is not allowed基本都是 MySQL 8.0 引起的,5.7 上很少见。

驱动 jar 的选择遵循一个简单原则:如果你用 MySQL 5.7,用mysql-connector-java5.1.x 系列最稳;如果你用 MySQL 8.0,就用mysql-connector-j8.0.x 系列。不要在 5.7 的库上硬上 8.x 驱动,虽然 5.1.x 驱动也能连 8.0,但需要额外给 root 改认证插件,属于给自己挖坑。

注意:JDK 安装路径不要带空格和中文,C:\Program Files\Java这种路径在部分老版本 JDK 下会引发脚本解析问题。我一般直接装在D:\JDK8这类无空格路径下。

2.2 用 Navicat 先建库,别让代码背建库的锅

很多新手喜欢在 java 代码里用CREATE DATABASE IF NOT EXISTS建库,我一般不建议这么做。学生信息管理系统本身只有几张表,完全没有必要在业务代码里做 DDL(数据定义语言)操作。更靠谱的顺序是:先用 Navicat 或命令行把库和表建好,再用代码去连。

在 Navicat 里新建数据库student_db,字符集选择utf8mb4,排序规则选utf8mb4_general_ci。utf8mb4能存 emoji 和生僻字,比utf8更省心。然后执行下面的建表 SQL:

CREATE DATABASE IF NOT EXISTS student_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE student_db; CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '学号,自增主键', student_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号,业务唯一键', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT DEFAULT 1 COMMENT '性别:1 男,0 女', age INT DEFAULT 0 COMMENT '年龄', class_name VARCHAR(50) DEFAULT '' COMMENT '班级', phone VARCHAR(20) DEFAULT '' COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表'; CREATE TABLE admin_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT '密码的 MD5 值' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; INSERT INTO admin_user (username, password) VALUES ('admin', '21232f297a57a5a743894a0e4a801fc3');

上面这段 SQL 里有两个关键的建模细节。student_no设了UNIQUE,这个约束能在应用层代码漏判重复时兜底,防止同一个学号插入两次;password字段存的是MD5('admin')的结果,不是明文。注意,这个系统只是课程设计级别,用 MD5 做演示可以理解,但你要知道生产环境密码存储必须用 BCrypt 这类带盐的慢哈希算法,这也是面试容易被追问的地方:为什么不能明文存密码。

建好表后,用 Navicat 新建一个连接测试一下:主机127.0.0.1、端口3306、用户名root、密码填你安装时设的密码。如果 Navicat 能连上,说明 MySQL 服务、账号、端口都没问题,接下来代码里连不上,就只剩驱动和连接串的问题了。

2.3 导入驱动 jar 的正确姿势:不是下载了就行

这个项目里,驱动 jar 的引入是新手第一个翻车点。如果你用 IDEA 且项目是普通 Java 工程(非 Maven),正确做法是把mysql-connector-java-xxx.jar复制到项目根目录下的lib文件夹,然后右键点击 jar 选择Add as Library。如果少了最后一步,代码里Class.forName("com.mysql.cj.jdbc.Driver")编译能通过,但一运行就抛ClassNotFoundException,这就是典型的「代码对、依赖没挂上」。

如果你用 Maven 工程,则在pom.xml里加:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>

注意版本号:5.1.49 对应 MySQL 5.7;如果是 MySQL 8.0,groupId 是com.mysql,artifactId 是mysql-connector-j,版本用8.0.33。两个时代的驱动类名也不一样:5.x 用com.mysql.jdbc.Driver,8.x 必须用com.mysql.cj.jdbc.Driver,写错就报ClassNotFoundException。这一点很多视频里没讲清楚,等你对着旧代码抄的时候最容易栽在这。

3. JDBC 连接与登录模块:先跑通 ConnectionManager 再谈界面

3.1 写一个不花哨但稳定的连接工具类

界面上的控件还没必要先写,我一般先写一个DBUtil或ConnectionManager类,把连接逻辑固定住,后面所有业务代码都通过它拿Connection。这个类不需要任何框架,用 JDBC 原生写法就够。

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String DRIVER = "com.mysql.jdbc.Driver"; // MySQL 5.7 驱动类 private static final String URL = "jdbc:mysql://127.0.0.1:3306/student_db" + "?useUnicode=true&characterEncoding=utf8&useSSL=false"; private static final String USER = "root"; private static final String 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(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把驱动加载进 JVM,这一步保证后续DriverManager能识别jdbc:mysql://这种协议;getConnection每次调用都建立一条物理连接;close负责按ResultSet → Statement → Connection的顺序反向释放资源。很多人会把close顺序写反,先关Connection再关ResultSet,虽然 JDBC 规范里关闭Connection会自动释放关联资源,但代码规范上必须先关从属对象。

连接串里的参数逐个说。useSSL=false是关闭 SSL 加密通道,本地开发环境没有配置证书,不关会报SSL connection error;characterEncoding=utf8保证中文从窗口到数据库不乱码;useUnicode=true是characterEncoding的前置开关,JDBC 规范里要配套写。如果你用的是 MySQL 8.0,URL 里还要额外追加&serverTimezone=Asia/Shanghai,否则驱动会拿默认时区去解析DATETIME,导致时间字段偏移 8 小时或者直接报错。

3.2 登录校验:别用 Statement 拼接,小心 SQL 注入

登录功能是这个系统的门面,后台逻辑就是查admin_user表。但这里有个最常见的教学级错误:直接用Statement拼字符串查询。比如:

String sql = "SELECT * FROM admin_user WHERE username='" + username + "' AND password='" + password + "'";

这种写法在课设答辩时被老师问「SQL 注入怎么防」会很难看,更重要的是它确实不安全。正确做法是PreparedStatement预编译加占位符:

public boolean login(String username, String password) { String md5Pwd = MD5Util.md5(password); String sql = "SELECT id FROM admin_user WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Pwd); try (ResultSet rs = ps.executeQuery()) { return rs.next(); } } catch (SQLException e) { e.printStackTrace(); return false; } }

PreparedStatement的两个好处:一是参数无论输入什么,都只被当作字符串字面量,不会参与 SQL 语法解析,从根上杜绝注入;二是同一条 SQL 多次执行时,数据库端有预编译缓存,性能更好。setString的索引从 1 开始,很多人习惯从 0 开始写,会直接报IndexOutOfBoundsException,这也是新手的经典报错。

另外注意密码的 MD5 转换放在 java 层做,而不是把明文传进 SQL 里让MD5()函数处理。原因很简单:SQL 里写MD5(password)的话,password 是拼接进去的,又回到注入的老路上了。自己写一个MD5Util,用MessageDigest.getInstance("MD5")做摘要,再转成 16 进制字符串。这段逻辑不复杂,但建议独立成工具类,因为在后面的「新增管理员」功能里还会用到。

3.3 从登录窗口跳转到主界面

登录成功后的跳转逻辑,我习惯写成:先dispose()当前窗口,再new MainFrame().setVisible(true)。这里有个细节容易被忽略——dispose()只释放当前窗口资源,JVM 不会退出,所以主界面要设置默认关闭方式为EXIT_ON_CLOSE,否则关掉主窗口后 java 进程还赖在后台,表现为「关了窗口但控制台一直挂着」。

登录失败时的提示不要只在控制台打印,要在控件上给用户反馈。用JOptionPane.showMessageDialog(this, "用户名或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE)。这句代码同时干了三件事:弹窗提示、标题栏写清错误类型、图标区分错误级别。答辩时老师看的是你对异常路径的处理,登录成功路径谁都会写,失败路径才是拉开差距的地方。

4. 学生信息增删改查:PreparedStatement 与 Swing 表格的数据映射

4.1 新增学生:参数化插入与唯一键冲突处理

新增学生是整个系统最核心的业务动作。界面上是JTextField控件收集输入,提交时调用 DAO 层把数据写进student表。这里有一个控件台项目最常见的架构误区:把 JDBC 代码直接写进窗口类的按钮监听器里。一个按钮回调里同时出现getConnection、PreparedStatement、JOptionPane和界面刷新逻辑,短期看能跑,但后续加「修改」「删除」「查询」时,同一个类会膨胀到难以维护。

我通常的做法是独立一个StudentDAO类,界面只负责收集数据并调用dao.insert(student),DAO 返回布尔值表示是否成功。

public boolean insert(Student student) { String sql = "INSERT INTO student (student_no, name, gender, age, class_name, phone) " + "VALUES (?, ?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, student.getStudentNo()); ps.setString(2, student.getName()); ps.setInt(3, student.getGender()); ps.setInt(4, student.getAge()); ps.setString(5, student.getClassName()); ps.setString(6, student.getPhone()); return ps.executeUpdate() > 0; } catch (SQLIntegrityConstraintViolationException e) { System.err.println("学号重复: " + student.getStudentNo()); return false; } catch (SQLException e) { e.printStackTrace(); return false; } }

这里要特别说明executeUpdate()的返回值含义:它返回受影响的记录行数,插入一条数据成功应该是 1,所以> 0判断没问题。SQLIntegrityConstraintViolationException是 JDBC 规范里的标准异常,当student_no触发了数据库的UNIQUE约束时抛出。你可能会问:为什么不在插入前先查一次学号是否存在?我的建议是两者都做:应用层先查一次是为了给出友好的「学号已存在」提示,数据库约束是兜底,防止并发场景下两条 insert 同时通过预检查。课设阶段做一次预检查就够,但你要在代码注释里写明这一层考虑,答辩时都是加分项。

界面上性别通常用JRadioButton控件,取值时要判断isSelected(),而不是拿getText()去匹配「男/女」,因为按钮的文本可能被修改,但getActionCommand()对应的值不该变。我习惯给两个单选按钮分别设置setActionCommand("1")和setActionCommand("0"),提交时通过ButtonGroup获取选中的 actionCommand 转成 int。这个套路在后续的「按性别筛选」功能里也能复用。

4.2 修改与删除:主键定位行,不要用学号

修改和删除的核心不是 SQL 本身,而是「用哪一列定位记录」。很多人的第一反应是用student_no学号做WHERE条件,因为业务上它是唯一的。但在实际的表设计里,主键是id(自增 int),它的职责就是定位物理行;student_no虽然也有唯一约束,但它本质是业务标识。当你要做「把学号从 20240101 改成 20240102」时,用student_no做WHERE就尴尬了——你还没改,条件就已经失效了。

public boolean update(Student student) { String sql = "UPDATE student SET student_no=?, name=?, gender=?, age=?, class_name=?, phone=? WHERE id=?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, student.getStudentNo()); ps.setString(2, student.getName()); ps.setInt(3, student.getGender()); ps.setInt(4, student.getAge()); ps.setString(5, student.getClassName()); ps.setString(6, student.getPhone()); ps.setInt(7, student.getId()); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }

注意WHERE id=?这个条件的顺序:占位符索引 7 对应的id参数排在最后,和 SQL 里?的出现顺序严格一致。这个顺序错位是PreparedStatement最常见的 bug——SQL 能编译但运行时数据写错列,排查起来特别费劲。推荐每次写完先打印 SQL 和参数组合确认,再提交。

删除功能我只贴核心代码,逻辑和 update 对称:

public boolean deleteById(int id) { String sql = "DELETE FROM student WHERE id = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, id); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }

删除有个交互层的坑:用户可能没选中任何一行就点了删除按钮。界面上要通过JTable.getSelectedRow()判断返回值是否为 -1,-1 表示没有任何行被选中。如果不做这个判断直接取model.getValueAt(selectedRow, 0)拼 id,会抛出ArrayIndexOutOfBoundsException,这就是典型的「界面层没做前置校验,锅却甩给 DAO」的翻车现场。

4.3 查询与结果回显:把 ResultSet 转成 JTable 能认识的数据

查询分两种:无条件加载全部、按关键字模糊查询。数据量不大时全量加载无所谓,但代码里要刻意写成「分页查询」的形态,这也是常见的答辩追问点。

public List<Student> queryByKeyword(String keyword, int page, int pageSize) { List<Student> list = new ArrayList<>(); StringBuilder sql = new StringBuilder("SELECT id, student_no, name, gender, age, class_name, phone FROM student"); if (keyword != null && !keyword.trim().isEmpty()) { sql.append(" WHERE name LIKE ? OR student_no LIKE ? OR class_name LIKE ?"); } sql.append(" ORDER BY id DESC LIMIT ? OFFSET ?"); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql.toString())) { int idx = 1; if (keyword != null && !keyword.trim().isEmpty()) { String like = "%" + keyword.trim() + "%"; ps.setString(idx++, like); ps.setString(idx++, like); ps.setString(idx++, like); } ps.setInt(idx++, pageSize); ps.setInt(idx, (page - 1) * pageSize); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Student stu = new Student(); stu.setId(rs.getInt("id")); stu.setStudentNo(rs.getString("student_no")); stu.setName(rs.getString("name")); stu.setGender(rs.getInt("gender")); stu.setAge(rs.getInt("age")); stu.setClassName(rs.getString("class_name")); stu.setPhone(rs.getString("phone")); list.add(stu); } } } catch (SQLException e) { e.printStackTrace(); } return list; }

这段代码里有三个关键决策。第一,LIKE的匹配串%keyword%放在 java 层拼接好,而不是直接写LIKE '%?%',因为?是占位符,不能放进字符串字面量里。第二,LIMIT ? OFFSET ?这两个参数也是占位符,但要注意它们的类型:pageSize是每页条数,OFFSET是跳过的行数,等于(page - 1) * pageSize,这是分页的标准公式。第三,从ResultSet取列值时用列名而不是列索引,例如rs.getString("student_no")。虽然列索引性能略好,但列名可读性强,SQL 里列的排列顺序一变不至于全盘炸掉。

把List<Student>渲染到JTable上的标准写法是DefaultTableModel。注意每次刷新数据时要先setRowCount(0)清空旧数据再逐行addRow,否则上一次查询结果会残留在表格里,越点查询数据越多。

DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); for (Student stu : list) { model.addRow(new Object[]{ stu.getId(), stu.getStudentNo(), stu.getName(), stu.getGender() == 1 ? "男" : "女", stu.getAge(), stu.getClassName(), stu.getPhone() }); }

这里还有一个小细节:gender字段在数据库里是tinyint,控件台表格里直接显示 1/0 不友好,用三元表达式转成「男/女」。这种转换放在界面层而不是 SQL 层,是为了保持 DAO 层返回的数据和表结构一致,界面只做展示适配。

5. 学生信息管理系统的 4 个高频踩坑点:从 SSL 报错到中文乱码

5.1 连接层的三个经典报错:SSL、时区与驱动类名

第一个高频报错是Establishing SSL connection without server's verification。现象:控制台打出大段红色警告,连接最后其实成功了但心里没底。原因:MySQL 服务端默认开了 SSL 支持,而 JDBC 驱动默认也要协商 SSL,本地开发没有证书就会告警。解决:URL 里显式加useSSL=false,明确告诉驱动不需要加密通道。注意这个告警在 MySQL 5.7 上是警告不阻断,在个别版本上会直接升级成SSL connection error,所以别赌它不报错,直接加上更省事。

第二个报错是The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。现象:看到一堆乱码时区名,然后连接直接失败。原因:MySQL 8.0 的驱动对时区敏感,而系统默认时区和驱动解析机制对不上。解决:在连接串尾部追加serverTimezone=Asia/Shanghai,如果还不行,把时区改成GMT%2B8(即 GMT+8 的 URL 编码形态)。记住一个优先级:serverTimezone=Asia/Shanghai是最稳妥的写法,GMT+8在有特殊字符环境下反而容易出解析问题。

第三个报错是ClassNotFoundException: com.mysql.cj.jdbc.Driver。现象:代码完全按教程抄的,编译也过了,一运行就找不到类。原因:你的 MySQL 是 8.0,但教程里的驱动类是 5.x 的com.mysql.jdbc.Driver;或者反过来,你项目里引的是 5.x 驱动却写了 8.x 的类名。解决:先确认mysql-connector的 jar 版本,再写对应的Class.forName。我用过最笨但最有效的方法:解压 jar 进去直接看META-INF/services/java.sql.Driver文件里写的是哪个类名,用那个类名就不会错。

5.2 数据层的四个翻车现场:服务不启动、端口占用、权限拦截与中文乱码

第一个现场是Communications link failure。现象:报错信息里带着Connection refused,看起来像是代码问题。原因:MySQL 服务没启动,或者端口被改过。解决:先用netstat -ano | findstr 3306看端口有没有进程监听,没监听就去 Windows 服务里手动启动MySQL服务;有监听但连不上,大概率是my.ini里改了port,把连接串里的 3306 改掉。这一步很多人绕了远路去检查代码,其实最快的排查路径是「先确认服务,再确认端口,再确认账号密码」。

第二个现场是Access denied for user 'root'@'localhost'。现象:用户名密码确定没错,但就是被拒。原因:root 的密码强度策略或者认证插件和你的客户端不匹配,尤其是 MySQL 8.0 的caching_sha2_password在旧版 Navicat 或旧版驱动下会握手失败。解决:Navicat 升级到 16 以上;驱动升到 8.x;如果只是本地学习不想折腾认证插件,可以执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';把 root 切回老认证方式。这不是生产环境的推荐做法,但课设阶段能救命。

第三个现场是Unknown column 'xxx' in 'field list'。现象:SQL 看起来没问题,但跑起来说列不存在。原因:你有两个数据库,代码连的库和你在 Navicat 里建表的库不是同一个。解决:在DBUtil的 URL 里把数据库名打印出来核对一次;另外在 SQL 工作台执行SELECT DATABASE();确认当前连接选中的是哪个库。这类问题最坑的地方在于它是「半正确」的——服务能连、驱动能跑、账号密码都对,就是库名错了。

第四个现场是插入中文后显示???。现象:Navicat 里看数据全是问号,但代码没报错。原因:连接串里没有characterEncoding=utf8,或者建表时字符集不是utf8mb4。解决:建表统一用utf8mb4,连接串统一带useUnicode=true&characterEncoding=utf8,双保险。还要检查代码文件本身的编码:IDEA 右下角把文件编码从 GBK 切到 UTF-8,否则源码里的中文字符串字面量在编译时就已经乱了,这属于「字符集层层传递」里最容易忽略的一环。

提示:以上四个现场是按出现频率排的,其中「服务不启动」和「中文问号」最让人心态崩溃,因为它们往往不是代码 bug,而是环境状态问题。遇到先深呼吸,然后按「服务 → 端口 → 库名 → 字符集 → 驱动类名」五步排查法走一遍,90% 的问题十分钟内能定位。

5.3 控件台界面层最容易拖垮进度的两个问题

第一个是JTable的表头不刷新。现象:第一次加载有表头,执行一次查询后表头消失了。原因:你重新setModel的时候,把带表头的DefaultTableModel换成了不带表头的新模型,或者直接对JTable的 model 做了强转但模型类型不匹配。解决:不要在查询方法里新建DefaultTableModel后再setModel,而是getModel拿到原有模型后操作它的数据。表头是JTable的getTableHeader()管理的,一旦模型被替换,表头里的列名全部丢失。

第二个是窗口尺寸和控件布局超出可视区。现象:在小屏幕笔记本上跑,按钮和输入框被截断,看不到「新增」「保存」按钮。解决:主窗口setSize(1000, 600)起步,JFrame默认布局是BorderLayout,中间放JScrollPane包住的表格,底部放JPanel包住按钮;不要用绝对定位setLayout(null)。绝对定位在课设里很常见,但它换来的是「换台电脑就乱套」的糟糕体验,答辩演示时如果窗口被截断,印象分会大打折扣。

6. 把系统做成能拿得出手的样子:批量导入、表格刷新与演示录像的验证顺序

6.1 用批处理把批量录入从「慢动作」变成「秒级」

如果演示时需要一次录入几十条学生数据,千万不要用 for 循环逐条调用dao.insert()。每次insert都是一次完整的网络往返加事务提交,几十条数据会明显卡顿。更优的做法是用 JDBC 的批处理:先addBatch()攒着,再一次executeBatch()交出去。

public boolean batchInsert(List<Student> students) { String sql = "INSERT INTO student (student_no, name, gender, age, class_name, phone) VALUES (?, ?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (Student s : students) { ps.setString(1, s.getStudentNo()); ps.setString(2, s.getName()); ps.setInt(3, s.getGender()); ps.setInt(4, s.getAge()); ps.setString(5, s.getClassName()); ps.setString(6, s.getPhone()); ps.addBatch(); } int[] results = ps.executeBatch(); return results.length == students.size(); } catch (SQLException e) { e.printStackTrace(); return false; } }

executeBatch()返回的int[]数组,每个元素对应一条 SQL 的影响行数。如果有一条失败,默认行为是中断并抛出BatchUpdateException,前面的成功数据不会自动回滚。所以在真实场景里,批处理必须配合事务手动控制:conn.setAutoCommit(false)、执行批处理、确认全部成功后再conn.commit(),任一条失败就conn.rollback()。课设阶段可以不写完整事务,但注释里要写清楚这个缺口——面试问「批处理失败怎么保证一致性」时,你能答出「需要外层事务包裹」就说明真懂。

6.2 演示视频的录制顺序:按老师最容易追问的路径走

标题里写了「含演示视频」,这个视频的录制顺序直接决定答辩观感。我建议按五步走:第一步演示登录,故意输错一次密码再输对,证明异常处理存在;第二步演示新增学生,中文、数字、边界年龄都输一遍,然后到数据库里查这条记录,证明真的落库了;第三步演示查询,用关键字模糊搜刚才录入的名字;第四步演示修改与删除,改完刷新表格看变化;第五步重启系统,再次登录并查询,证明数据持久化成功。这个顺序的隐藏逻辑是「每一步的输入都能在后一步看到结果」,形成闭环。

视频录制时注意避开两个细节:一是窗口不要最大化后再录,保持默认尺寸避免变形;二是输入密码时不会被录屏软件记录明文,但你要提前在代码里确认密码字段用的是JPasswordField而不是JTextField,否则视频里密码明文会直接展示出来,答辩现场也容易被老师当场指出来。

6.3 验收自测清单:能跑通不等于能交差

最后给自己留一个十分钟的自测清单,比反复「点着玩」更有效:第一条,关掉 MySQL 服务再启动系统,看是否报错且报错信息是否友好(至少不能是英文堆栈直接甩到控制台);第二条,连续点击「保存」按钮两次,看学号重复拦截是否生效;第三条,把系统窗口缩到最小再还原,看控件布局是否错乱;第四条,在查询框输入单引号'或百分号%,看是否报 SQL 异常——这也是 SQL 注入压力测试的最低版本;第五条,用Navicat手动删掉一条数据,回系统点刷新,看界面的空数据状态是否能用。

这个项目的技术深度不算高,但它是少数几个能让你把「面向对象」「JDBC 规范」「数据库约束」「异常处理」四条线完整串起来的练习。我当年做课设时图省事,把密码明文放在代码里,连接串里连useSSL都不写,运行起来一堆黄字也懒得管,结果答辩演示时换了台电脑直接连不上数据库,当场翻车。后来我把这套系统的连接参数和自测清单沉淀成了固定模板,换任何机器都能在十分钟内把它跑起来。希望这篇笔记能帮你少走这一步弯路,也希望你做完后能把它跑通在「不熟悉的机器」上——那才是真的做完了。

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

返回列表