ARTICLE DETAIL

资讯详情

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

用Java Swing+MySQL实现学生健康档案管理系统:数据库设计与JDBC实战

用Java Swing+MySQL实现学生健康档案管理系统:数据库设计与JDBC实战 简介学生健康档案管理系统HealthArchives是一套基于MySQL与Java Swing的完整项目源码面向计算机相关专业学生、毕业设计及课程设计人群。系统自上而下分为学生信息、体检信息、病历信息三大模块支持学号、姓名、性别、系别、诊断、日期、身高、体重、胸围等字段的录入与查询并附带导入导出Excel、多条件高级查询、动态折线图分析等功能解决高校健康数据纸质化管理的痛点。资源包共98个文件包含30个Java源文件与38个class文件实现核心业务XML与properties为配置项PNG/JPG为界面截图另附SQL建表脚本与XLS示例数据压缩后仅1.27MB结构清晰便于学习。已有76人下载学习既适合初学Java Swing与MySQL者快速入门也适合中高级学生参考完整工程架构与数据流设计。项目代码均测试运行通过并配有数据库E-R图、表结构图示和操作界面预览可帮助读者快速理解系统设计思路直接用于课程汇报或二次开发。1. 用 MySQL Java Swing 做学生健康档案系统为什么这个组合还值得选每年到了课设季“学生健康档案管理系统 HealthArchives”都会以各种变体出现在选题列表里有的叫健康档案有的叫体质测试管理本质上都是同一个需求——把学生的基本信息、体检数据、体测成绩从纸质台账挪进数据库再提供一个界面让人录得进去、查得出来。而 MySQL 加 Java Swing 正是这个题目最常见的落地组合Swing 是 JDK 自带的桌面 GUI 工具包不依赖 Web 容器写完双击就能跑MySQL 免费且教程密度极高JDBC 驱动也足够成熟。对正在学 Java 基础、想做课程设计或者补一个完整 CRUD 项目经验的从业者来说这个组合能把 JDBC 连接、SQL 表设计、GUI 事件监听、数据校验这些核心技能一次性练全是性价比最高的入门型信息系统方案。这套系统能解决的实际问题很具体校医室或体卫处需要维护学生的身高体重、视力、血压、肺活量和体测成绩同时关注少数异常指标。用 Excel 管容易版本混乱用 Web 系统又要考虑部署环境而一个本地桌面程序配合 MySQL 数据库既满足单机或局域网使用又保留了以后扩展成 C/S 架构的空间。下面我就按自己做过这类系统的顺序拆开讲先建表再搭界面最后打通 JDBC并且把交付时最容易翻车的几个坑提前摆出来。2. 先把数据模型立住HealthArchives 的 MySQL 表设计与字段取舍做 Swing 项目最容易犯的错是一上来就拖控件画窗口。界面画完才发现需要的字段没建或者表结构设计得改于是 DAO 层重写所有查询面板跟着返工。我的一般做法是先把 MySQL 表结构定死再写一行界面代码。因为后续的 VO 类、DAO 方法、表格列全部围绕表结构展开表结构每改动一次Java 代码至少改三处这个成本在课设周期里是伤不起的。2.1 五张核心表从学生档案到体检记录的关系拆解健康档案系统的核心数据可以拆成五张表班级表、学生表、体检记录表、体测成绩表、管理员账户表。班级和学生是一对多一个班级下有多个学生学生和体检记录是一对多一名学生每年可能有多条体检记录体测成绩同理。管理员表独立出来是为了登录鉴权时不再把账号密码混在学生信息里。表名职责关键字段与主表关系class_info班级基础信息class_id, class_name, grade被 student 引用student学生基础档案student_id, student_no, name, gender, birth_date, class_id核心主表health_record每次体检的指标数据record_id, student_id, exam_date, height, weight, vision_left, vision_right, blood_pressure多对一关联 studentphysical_fitness体测成绩fitness_id, student_id, test_date, run_50m, run_800m, sit_up, jump多对一关联 studentsys_user登录账户user_id, username, password, role独立为什么要把体检数据单独拆表而不是叠在学生表后面如果每次都往 student 表里塞十几列体检数据一个学生体检三次就得建三行重复基础信息后续更新班级或班主任时全表扫描再逐条改极易造成数据不一致。拆开后学生基础信息只维护一份历史上每次体检就是一条新记录查“最近一次体检结果”只需要对 exam_date 做排序取最新这才是关系型数据库解决问题的方式。2.2 建库建表 DDL字段类型与字符集的一次性配置以下是我在 MySQL 8.0 下建库建表的完整脚本MySQL 5.7 同样适用只要把默认字符集显式指定即可。-- 创建数据库显式指定 utf8mb4避免中文乱码 CREATE DATABASE IF NOT EXISTS healtharchives DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE healtharchives; CREATE TABLE class_info ( class_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 班级ID, class_name VARCHAR(20) NOT NULL COMMENT 班级名称如计科2101, grade VARCHAR(10) NOT NULL COMMENT 年级如2021级, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT班级信息表; CREATE TABLE student ( student_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学生ID系统内部使用, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号业务唯一键, name VARCHAR(30) NOT NULL COMMENT 姓名, gender CHAR(1) NOT NULL COMMENT 性别M男/F女, birth_date DATE COMMENT 出生日期, class_id INT NOT NULL COMMENT 所属班级ID逻辑关联class_info, phone VARCHAR(20) COMMENT 监护人联系电话, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 建档时间 ) ENGINEInnoDB COMMENT学生基础档案表; CREATE TABLE health_record ( record_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 体检记录ID, student_id INT NOT NULL COMMENT 学生ID逻辑关联student, exam_date DATE NOT NULL COMMENT 体检日期, height DECIMAL(5,2) COMMENT 身高(cm)如165.50, weight DECIMAL(5,2) COMMENT 体重(kg), vision_left DECIMAL(4,2) COMMENT 左眼视力, vision_right DECIMAL(4,2) COMMENT 右眼视力, blood_pressure VARCHAR(15) COMMENT 血压如120/80, is_abnormal TINYINT(1) DEFAULT 0 COMMENT 是否异常0否/1是, remark VARCHAR(200) COMMENT 异常备注, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT体检记录表;这段脚本里有几个参数值得单独说明。DECIMAL(5,2) 表示总共 5 位数字、小数占 2 位最大能存 999.99身高和体重用这个类型正好如果身高将来要存 180.5不存在精度丢失。gender 用 CHAR(1) 而不枚举是为了避免 MySQL 枚举类型后续加选项时需要 ALTER TABLE在课设场景里省事更重要。student_no 设置成 UNIQUE这是业务层面的硬约束防止同一学号录两次。create_time 用 TIMESTAMP DEFAULT CURRENT_TIMESTAMP插入数据时不用专门赋值数据库自动补齐时间戳后面做“最近建档”排序时直接用这个字段。2.3 物理外键要不要建健康档案系统的取舍经验很多教材强调必须建 FOREIGN KEY我在这个项目里却不建只用逻辑关联。原因很实际物理外键会让批量导入数据的顺序变得非常敏感必须先导班级再导学生否则外键校验直接报错删除学生时如果有历史体检记录外键默认的 RESTRICT 会拦住 DELETE新手在 MySQL Workbench 里删测试数据时经常被“cannot delete or update a parent row”卡住半天。而健康档案系统的使用者是校医室或体育老师业务上要求的是录入和查询顺畅不是银行级的强一致性所以我在建表时只用 COMMENT 标注“逻辑关联某表”在 Java 应用层做删除前的检查。这个取舍是一条血泪经验与其让外键约束增加调试成本不如在 DAO 层用代码保证一致性。日期字段的处理也值得说一下。birth_date 和 exam_date 都用 DATE不推荐 TIMESTAMP因为体检记录只需要精确到天DATE 占 3 字节比 TIMESTAMP 的 4 字节省空间查询时也省去处理时区的坑。插入一条示例数据验证表结构INSERT INTO class_info (class_name, grade) VALUES (计科2101, 2021级); INSERT INTO student (student_no, name, gender, birth_date, class_id) VALUES (2021010203, 张伟, M, 2003-05-12, 1); INSERT INTO health_record (student_id, exam_date, height, weight, vision_left, vision_right, blood_pressure, is_abnormal) VALUES (1, 2024-09-10, 175.50, 62.30, 5.0, 4.8, 118/76, 0);插入成功后用 SELECT 验证一下 student 表和 health_record 表的关联查询是否正常确认 JOIN 能查出“某学号学生最近一次体检记录”再开始写 Java 那边的代码。这一步能过滤掉大多数字段命名笔误因为 Java 代码里的 getter/setter 是照着这些列名写的列名错了后面编译期根本发现不了。3. 用 Java Swing 搭出主界面从空窗口到能看能查的档案面板表结构定好后进入 Swing 界面开发。这个阶段的核心任务不是把界面做得花哨而是把三条链路走通打开窗口、加载数据到表格、把表格数据编辑后写回数据库。Swing 单线程模型决定了所有界面刷新必须在事件分发线程上做所以入口用 SwingUtilities.invokeLater 包裹是最稳的启动方式。3.1 主窗口骨架JFrame、菜单栏和数据表格的组织主窗口用一个 JFrame顶部放 JMenuBar左侧放操作工具栏中间放承载学生列表的 JTable。这样布局既符合桌面程序的习惯也为后续加体检记录面板留出空间。public class MainFrame extends JFrame { private JTable studentTable; private DefaultTableModel tableModel; private JTextField searchField; public MainFrame() { setTitle(学生健康档案管理系统 - HealthArchives); setSize(900, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JMenuBar menuBar new JMenuBar(); JMenu archiveMenu new JMenu(档案管理); JMenuItem addStudentItem new JMenuItem(新增学生); JMenuItem refreshItem new JMenuItem(刷新列表); archiveMenu.add(addStudentItem); archiveMenu.add(refreshItem); menuBar.add(archiveMenu); setJMenuBar(menuBar); tableModel new DefaultTableModel( new Object[]{ID, 学号, 姓名, 性别, 出生日期, 班级}, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 表格默认只读编辑走对话框 } }; studentTable new JTable(tableModel); studentTable.setRowHeight(24); studentTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); add(new JScrollPane(studentTable), BorderLayout.CENTER); refreshStudentList(); setVisible(true); } }这里有两个设计点。表格模型把 isCellEditable 返回 false让表格只读所有编辑操作都通过“新增”“修改”按钮弹出对话框完成避免用户直接在表格单元格里乱改导致的数据校验失控。表格右上角我习惯放一个搜索框按学号或姓名过滤底层就是 SQL 的 LIKE 查询这对后面体测数据录入时快速定位学生非常有用。主窗口里不直接执行 JDBC 查询而是调用 DAO 层的 listStudents 方法界面和数据处理解耦后续换数据库或改表结构时只动 DAO。3.2 体检记录录入面板JDialog 与数据校验的组合录入体检数据用一个模态 JDialog。JDialog 阻止用户关闭窗口前操作主窗体保证一条记录要么完整保存要么取消不会录一半切走。表单包含学号、姓名、体检日期、身高、体重、左右视力和血压。public class HealthRecordDialog extends JDialog { private JTextField studentNoField; private JTextField nameField; private JFormattedTextField heightField; public HealthRecordDialog(JFrame owner) { super(owner, 录入体检记录, true); studentNoField new JTextField(12); nameField new JTextField(12); nameField.setEditable(false); NumberFormatter heightFormatter new NumberFormatter(); heightFormatter.setMinimum(50.0); heightFormatter.setMaximum(250.0); heightFormatter.setAllowsInvalid(false); heightField new JFormattedTextField(heightFormatter); heightField.setColumns(8); // 学号输入框添加焦点失去监听自动带出学生姓名 studentNoField.addFocusListener(new FocusAdapter() { Override public void focusLost(FocusEvent e) { StudentDAO dao new StudentDAO(); String name dao.getNameByStudentNo(studentNoField.getText().trim()); nameField.setText(name); } }); JButton saveBtn new JButton(保存); saveBtn.addActionListener(e - saveRecord()); // ... 布局代码省略 } }JFormattedTextField 是 Swing 提供的有格式输入框配合 NumberFormatter 能直接限制身高输入范围用户想输 300 都输不进去比提交后再校验体验好很多。学号输入框失去焦点时自动查学生表带出姓名这是录入体验里的关键细节否则每次录一个学生要重复敲两遍名字。保存按钮的事件里先取所有字段再逐项做非空和范围校验最后调用 DAO 的 insertHealthRecord 方法。按钮事件里有一点要注意数据库操作如果放在事件线程里数据量大时窗口会卡住。最简单的方案是先用小数据集开发确认功能正确后再把耗时查询挪进 SwingWorker。别一上来就上并发工具类排查起来反而麻烦。3.3 双击表格行回显数据编辑记录的基础操作在表格里双击一行系统应该把这条体检记录带出来修改。我的做法是查询时把 record_id 作为隐藏列放在表格模型第 0 列用户看到的表格里隐藏列不显示但后台能拿到这条记录的准确主键。studentTable.addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() 2) { int row studentTable.getSelectedRow(); if (row 0) return; int recordId (int) tableModel.getValueAt(row, 0); String studentNo (String) tableModel.getValueAt(row, 1); HealthRecord record new HealthRecordDAO().findById(recordId); HealthRecordDialog dialog new HealthRecordDialog(MainFrame.this, record); dialog.setVisible(true); } } });用主键 ID 做更新条件而不是用学号。学号虽然唯一但实际使用中存在改学号的可能主键才是数据库里不变的定位符。这个细节很容易被忽视我见过不少项目按姓名更新重名学生直接把别人数据覆盖了。编辑对话框复用录入窗口传入 record 对象后先把现有值回显到表单保存时调用 update 方法按 record_id 更新即可。4. JDBC 连接与 DAO 层让健康档案数据真正落库界面层完成接下来是打通数据库的关键一步。许多课设项目卡在这一步驱动加载不对、连接串参数缺失、编码未统一造成“代码能编译但点按钮没反应”。JDBC 这块我把自己的固定配置写出来照抄基本不会翻车。4.1 连接参数与驱动配置从 DriverManager 到连接池MySQL 8.x 使用的驱动类是 com.mysql.cj.jdbc.Driver老一点的 mysql-connector-java 5.x 用的是 com.mysql.jdbc.Driver。新版 JDBC 驱动支持 SPI 自动注册省略 Class.forName 也能运行但写上更保险也方便别人看懂意图。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/healtharchives ?useSSLfalse serverTimezoneAsia/Shanghai characterEncodingutf8 allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD your_password; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }连接串里这几个参数逐个说。useSSLfalse 关闭 SSL 加密本地开发时没有配置证书默认 true 会抛 SSL 连接异常这是新手最常碰到的第一个拦路虎。serverTimezoneAsia/Shanghai 解决 MySQL 8 对时区的强校验不加会报 CST 时区错误。characterEncodingutf8 保证中文从 Java 到数据库不乱码。allowPublicKeyRetrievaltrue 专门解决 MySQL 8 默认 caching_sha2_password 认证插件下新版驱动的密钥获取问题不加会报 “Public Key Retrieval is not allowed”。PASSWORD 字段在课设里可以写死但正式项目建议放到配置文件中读取。如果你想把项目做得更像工程而不是纯课设可以换成 HikariCP 连接池。连接池的好处是复用连接避免每次查询都重新握手建立 TCP 连接频繁点击操作时界面响应更快。HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/healtharchives?serverTimezoneAsia/ShanghaicharacterEncodingutf8useSSLfalse); config.setUsername(root); config.setPassword(your_password); config.setMaximumPoolSize(10); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接数 config.setConnectionTimeout(30000); // 获取连接超时30秒 HikariDataSource ds new HikariDataSource(config);HikariCP 的 jar 包可以从官方 GitHub 发布页下载记得连同 slf4j-api 和 slf4j-simple 一起放进 lib 目录否则运行时报 NoClassDefFoundError。连接池参数里最大连接数设 10 对于一个单机桌面应用绰绰有余因为同时操作数据库的只有当前这个用户连接超时设 30 秒是为了防止数据库假死时界面无限等待。数据库可视化工具我推荐 MySQL Workbench 或 DBeaver正版免费功能足够不建议用破解版 Navicat给自己留风险没有意义。4.2 DAO 层的 CRUD 实现PreparedStatement 是底线DAO 层把 SQL 语句集中封装界面代码只调用业务方法。PreparedStatement 必须用不能拿字符串拼接 SQL。拼接 SQL 不但有注入风险还容易在值里带单引号时直接把语句截断导致语法错误。public class HealthRecordDAO { public int insert(HealthRecord record) { String sql INSERT INTO health_record (student_id, exam_date, height, weight, vision_left, vision_right, blood_pressure, is_abnormal, remark) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, record.getStudentId()); ps.setDate(2, Date.valueOf(record.getExamDate())); ps.setBigDecimal(3, record.getHeight()); ps.setBigDecimal(4, record.getWeight()); ps.setBigDecimal(5, record.getVisionLeft()); ps.setBigDecimal(6, record.getVisionRight()); ps.setString(7, record.getBloodPressure()); ps.setInt(8, record.getIsAbnormal()); ps.setString(9, record.getRemark()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }这段代码用了 try-with-resourcesJava 7 以后的特性Connection、PreparedStatement 在语句块结束后自动关闭省去在 finally 里手动 close 的样板代码也避免漏关连接导致数据库连接数被打满。setDate 接收 java.sql.Date所以传入的参数如果是 LocalDate 需要先用 Date.valueOf 转换。executeUpdate 返回影响行数1 表示插入成功0 表示失败调用方可以根据返回值弹出成功或失败的提示框。字段顺序一定要和 SQL 里的问号顺序一一对应这是排错时最容易看花眼的地方。查询方法同样用 PreparedStatement查询结果集转成 VO 对象列表返回给界面层。public ListHealthRecord listByStudentId(int studentId) { String sql SELECT * FROM health_record WHERE student_id ? ORDER BY exam_date DESC; ListHealthRecord list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, studentId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { HealthRecord r new HealthRecord(); r.setRecordId(rs.getInt(record_id)); r.setStudentId(rs.getInt(student_id)); r.setExamDate(rs.getDate(exam_date).toLocalDate()); r.setHeight(rs.getBigDecimal(height)); // 其余字段逐个 set list.add(r); } } } catch (SQLException e) { e.printStackTrace(); } return list; }查询按体检日期倒序这样表格第一行永远是最近一次体检符合管理系统的使用习惯。ResultSet 也要放在独立的 try-with-resources 块里最终返回的 list 不受连接关闭影响。注意 rs.getDate 返回 java.sql.Date转 LocalDate 时直接调用 toLocalDate。我在开发中会把所有 DAO 方法集中放在一个类里总共二十来个方法每个方法对应一张表的一个操作后续维护时搜索非常方便。4.3 数据一致性批量导入体测记录要用事务单机 Swing 应用的数据一致性不涉及分布式事务核心就是两条前置校验加数据库事务。批量导入体测数据时如果几十条 INSERT 执行到一半突然断电前面成功的数据和后面没执行的数据就会拼出一个残缺的导入结果。解决办法是关闭自动提交全部成功再统一 commit失败则回滚。public int batchInsert(ListFitnessRecord records) { String sql INSERT INTO physical_fitness (student_id, test_date, run_50m, run_800m, sit_up, jump) VALUES (?, ?, ?, ?, ?, ?); Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 ps conn.prepareStatement(sql); for (FitnessRecord r : records) { ps.setInt(1, r.getStudentId()); ps.setDate(2, Date.valueOf(r.getTestDate())); ps.setBigDecimal(3, r.getRun50m()); ps.setBigDecimal(4, r.getRun800m()); ps.setInt(5, r.getSitUp()); ps.setBigDecimal(6, r.getJump()); ps.addBatch(); // 加入批次 } ps.executeBatch(); conn.commit(); // 全部成功才提交 return records.size(); } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return 0; } finally { try { if (ps ! null) ps.close(); if (conn ! null) conn.setAutoCommit(true); // 恢复默认 if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这里事务的范围是全部批次的 INSERT任何一个语句抛异常就走 rollback所有数据保持原样。批量接口不要混着用 Java 8 的 stream 和 for 循环addBatch 本身就维护了批次边界for 循环配合 ps.addBatch 是最清晰的组合。executeBatch 的返回值是每条 SQL 影响行数的数组只判断最后是否抛异常即可不用逐条解析。另外注意事务期间连接的 setAutoCommit(false) 状态在关闭前要恢复 true因为这个连接归还给连接池后下个使用者要的是默认自动提交状态连接池不会自动帮你重置。5. MySQL 连接与交付避坑五条最常见的翻车记录这部分是我把几个常见问题和踩坑记录整理出来按“现象 → 原因 → 解决”的格式写做这个系统时按顺序排查能省下大量时间。5.1 error 2002 (HY000)连接不上本地 MySQL 的排查顺序现象运行程序点登录控制台报 “java.sql.SQLException: Cannot create PoolableConnectionFactory” 或者命令行直接报 “ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock”。原因和解决按以下顺序排查。第一确认 MySQL 服务真的在运行。Windows 下检查任务管理器里 mysqld 进程或运行net start | findstr mysqlLinux 下执行systemctl status mysql没启动就systemctl start mysql。第二确认程序里连接的端口和密码对得上。用命令行验证mysql -u root -p -h 127.0.0.1 -P 3306能进去说明服务正常问题在 Java 连接串。第三看是不是认证插件不兼容。MySQL 8 默认创建的用户使用 caching_sha2_password老版本驱动不吃这一套解决办法是连接串加 allowPublicKeyRetrievaltrue或者升级到 mysql-connector-j 8.0 以上。第四检查端口占用和绑定地址。如果 MySQL 配置了 bind-address127.0.0.1远程连不过来是正常的本地测试则没关系。这条排查路径基本覆盖了 90% 的连不上问题顺序倒着来最容易绕弯路。5.2 中文乱码数据库存进去全是问号现象界面上输入“张伟”保存后数据库里变成“??”或者反过来数据库里正常、界面显示乱码。原因是 Java 源码文件、JVM 运行时编码、连接串字符集、数据库表字符集四层没有统一。我见过最隐蔽的一层是 IDEA 里源码文件是 UTF-8但编译时 JVM 默认取了操作系统的 GBK导致字符串在编译期就已经变形。标准解法第一源码文件统一 UTF-8IDEA 里在 Settings 中把 Project Encoding 和 File Encoding 都改成 UTF-8第二JDBC 连接串加 characterEncodingutf8第三建库建表显式指定 utf8mb4第四运行参数加-Dfile.encodingUTF-8。四层都统一后中文就不会再出问题。排查时可以分两步先在 MySQL Workbench 里直接手工插入一条中文数据看是否能正常显示能显示说明表和库没问题问题在 Java 侧不能显示则先改表字符集。5.3 字段名与 MySQL 保留字冲突报错信息却没指这一层现象建表一切正常但写 INSERT 或 UPDATE 时报语法错误错误信息指向某一行 SQL你反复检查语句看不出问题。原因字段名用了 MySQL 保留字。健康档案系统里常见的就是desc、from、order这类词。有人习惯把“描述”字段命名为 desc在 MySQL 5.7 某些配置下能用升级到 8.0 后直接报错。解决要么给字段加反引号desc VARCHAR(200)要么直接改成 description 或 remark。我更建议后者因为反引号写进每条 SQL 里都很碍眼而且换数据库时反引号的兼容性又成问题。另外表名也尽量不用单数复数混写统一用单数class_info 而不是 classes避免团队协作时每个人记的表名都不一样。5.4 打包成可双击运行 JAR双击没反应或找不到主类现象IDEA 里运行一切正常导出 JAR 后双击要么没反应要么命令行运行报 “could not find or load main class”要么报驱动类 ClassNotFoundException。原因有三清单文件里没有 Main-ClassMySQL 驱动 jar 没有打进最终的 JAR 里JDK 编译版本和运行版本不一致。解决最省心的是用 IDEA 的 Artifacts 功能打包时选 “JAR from modules with dependencies”这样驱动和依赖会被一起打进 fat jar。每次改代码后重新 Build → Build Artifacts就能拿到一个独立运行的 JAR。如果双击还是没反应先到命令行手动执行java -jar HealthArchives.jar控制台会把异常打出来比双击黑屏靠谱得多。也可以把依赖 jar 放 lib 目录用启动脚本运行echo off cd /d %~dp0 java -Dfile.encodingUTF-8 -cp HealthArchives.jar;lib\mysql-connector-j-8.0.33.jar com.healtharchives.Main pause这个 bat 脚本很通用。第一行切到脚本所在目录防止在别的路径双击时找不到 jar 包第二行指定 UTF-8 编码运行主类: 后面的 Main 类是项目的启动入口pause 让窗口在程序异常退出时停留方便看报错信息。生成 bat 后放到 JAR 同目录交付时连同 lib 目录一起给双击 bat 就能启动。5.5 表格数据不刷新点查询没反应或还是旧数据现象第一次打开窗口时表格有数据点“新增”保存后表格里看不到新记录重启程序又出现了。原因是保存后没有重新查询表格数据源。Swing 表格的数据来自 DefaultTableModel新增数据后必须调用查询方法重新填充模型再调用 fireTableDataChanged() 或通过 setDataVector 触发 UI 重绘。解决保存业务完成后在主窗口里重新执行一次 refreshStudentList()把 DAO 查询出的 List 转为表格模型的行数据。如果查询就慢学生量超过几百条就要考虑加搜索条件和分页先把 SQL 的 WHERE 条件加上后续再考虑 LIMIT 分页。Swing 本身不是为大数据量展示设计的全表一次拉一万行不但慢滚动也卡健康档案系统一个学校几千学生按班级过滤就很快了。6. 把 HealthArchives 做成能提报的项目导出体检汇总与自检清单系统能跑通增删改查只是及格线。交付前我一般会补两个能力把体检汇总导出成 Excel 方便提交给教务处做一份自检清单验证边界情况。6.1 用 Apache POI 导出体检汇总表有同学问 Java POI 能不能在 Word 里直接生成图表以 Apache POI 的 XWPF 组件来说往 Word 文档里插入原生图表支持很有限折腾占位符和 XML 的性价比很低。常见做法是导出 ExcelExcel 里既能承载数据又能让用户自行插入图表。下面这个方法是导出一份按班级汇总的体检统计表XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(班级体检汇总); String[] headers {班级, 人数, 平均身高, 平均体重, 视力异常人数}; for (int i 0; i headers.length; i) { sheet.createRow(0).createCell(i).setCellValue(headers[i]); } ListClassSummary summaries new HealthRecordDAO().listClassSummary(); int rowIndex 1; for (ClassSummary s : summaries) { Row row sheet.createRow(rowIndex); row.createCell(0).setCellValue(s.getClassName()); row.createCell(1).setCellValue(s.getCount()); row.createCell(2).setCellValue(s.getAvgHeight()); row.createCell(3).setCellValue(s.getAvgWeight()); row.createCell(4).setCellValue(s.getAbnormalCount()); } try (FileOutputStream out new FileOutputStream(体检汇总.xlsx)) { workbook.write(out); } workbook.close();XSSFWorkbook 对应 xlsx 格式HSSFWorkbook 对应 xls 格式新项目直接用 XSSFWorkbook。listClassSummary 是 DAO 里的一个统计方法用 GROUP BY class_id 加 AVG(height) 和 COUNT 聚合。导出后文件默认生成在项目根目录也可以改成 JFileChooser 让用户选保存路径。Excel 里中文列头在工作表打开后正常显示这个方法的输出结果教务处直接能用。6.2 交付前自检按清单过一遍边界场景最后分享一份项目提报前的自检清单我每次交付前都会照着走一遍。功能层面的自检用表格列出比口述清楚。检查项操作预期结果学号唯一性录入两个相同学号的学生第二次保存被拦截并提示数字范围身高输入 250 以上输入框直接拒绝或保存时弹校验提示空值校验不选班级直接保存提示“请选择班级”删除一致性删除有体检记录的学生先提示关联记录数确认后可一并删除或禁止删除中文显示录入中文姓名并重启系统各处显示无乱码时间排序同一学生录两学年体检数据表格默认显示最新一条在前打包运行在未装 IDEA 的机器上运行双击 bat 能正常启动并连上 MySQL上述检查里“删除有体检记录的学生”最容易藏雷。数据库里没有物理外键你删了 student 表的记录health_record 表里残留的孤儿数据会在统计平均身高时导致 JOIN 出空行。所以删除逻辑必须是先 SELECT COUNT(*) FROM health_record WHERE student_id?数量大于 0 时弹窗询问是否一并删除或者直接禁止删除只允许标记“已离校”。这比数据库外键更灵活也更贴近真实业务。我在带课设和带新人时反复讲一句话凡是先把表结构和连接参数定死再写界面的项目最后基本都能顺利交付凡是先画界面再补 SQL 的大多会在截止前最后一周里炸出各种数据问题。学生健康档案管理系统 HealthArchives 这个题目不大但它把 MySQL、JDBC、Swing 这三样 Java 从业者的基本功完整串了一遍把这个流程走通后面再接触 Spring 和 Web 层时你对数据模型和事务的理解会扎实很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表