ARTICLE DETAIL

资讯详情

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

Java+Swing+MySQL学生信息管理系统:从建库到打包全流程实战

Java+Swing+MySQL学生信息管理系统:从建库到打包全流程实战 简介这是一份基于JavaSwingMySQL构建的学生信息管理系统完整项目适合Java初学者、课程设计使用者以及需要快速搭建桌面管理应用的学习者。系统采用三层架构涵盖数据录入、查询、修改、删除与展示等核心功能并包含登录验证与权限控制模块可帮助读者理解面向对象编程、Swing界面设计与数据库交互的完整流程。压缩包共58个文件以Java源码、编译后的class文件为主附带SQL建表脚本、项目配置文件和依赖jar包整体大小约2.01MB结构清晰便于导入IDE运行和二次开发。已有5132人浏览学习适合对照源码练习数据库增删改查操作也可作为毕业设计或课设的参考资料。1. 学生信息管理系统为什么要用 JavaSwingMySQL 这套组合每到期末和毕设季JavaSwingMySQL 实现学生信息管理系统会出现在大量课程设计题目里。这个项目看起来很传统却是最稳妥的练手方向Swing 负责桌面界面MySQL 存数据JDBC 做桥梁三样东西都是 Java 后端开发绕不开的基础。相比 Web 项目它不需要配置 Tomcat不需要处理前端跨域逻辑链路短调通一条数据流的成本低很多。这篇文章把整个系统的拆解过程完整讲一遍从 MySQL 建库建表到 JDBC 数据访问层再到 Swing 登录界面和 JTable 增删改查最后落在验证打包和一串真实踩坑记录上。新手能照着逐层跑通已经写过类似系统的读者重点看第 5 章的边界问题和第 6 章的打包验证这两部分是课设答辩最容易丢分的地方。2. 技术选型和环境准备先把地基打稳再动代码2.1 为什么不是 JavaFX也不是 MyBatisSwing 到今天看起来是有些过时但它有一个无法替代的优势JDK 自带不需要额外引入 UI 依赖写完就能跑。JavaFX 虽然界面更现代配置 Scene Builder、处理 FXML 对新手来说是不小的负担放在课程设计里反而拖慢进度。做这个需求Swing 是最低成本的路径。数据访问层上常见做法是直接用 JDBC 手写 DAO不引入 MyBatis。原因很实在这个系统的表就两三张SQL 语句也就那几条引入 MyBatis 反而要处理 XML 映射、MyBatis 配置文件这些额外的东西。但有一件事必须做——用 PreparedStatement不要用 Statement 拼 SQL。这不仅是为了防止 SQL 注入更是为了避免姓名里带单引号时直接报语法错误。这个点很多系统都会忽略后面避坑章节专门展开。2.2 JDK 版本和 MySQL 版本怎么配才不会翻车很多人在动手前卡在环境上其实就两个版本关键点JDK 建议用 8 或 11对应javax.swing包原生存在不需要额外装MySQL 用 5.7 或 8.0 都行但 JDBC 驱动必须和数据库版本对应。这是最容易翻车的第一个坑——MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 必须用com.mysql.cj.jdbc.Driver两者驱动的类名是不同的。启动 MySQL 前先确认服务已经跑起来。Windows 下在命令行执行net start mysql mysql -u root -p能进入 mysql 命令行界面就说明服务正常。如果执行 mysql 命令提示找不到多半是没配环境变量把 MySQL 安装目录下的 bin 路径加进 PATH 即可。之后记得确认一下 root 密码和端口默认 3306如果你本机装过多个版本的 MySQL端口可能被改成 3307 或 3308这个值后面要写进 JDBC 连接串写错会报 connection refused。2.3 建立数据库和 student 表字段设计决定后期改代码的工作量建库建表是整个系统里最值得先做的一步。我的建议是直接用命令行或 MySQL Workbench 执行 SQL把表结构和几条测试数据先准备好再开始写 Java 代码这样后面调 JDBC 时能立刻确认是代码问题还是数据问题。CREATE DATABASE IF NOT EXISTS student_info DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE student_info; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL, name VARCHAR(50) NOT NULL, gender VARCHAR(4) DEFAULT 男, age INT, major VARCHAR(50), phone VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_stu_no (stu_no) ); CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL ); INSERT INTO admin (username, password) VALUES (admin, 123456); INSERT INTO student (stu_no, name, gender, age, major, phone) VALUES (2023001, 张伟, 男, 20, 计算机科学与技术, 13800138000), (2023002, 李娜, 女, 21, 软件工程, 13900139000);字符集用utf8mb4而不是utf8因为utf8在 MySQL 里最多存 3 字节遇到生僻字或 emoji 会报错虽然学生表里大概率用不到但建表时顺手写对没有坏处。stu_no加了唯一索引这是防止同一学号被重复录入的数据库级防线比只在 Java 代码里判断要可靠。admin表用来做登录验证课程设计阶段密码不加密可以接受但最好在代码注释里标注这里是明文存储仅作演示用。数据库里所有 SQL 语句执行成功后用SHOW TABLES;和SELECT * FROM student;确认数据在这一步过了后面 Java 代码的调试就会顺畅很多。3. JDBC 数据访问层写一个能直接用的 DBUtil 和 DAO3.1 连接工具类连接串里的每个参数都是经验值DBUtil 是整个系统的地基。很多人的代码在别人电脑上跑不通问题就出在连接串的参数上。我一般这样写import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/student_info ?useSSLfalse serverTimezoneAsia/Shanghai characterEncodingutf8 allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 你的密码; 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); } }参数逐个说明useSSLfalse是关闭 SSL 加密本地开发不需要加密传输不加这个参数 MySQL 8 会输出一大段 SSL 警告serverTimezoneAsia/Shanghai指定时区不指定时 MySQL 8 会直接报时区错误这是最常见的启动失败原因characterEncodingutf8保证中文不会乱码配套数据库建表时的utf8mb4从源头到传输全链路都是 UTF-8allowPublicKeyRetrievaltrue只在 MySQL 8 下需要不配置会报Public Key Retrieval is not allowed。注意Class.forName这段写进了静态代码块类加载时就会执行确保 Driver 只注册一次。如果使用 MySQL 5.x把驱动类名改成com.mysql.jdbc.Driver驱动 jar 也从 5.x 的版本里找。3.2 StudentDAO增删改查全部用 PreparedStatementDAO 层就是四个方法按课程设计标准来写每个方法都接收参数、返回结果界面层只管调用不直接拼 SQL。import java.sql.*; import java.util.ArrayList; import java.util.List; public class StudentDAO { public boolean addStudent(Student s) { String sql INSERT INTO student (stu_no, name, gender, age, major, phone) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, s.getStuNo()); ps.setString(2, s.getName()); ps.setString(3, s.getGender()); ps.setInt(4, s.getAge()); ps.setString(5, s.getMajor()); ps.setString(6, s.getPhone()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } public boolean deleteStudent(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; } } public boolean updateStudent(Student s) { String sql UPDATE student SET stu_no?, name?, gender?, age?, major?, phone? WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, s.getStuNo()); ps.setString(2, s.getName()); ps.setString(3, s.getGender()); ps.setInt(4, s.getAge()); ps.setString(5, s.getMajor()); ps.setString(6, s.getPhone()); ps.setInt(7, s.getId()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } public ListStudent queryAll() { String sql SELECT * FROM student ORDER BY id; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } catch (SQLException e) { e.printStackTrace(); } return list; } public ListStudent queryByKeyword(String keyword) { String sql SELECT * FROM student WHERE stu_no LIKE ? OR name LIKE ? ORDER BY id; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setString(2, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } } catch (SQLException e) { e.printStackTrace(); } return list; } private Student mapRow(ResultSet rs) throws SQLException { Student s new Student(); s.setId(rs.getInt(id)); s.setStuNo(rs.getString(stu_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setAge(rs.getInt(age)); s.setMajor(rs.getString(major)); s.setPhone(rs.getString(phone)); return s; } }Java 7 的 try-with-resources 写法在这里很关键连接、PreparedStatement、ResultSet 都实现了 AutoCloseable方法结束自动关闭不用写 finally 里那一堆 null 判断。这套写法值得背下来面试问 JDBC 资源管理时也能直接用上。queryByKeyword里的 LIKE 模糊查询是课设里的加分项注意这里的%是拼在参数里的SQL 语句本体仍然是参数化查询没有注入风险。3.3 关于数据库连接池课设阶段真不用上有些同学看到热门技术里经常出现mysql 数据库连接池就想往课设里塞一个 Druid 或 HikariCP。我的建议是除非你的系统里有上千条并发查询的演示场景否则不要加。连接池的收益在高并发下才明显课设系统单机单用户DriverManager.getConnection足够。加了连接池反而要把配置文件、池参数、jar 依赖都引进来出了问题排查成本更高。如果确实想体现这块能力正确的做法是在 DBUtil 里预留接口不改变 DAO 层的调用方式。把getConnection()内部实现换掉DAO 代码一行不用动这个设计本身就比硬塞一个连接池更能体现对分层结构的理解。4. Swing 界面层从登录框到主窗体的完整实现4.1 登录逻辑admin 表校验和密码输入框登录界面建议单独做成一个LoginFrame类主方法直接从这里启动。界面布局用绝对定位还是布局管理器课设阶段选 FlowLayout 或 GridBagLayout 都行。密码框必须用JPasswordField不能用 JTextField这是基本的安全意识答辩时老师会看。校验逻辑写在一个登录按钮的 ActionListener 里import javax.swing.*; import java.awt.*; import java.sql.*; public class LoginFrame extends JFrame { private JTextField usernameField; private JPasswordField passwordField; public LoginFrame() { setTitle(学生信息管理系统 - 登录); setSize(360, 220); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); setResizable(false); JPanel panel new JPanel(new GridLayout(3, 2, 8, 8)); panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); panel.add(new JLabel(用户名:)); usernameField new JTextField(); panel.add(usernameField); panel.add(new JLabel(密码:)); passwordField new JPasswordField(); panel.add(passwordField); JButton loginBtn new JButton(登 录); panel.add(loginBtn); JButton resetBtn new JButton(重 置); panel.add(resetBtn); setContentPane(panel); loginBtn.addActionListener(e - doLogin()); resetBtn.addActionListener(e - { usernameField.setText(); passwordField.setText(); }); } private void doLogin() { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()).trim(); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } if (checkAdmin(username, password)) { new MainFrame().setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } } private boolean checkAdmin(String username, String password) { String sql SELECT * FROM admin WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { return rs.next(); } } catch (SQLException e) { e.printStackTrace(); return false; } } public static void main(String[] args) { SwingUtilities.invokeLater(() - new LoginFrame().setVisible(true)); } }两个细节new String(passwordField.getPassword())是因为 JPasswordField 的 getPassword 返回的是 char 数组不直接给 String登录成功后dispose()关掉登录窗口而不是setVisible(false)这样不会残留一个隐藏窗口。SwingUtilities.invokeLater的作用是把 UI 创建动作放到事件分发线程上这是 Swing 的线程模型要求虽然不是每次都能察觉到区别但养成本来就应该这样写的习惯没有坏处。4.2 主窗体JTable DefaultTableModel 的实现和刷新主窗体是整个系统里代码量最大的部分核心是一个嵌在JScrollPane里的 JTable配合上方的按钮栏完成增删改查和刷新。这里有个新手很容易踩的设计问题直接改 JTable 里显示的数据改完发现表格不更新。正确做法是每次操作后重新构建表格模型把 DAO 查出来的数据重新灌进去。import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.util.List; public class MainFrame extends JFrame { private JTable table; private DefaultTableModel tableModel; private JTextField keywordField; private StudentDAO dao new StudentDAO(); public MainFrame() { setTitle(学生信息管理系统); setSize(800, 500); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel topPanel new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); JButton addBtn new JButton(新增); JButton editBtn new JButton(修改); JButton deleteBtn new JButton(删除); JButton refreshBtn new JButton(刷新); keywordField new JTextField(12); JButton searchBtn new JButton(查询); topPanel.add(addBtn); topPanel.add(editBtn); topPanel.add(deleteBtn); topPanel.add(refreshBtn); topPanel.add(new JLabel(关键字:)); topPanel.add(keywordField); topPanel.add(searchBtn); add(topPanel, BorderLayout.NORTH); tableModel new DefaultTableModel(new Object[]{ID, 学号, 姓名, 性别, 年龄, 专业, 电话}, 0); table new JTable(tableModel); add(new JScrollPane(table), BorderLayout.CENTER); addBtn.addActionListener(e - showEditDialog(null)); editBtn.addActionListener(e - { int row table.getSelectedRow(); if (row 0) { JOptionPane.showMessageDialog(this, 请先选中要修改的行); return; } showEditDialog(getStudentFromRow(row)); }); deleteBtn.addActionListener(e - doDelete()); refreshBtn.addActionListener(e - refreshTable()); searchBtn.addActionListener(e - { String keyword keywordField.getText().trim(); if (keyword.isEmpty()) { refreshTable(); } else { refreshTable(dao.queryByKeyword(keyword)); } }); refreshTable(); } private void refreshTable() { refreshTable(dao.queryAll()); } private void refreshTable(ListStudent list) { tableModel.setRowCount(0); for (Student s : list) { tableModel.addRow(new Object[]{ s.getId(), s.getStuNo(), s.getName(), s.getGender(), s.getAge(), s.getMajor(), s.getPhone() }); } } private Student getStudentFromRow(int row) { Student s new Student(); s.setId((Integer) tableModel.getValueAt(row, 0)); s.setStuNo((String) tableModel.getValueAt(row, 1)); s.setName((String) tableModel.getValueAt(row, 2)); s.setGender((String) tableModel.getValueAt(row, 3)); s.setAge((Integer) tableModel.getValueAt(row, 4)); s.setMajor((String) tableModel.getValueAt(row, 5)); s.setPhone((String) tableModel.getValueAt(row, 6)); return s; } private void doDelete() { int row table.getSelectedRow(); if (row 0) { JOptionPane.showMessageDialog(this, 请先选中要删除的行); return; } int id (Integer) tableModel.getValueAt(row, 0); int confirm JOptionPane.showConfirmDialog(this, 确认删除该学生记录吗?, 确认, JOptionPane.YES_NO_OPTION); if (confirm JOptionPane.YES_OPTION dao.deleteStudent(id)) { refreshTable(); JOptionPane.showMessageDialog(this, 删除成功); } } private void showEditDialog(Student student) { new StudentEditDialog(this, student, dao, this::refreshTable).setVisible(true); } }getSelectedRow返回 -1 时先弹出提示而不是直接执行这是基本的交互保护。表格刷新用setRowCount(0)清空再一行行 addRow虽然简单粗暴但对于几百条数据量的课设场景完全够用不需要引入 TableModel 的事件通知机制。刷新后数据从数据库读一遍保证看到的是真实落库结果不会出现界面和数据源不同步的问题。getStudentFromRow把表格当前行的数据封装回 Student 对象是为了修改时把原值带进编辑框这个操作是很多简化版课设没有做但很实用的设计用户不用重新输入全部字段。4.3 编辑对话框新增和修改复用同一个窗体新增和修改可以复用同一个对话框类传入的 student 参数为 null 表示新增非 null 表示修改。这个复用的做法简化了代码量也让逻辑更集中。import javax.swing.*; import java.awt.*; public class StudentEditDialog extends JDialog { private JTextField stuNoField, nameField, ageField, majorField, phoneField; private JComboBoxString genderBox; private StudentDAO dao; private Student student; private Runnable onSuccess; public StudentEditDialog(Frame owner, Student student, StudentDAO dao, Runnable onSuccess) { super(owner, student null ? 新增学生 : 修改学生, true); this.student student; this.dao dao; this.onSuccess onSuccess; setSize(320, 300); setLocationRelativeTo(owner); JPanel form new JPanel(new GridLayout(6, 2, 6, 6)); form.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); form.add(new JLabel(学号:)); stuNoField new JTextField(); form.add(stuNoField); form.add(new JLabel(姓名:)); nameField new JTextField(); form.add(nameField); form.add(new JLabel(性别:)); genderBox new JComboBox(new String[]{男, 女}); form.add(genderBox); form.add(new JLabel(年龄:)); ageField new JTextField(); form.add(ageField); form.add(new JLabel(专业:)); majorField new JTextField(); form.add(majorField); form.add(new JLabel(电话:)); phoneField new JTextField(); form.add(phoneField); JButton saveBtn new JButton(保存); JButton cancelBtn new JButton(取消); form.add(saveBtn); form.add(cancelBtn); setContentPane(form); if (student ! null) { stuNoField.setText(student.getStuNo()); nameField.setText(student.getName()); genderBox.setSelectedItem(student.getGender()); ageField.setText(String.valueOf(student.getAge())); majorField.setText(student.getMajor()); phoneField.setText(student.getPhone()); } saveBtn.addActionListener(e - doSave()); cancelBtn.addActionListener(e - dispose()); } private void doSave() { String stuNo stuNoField.getText().trim(); String name nameField.getText().trim(); String ageText ageField.getText().trim(); if (stuNo.isEmpty() || name.isEmpty() || ageText.isEmpty()) { JOptionPane.showMessageDialog(this, 学号、姓名、年龄不能为空); return; } int age; try { age Integer.parseInt(ageText); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, 年龄必须是数字); return; } boolean ok; if (student null) { Student s new Student(); s.setStuNo(stuNo); s.setName(name); s.setGender((String) genderBox.getSelectedItem()); s.setAge(age); s.setMajor(majorField.getText().trim()); s.setPhone(phoneField.getText().trim()); ok dao.addStudent(s); } else { student.setStuNo(stuNo); student.setName(name); student.setGender((String) genderBox.getSelectedItem()); student.setAge(age); student.setMajor(majorField.getText().trim()); student.setPhone(phoneField.getText().trim()); ok dao.updateStudent(student); } if (ok) { JOptionPane.showMessageDialog(this, student null ? 新增成功 : 修改成功); if (onSuccess ! null) onSuccess.run(); dispose(); } else { JOptionPane.showMessageDialog(this, 保存失败请检查输入或数据库连接); } } }年龄输入用 try-catch 包住Integer.parseInt这一步把非数字输入挡在数据库之外数据库层面不会收到错误类型的参数。新增和修改的差异只在 Student 对象有没有 idonSuccess回调在保存成功后触发主界面的refreshTable不需要两个窗体之间互相持有引用。这里需要补一个 Student 实体类字段对应数据库表再写上 getter/setter这是连接数据库和界面的数据载体。类本身不复杂代码就省略了但不要省略这个类直接拿散装字符串传参那会让后面的维护变得很别扭。4.4 业务代码的分层边界DAO、Service、UI 各管一段课程设计里最常见的坏味道是把 SQL、业务逻辑、界面事件全写在一个按钮监听器里。这个系统的规模不需要引入 Spring但分层思想可以把代码组织得清楚UI 层只处理界面展示和用户交互调用 DAO 拿数据DAU 层只负责 SQL 和结果集映射中间如果有业务规则再插一个 Service 层。比如查重这个逻辑新增学生时如果学号已经存在数据库的唯一索引会直接报错但报错信息不友好。更好的做法是在 DAO 里加一个isStuNoExists(String stuNo)方法界面保存前先查一次给出该学号已存在的明确提示。这一层业务判断放 DAO 还是 Service按你自己的习惯但不要堆在按钮监听器里。这个分层习惯比这个项目本身更值钱后面接触 Java Web 和 Spring Boot你会发现同样的结构到处都是。5. 避坑与排查5 个让课设翻车的经典问题5.1 启动报错Public Key Retrieval is not allowed现象程序启动时第一次查询数据库就抛异常异常信息里包含Public Key Retrieval is not allowed。原因MySQL 8 默认使用 caching_sha2_password 认证插件在 SSL 关闭的情况下客户端第一次连接时需要通过 RSA 公钥加密传输密码而连接串没允许客户端自动获取公钥。解决JDBC 连接串上追加allowPublicKeyRetrievaltrueuseSSLfalse两个参数一起使用。只加 allowPublicKeyRetrieval 不关 SSL 也能解决但本地开发建议两个都加上省去 SSL 握手的额外开销。如果加了参数还报同样的错确认一下修改后有没有重启程序驱动类是类加载时读取的有些 IDE 需要 clean 一下再跑。5.2 明明建了库程序却报 database 不存在现象代码里 URL 写的jdbc:mysql://localhost:3306/student_info报错提示Unknown database student_info但命令行SHOW DATABASES能看到这个库。原因最常见的是建库语句没执行成功或者是大小写不一致。MySQL 在 Linux 下对数据库名和表名的大小写敏感Windows 下不敏感课程设计如果跨系统演示这个问题会突然冒出来。解决SHOW DATABASES确认库的实际名称逐个字符核对。另外确认建库时用了USE student_info;并且建表语句是在这个库下执行的。如果表也建在别的库下面查询时同样会报 table doesnt exist。统一用小写库名和表名可以少踩很多跨平台的问题。5.3 中文乱码插入和查询显示问号现象通过 Java 程序插入的中文在 MySQL 命令行里显示为??反过来命令行里插入的中文在 Swing 表格里显示为乱码。原因这个有两层。第一层是 JDBC 连接串缺characterEncodingutf8导致传输层用了默认的 latin1第二层是数据库或表的字符集不是 utf8mb4。解决先用SHOW CREATE TABLE student;看表的默认字符集如果不是 utf8mb4执行ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;。然后确认连接串里带了characterEncodingutf8。两层都改完后删除表里已有的乱码数据重新插入一遍乱码数据本身已经是坏字节改完字符集后不会自动变好。这个血泪经验值得记住字符集问题一定要在写入之前确认数据一旦写坏只能删掉重来。5.4 Swing 界面点击按钮后卡住不动现象点击刷新或新增后界面白屏窗口无响应过很久才恢复或者一直卡住。原因Swing 是单线程模型所有 UI 更新必须在事件分发线程EDT上执行。如果在事件监听器里执行了耗时操作比如数据库查询、大文件读写EDT 被阻塞界面就卡住了。解决课设场景数据量小通常不会触发这个问题。但如果演示时数据量到了几千条或者查询语句写得低效卡顿就会出现。推荐的方案是给耗时操作单独开线程再把结果通过SwingUtilities.invokeLater交回 EDT 更新界面Thread queryThread new Thread(() - { ListStudent list dao.queryAll(); SwingUtilities.invokeLater(() - refreshTable(list)); }); queryThread.start();这块属于加分项不写不影响功能写了就能在答辩时解释 Swing 的线程模型。核心思路是DAO 的数据库操作放后台线程界面的 tableModel 更新放回 EDT。5.5 运行环境换到别的电脑数据库连不上现象把项目打包带去演示换一台电脑运行报Communications link failure或连接超时。原因连接串里写死的localhost:3306只在本机有效。换电脑演示时如果那台机器没装 MySQL或者端口不一致连接必定失败。这是课设答辩最典型的翻车现场。解决提前准备两个方案。方案一演示机提前装好 MySQL导入建表脚本改一下 DBUtil 里的密码就能跑方案二不想装 MySQL把数据库文件拷过去也不行MySQL 不能这样迁移。最稳妥的做法是答辩前一晚在演示机上从头到尾跑一遍建库脚本和程序确认能通。连接串里把localhost改成127.0.0.1在某些环境下可以绕过 IPv6 解析问题如果出现连接超时而 MySQL 服务正常值得试一下。6. 打包成可执行 jar 和完整验证从代码到能演示的最后一公里写完了代码下一步是把自己这台机器上的工程变成一个能带走的可执行 jar。这里介绍没有 Maven 的情况直接在 IDE 里操作IntelliJ IDEA 里打开 Project Structure选择 Artifacts添加一个 JAR类型选 From modules with dependencies主类选LoginFrame。构建时 IDEA 会把项目依赖的 mysql-connector jar 一起打进去。如果构建后运行报找不到主类多半是 Manifest 文件里的 Main-Class 没配对。可以手动写一个 Manifest注意最后一行必须是空行这是 Java 打包的老规矩但这算比较少见的问题。Maven 用户可以用 assembly 插件打 fat jar这一步跳过不写直接执行mvn package就行。运行验证按三层来做。第一层验证是数据库连接单独写一个测试 main 方法只调DBUtil.getConnection()能拿到连接再继续不要跳这步。第二层验证是 DAO 的增删改查写一个命令行测试类依次调用新增、查询、修改、删除每步输出结果确认。这两层跑通后再启动LoginFrame做界面验证此时如果出问题问题一定在界面层不需要回头查数据库。验证用例清单建议按这个顺序执行登录输错密码有提示输对密码能进去新增填完整信息保存成功表里出现新记录新增异常学号重复时给出友好提示而不是程序报错修改选中一行改姓名和专业保存后表格刷新删除选中一行删除有确认框删除后表格刷新查询输入关键字查学号和姓名能模糊匹配刷新清空关键字后点刷新恢复全表显示每个用例跑完去 MySQL 命令行用SELECT确认数据真的写进了数据库这能同时验证程序和数据库的一致性。最后一章给一个实战技巧给系统加一个启动画面。Swing 有一个SplashScreen类可以用来在程序加载时显示一张图对课设答辩观感提升不小。做法是在 Manifest 里配SplashScreen-Image: splash.png把图片放在 jar 同级目录启动时 JVM 会自动加载。这个功能不复杂但有这个细节整个系统给人的完成度会高一个档次。回看这套做法我个人的习惯是把验证脚本留在工程里不要删掉。下次改别的功能改完先跑一遍测试类再碰界面能省下大量反复点按钮的时间。这套流程不一定是最快的但一定是最稳的。希望帮到你。本文还有配套的精品资源点击获取
返回列表