简介:基于MySQL与Java Swing实现的学生健康档案管理系统,面向高校计算机相关专业学生、教师及企业开发者,适用于课程设计、毕业设计或Java GUI与数据库开发的入门进阶。系统覆盖学生信息管理、体检信息管理、病历信息管理三大模块,支持体检/病历数据的增删改查、多条件高级查询、导入导出Excel及动态折线图数据分析,并附带E-R图与数据库表结构说明。压缩包共98个文件,其中包含30个Java源文件、38个class编译文件、7个XML配置、7张PNG和6张JPG效果图,另有SQL建表脚本、properties配置文件、xls测试数据及.rp原型设计文档,整体大小1.27MB。资源还包含项目运行所需的pom.xml和可直接导入的数据库脚本,目录结构清晰,便于对照学习。已有76人学习下载,代码经测试运行成功,适合拿来练手或在此基础上二次开发。
1. 学生健康档案系统:一个 Java Swing + MySQL 的完整课设样本
如果你正处在课程设计或者毕设初期的“选题焦虑”里,大概率刷到过这类名字长到一口气念不完的项目:基于 MySQL + Java Swing 实现的学生健康档案管理系统。我第一次拆这个源码包的时候,说实话没抱太高期望,以为又是那种把数据库课设换个皮就拿出来重复上传的流水线作品。但看完HealthArchivesManagementSystem的代码结构和那几张功能截图之后,我得承认它比我预想的要完整不少——不是只有 CRUD 摆在那里,而是把登录注册、病历管理、体检管理、多条件查询、批量操作、Excel 导入导出全部串在了一起,而且数据库脚本dbstuhealth.sql是直接能跑的。
这个项目适合三类人:第一类是计算机相关专业(软件工程、物联网、电子信息这类)需要交课程设计或者毕设初稿的学生,拿它当骨架能省掉大量造轮子的时间;第二类是刚学完 Java SE、想看看 Swing 实际项目长什么样的小白;第三类是自己有管理系统开发需求、想参考模块划分和数据表设计的人。接下来我会把这套系统的表结构、界面实现、关键代码逻辑和最容易翻车的几个点全部拆开讲,你照着能复现,踩过的坑我也会直接标出来。
2. 先立住数据层:学生、病历、体检三张核心表怎么设计
2.1 E-R 图背后的实体关系
拿到这个项目的源码包时,第一眼值得看的不是src下的 Java 类,而是根目录那张E-Rͼ.jpg。这张图把整个系统分成了三条业务线:学生信息管理、体检信息管理、病历信息管理。健康档案说白了就是把病历和体检两类数据挂到学生这个主实体下面,形成一对多的关系——一个学生可以有多条病历记录,也可以有多条不同日期的体检记录。
这种设计其实是医疗信息管理系统里最常见的做法:学生是主数据,体检记录和病历记录是流水数据,两者通过学号这个外键关联。学号在这里不仅是主键,还承担了业务标识的角色,这一点和很多只拿自增 id 当主键的课设项目不一样,实际开发里也更符合校方查询的习惯——不管是导 Excel 还是按学号检索,都比拿 id 去对要直观得多。
病历数据的字段是学号、姓名、性别、系别、诊断、日期;体检数据多了年龄、身高、体重、胸围这几项。注意体检表里没有独立的诊断字段,因为体检本身只是体征记录,不产生诊断结论,这也是为什么系统要把病历和体检拆成两张表而不是合成一张大宽表——两类数据的更新频率和关注维度不一样,合在一起反而会让查询逻辑变乱。
2.2 从建表 SQL 看字段类型选择
数据库脚本文件是dbstuhealth.sql,导入之后你会看到一共三张表,学生表是主表,另外两张是明细表。建表语句的风格非常“课设标准”:字段名直接见名知意,类型选择保守,没有玩什么花活。这种风格如果你是第一次做数据库设计,直接照抄都不会出大问题。
核心建议:导入dbstuhealth.sql之前先把 MySQL 的字符集确认好。这个项目里面有中文数据,包括初始化的学生姓名、系别,所以建库语句里如果没显式指定utf8mb4,Windows 下默认的latin1会让你后面查出来的中文全是问号。常见做法是先执行一条建库语句再导数据:
CREATE DATABASE IF NOT EXISTS dbstuhealth DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dbstuhealth; SOURCE /your/path/dbstuhealth.sql;utf8mb4比老版utf8多覆盖了生僻字和 Emoji,虽然学生档案里大概率不会出现这些字符,但统一用utf8mb4可以避免以后插入特殊字符时直接报错。主键字段我没看到用AUTO_INCREMENT的强制约束,学号是字符串类型,这也是合理的——如果学号前面有零开头的前缀,比如2025001,用整数类型会自动丢零;用VARCHAR虽然占空间稍多,但在这个数据量级别完全无所谓。
2.3 三张表之间的 JOIN 查询怎么写最顺
项目里数据看板和高级查询这两个功能涉及的 SQL 不是简单的单表查询。比如统计某个系别的平均身高体重,你得先 JOIN 学生表和体检表,再按系别分组。我在源码里看到的查询逻辑基本是这种模式:
SELECT s.s_no, s.s_name, s.s_dept, h.h_height, h.h_weight, h.h_date FROM student s LEFT JOIN health h ON s.s_no = h.s_no WHERE s.s_dept = '计算机系' AND h.h_date BETWEEN '2024-09-01' AND '2025-01-15' ORDER BY h.h_date DESC;逻辑说明:LEFT JOIN保证了即使某个学生没有体检记录,他的基本信息也会出现在结果集里,只是在身高体重列显示为 NULL。如果换成INNER JOIN,那么没有体检记录的学生会直接被过滤掉,这在学生管理场景下不是你想要的结果——你想看到的是完整名单,而不是只有体检过的名单。
参数说明:BETWEEN ... AND ...用来圈定日期范围,是体检周期查询最常用的写法。这里的日期字段类型建议用DATE而不是VARCHAR,项目建表时如果写成了VARCHAR也能跑,但你在做日期范围统计的时候就要用字符串比较,一旦格式不统一极容易翻车。我拆这个包的时候注意到它的日期的规范程度还算可以,但你拿到手最好先确认一下日期列存的到底是纯YYYY-MM-DD还是带时间戳的格式。
3. 代码骨架拆解:登录认证、DAO 层和 Swing 界面的联动逻辑
3.1 登录模块不是装饰,它是整个系统的入口守卫
登录注册.jpg那张截图看着朴素,但它的代码逻辑值得抄一下。登录校验的流程是:用户在JTextField输入账号密码,点击登录按钮后触发ActionListener,从文本框取值,发给UserDao的login()方法,方法内部通过PreparedStatement防注入地执行SELECT COUNT(*) FROM user WHERE username = ? AND password = ?,返回布尔值决定是否跳转到主界面。
常见做法是密码存 MD5 摘要而不是明文,这个项目里我看到了注册功能,所以注册的时候至少应该对密码做一次不可逆处理。Java 自带的MessageDigest就能实现 MD5,不需要引第三方 jar:
public static String md5(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(input.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (Exception e) { e.printStackTrace(); return null; } }逻辑说明:MessageDigest.getInstance("MD5")拿到的是 JDK 内置的加密器,对字符串转成的字节数组求摘要后,%02x是把每个字节格式化成两位十六进制的小写字符串。这样即使数据库被别人拖走,明文密码也不会直接暴露。
参数说明:String.format("%02x", b)里02表示至少占两位,x是十六进制。Java 的byte是有符号的,转十六进制时如果不做位运算会出现ffffff80这种怪结果,稳妥写法是b & 0xFF之后再格式化。你在这个项目里看到我这段代码,其实是我把它后端注册逻辑里漏掉的部分补出来的——源码里虽然没有现成的 MD5 工具类,但注册功能已经有了,后续你自己叠加上去很顺。
3.2 DAO 模式加 JTable 数据模型,是 Swing 项目最值得抄的部分
Swing 写业务界面有个痛点:JTable默认的数据模型是DefaultTableModel,但这个模型对数据库查询结果并不友好——你要手动把ResultSet一条条转成Object[]塞进Vector或者二维数组里。项目里各个管理界面的写法几乎统一:DAO 层负责查库返回List<实体>,界面层再把这个 List 转化成表格能用的二维数据。
我看它导出的 Excel 那张截图时注意到的第一件事是:表头跟数据库字段名完全对不上。这是对的,数据库字段s_no不能直接拿去做 Excel 的列名,你要在代码里映射成“学号”。这个思路其实暗合了后面第四点我会提到的“多层数据映射”原理——界面上显示的是业务名,代码里处理的是实体属性,数据库里存的是表的列名。
举个例子,体检模块查出来的是这样一组字段映射:
| 数据库字段 | Java 实体属性 | JTable 列名 |
|---|---|---|
| h_no | healthNo | 体检编号 |
| s_no | studentNo | 学号 |
| h_height | height | 身高/cm |
| h_weight | weight | 体重/kg |
| h_chest | chest | 胸围/cm |
| h_date | healthDate | 体检日期 |
逻辑说明:这三层命名有一个看起很“绕”但实际特别重要的设计意图——不管哪一层改名,其他两层不会全部跟着乱,数据访问层对界面层是屏蔽的。如果你以后接 ORM 框架(MyBatis 或 JPA),这种分层会让你迁移时省非常多事。
3.3 多选项高级查询和批量删除背后的 SQL 拼接技巧
多选项高级查询.png和多选删除.png这两个功能在课设答辩时非常加分,因为它们是“教科书外的真实需求”。多选项查询的难点在于:用户可能只选了系别没选性别,可能只填了身高范围没填日期,你的 SQL 条件个数是动态的。
项目里用的是字符串拼接加 flag 判断的做法,第一步先拼固定前缀WHERE 1=1,后面每个条件用AND接上:
StringBuilder sql = new StringBuilder("SELECT * FROM health WHERE 1=1 "); if (!dept.equals("全部")) { sql.append("AND s_dept = ? "); } if (minHeight > 0) { sql.append("AND h_height >= ? "); } if (maxHeight > 0) { sql.append("AND h_height <= ? "); }逻辑说明:WHERE 1=1不是废话,它是动态条件拼接的锚点。这样不管后面有没有条件追加,SQL 语法都不会因为多出一个裸AND而报错。这种写法在老工程师眼里可能不优雅,但对课设体量的系统来说是最稳的。
参数说明:minHeight和maxHeight用 0 作为“未填写”的哨兵值,是因为身高不可能为 0,这样就能用> 0来判断用户是否真的输入了数值。如果你用空字符串做哨兵,还得处理字符串转 int 的异常,代码会多一截 try-catch。
批量删除的 SQL 要稍微小心一点。常见的做法是拼IN子句,但如果你直接字符串拼接学号,可能会有 SQL 注入风险。稳妥做法是先把学号列表验证一遍(只允许数字和有限符号),再拼接进 SQL:
DELETE FROM health WHERE s_no IN ('2025001', '2025002', '2025003')这个功能在多选删除.png里的交互逻辑是:用户在 JTable 里勾选多行,程序收集所有选中行的学号,生成上面的 SQL 一次执行。不要写循环逐条 DELETE,那样不仅慢,而且遇到外键约束中断时会出现删一半的脏状态。
4. 避坑手册:从 MySQL 连接到 Excel 导入的五个常见翻车点
4.1 MySQL 连接失败:驱动版本和时区设置各占一半原因
现象:程序一启动,登录界面点按钮就报Communications link failure或者The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。原因:这个项目默认连的是 MySQL 5.x,但如果你本机装的是 MySQL 8.x,驱动 jar 不匹配,加上连接串里没配serverTimezone。解决:换 mysql-connector-java 8.x 的驱动,连接串显式加?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8,三个参数一个都不能少。
4.2 中文乱码:问题全在连接串和数据导入两个环节
现象:界面上显示的中文全部变成???或者菱形问号。原因:数据库表是utf8但连接串没指定characterEncoding=utf8,或者dbstuhealth.sql导入时 MySQL 客户端默认用了latin1。解决:先在 MySQL 命令行执行SET NAMES utf8mb4;再SOURCE dbstuhealth.sql;,同时把 JDBC 连接串里的编码参数加上。这个坑我每次配课设项目都会遇到,不是代码问题,是环境问题。
4.3 Excel 导入格式校验:空行是最大元凶
现象:导入体检数据 Excel 时,明明文件内容没问题,程序却报数据越界。原因:Excel 表格末尾往往存在“肉眼看不见”的空行,POI 按行号读取时拿到的 Row 对象不为 null,但里面的 Cell 全是 null,代码没判空就直接调getStringCellValue()。解决:遍历行时先用row == null判行、再用cell == null判单元格,连续空行超过 3 行就 break。项目里导入Excel.png对应的代码逻辑如果有这层判断就不会炸,没有的话你拿到手第一件事就是加上。
4.4 多选删除时外键约束报错
现象:删除一个学生时,提示Cannot delete or update a parent row: a foreign key constraint fails。原因:学生表是主表,体检表通过学号外键关联了它,直接删学生主表记录会被数据库拒绝。解决:要么先删从表记录再删主表,要么建表时在FOREIGN KEY上声明ON DELETE CASCADE。项目初始的 SQL 如果没配级联删除,这就是你大数据量操作时最可能遇到的第一个外键报错。
4.5 Swing 界面卡死:查询别放在事件线程里
现象:点击查询按钮后,整个窗口“假死”,标题栏显示未响应。原因:耗时操作(查库+组装 TableModel)放在了 Swing 事件分发线程里,界面渲染被阻塞。解决:用SwingWorker或者把查询丢进new Thread(() -> {...}).start(),查询完通过SwingUtilities.invokeLater()回到事件线程更新表格。课设答辩时如果评委现场狂点按钮没反应,这基本就是扣分点。项目代码里这部分做得比较粗,你要交高分版本的话这步必须改。
5. 从档案管理到数据分析:把系统升级成“能算数”的体检工具
5.1 用分组查询替代肉眼统计,体检数据才真正有价值
系统虽然能录入和展示体检数据,但我拿到手后发现它缺了一个关键的落点:统计报表。一个学生身高 175cm、体重 70kg,单看没有意义,但如果按系别分组算出平均身高、平均体重,数据就开始说话了。你可以在现有 DAO 层之上加一个统计方法,用 GROUP BY 实现:
SELECT s_dept, COUNT(*) AS student_count, ROUND(AVG(h_height), 2) AS avg_height, ROUND(AVG(h_weight), 2) AS avg_weight, MAX(h_date) AS latest_date FROM health GROUP BY s_dept;逻辑说明:GROUP BY s_dept把同系别的学生聚成一组,AVG和ROUND组合可以输出保留两位小数的均值。这个统计结果可以作为“数据概览”面板的核心数据源,比单纯展示明细列表要有说服力得多。
参数说明:ROUND(x, 2)是 MySQL 的保留小数位函数,不要用 Java 端去四舍五入——数据库这边算好再取,代码更干净。MAX(h_date)在这里表示每个系别最近一次体检日期,方便判断数据新鲜度。
5.2 给 Swing 界面插一个动态折线图面板
源码包里那张动态折线图数据分析.png是我觉得整个项目最有亮点的部分。它展示的不是静态图片,而是基于历史体检数据画出来的趋势折线图。实现路径不复杂:先查询某个学生或者某个系别在过去 N 次体检中的平均体重,得到一组(日期, 数值)序列,然后用 Java 2D 绘图把点画在JPanel上,连线形成折线图。
这里不需要引入 JFreeChart 这种大而全的第三方库,Swing 自带的Graphics2D就够用:
@Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; int baseY = getHeight() - 40; // 底部基线 int leftX = 60; // 左侧留白 int stepX = (getWidth() - leftX - 30) / (points.size() - 1); for (int i = 0; i < points.size() - 1; i++) { int x1 = leftX + i * stepX; int y1 = baseY - (int) (points.get(i).value * scale); int x2 = leftX + (i + 1) * stepX; int y2 = baseY - (int) (points.get(i + 1).value * scale); g2.drawLine(x1, y1, x2, y2); } }逻辑说明:paintComponent里先算出绘图区域的基线和左侧边距,然后按数据点数均匀分布 X 坐标,Y 坐标用(实际值 × 缩放系数)映射到面板像素空间。核心思路就是把“数值区间”映射成“像素区间”,搞懂这个比例关系,折线图就能正常显示。
参数说明:scale的取值很关键——如果写死为 1,身高 180 会直接画到面板外面去。我一般会动态计算:
double maxVal = points.stream().mapToDouble(p -> p.value).max().orElse(1); double scale = (getHeight() - 80) / maxVal;这样不管数据范围怎么变,图形都会自适应地填满面板高度。这一步改完,你就拥有了一张“读数据库实时刷新”的图表,答辩时评委问起来你可以直接演示修改一条体检记录后折线跟着变。
5.3 导出体检报表,日期和表头是两处关键细节
导出 Excel 功能项目本身已经实现了,但如果你要把它变成答辩的加分项,一个值得改造的点是导出文件名动态拼接日期。我看源码里导出文件名如果是写死的,就会有个小问题:连续导两次会把前一份覆盖掉。
String fileName = "体检报表_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".xls";逻辑说明:SimpleDateFormat把系统当前时间格式化成20250701_143000这种不带特殊字符的字符串,拼到文件名后面,保证每次导出都是新文件。这里的HHmmss是 24 小时制时分秒,yyyyMMdd是年月日,连在一起就是一份带时间戳的报表名。
参数说明:如果导出格式是.xls,建议用 HSSFWorkbook;如果是.xlsx,用 XSSFWorkbook。两者不兼容,用错了扩展名 Excel 打开文件会提示损坏。项目里导出Excel.png用的是老版.xls格式,别自己画蛇添足改成.xlsx但又没换 Workbook 类型。
5.4 这些坑我不会再踩第二次
我带学生做课设这几年,把同样的代码在不同机器上跑过很多遍,踩得最多的是数据表和代码字段对不上。这个项目的data.xls里面有几列看起来是给导入功能测试用的样例数据,但它的列顺序和 Java 代码里ImportExcelUtil读取的列索引必须完全一致——差一列,身高和体重就会互换位置,整个体检数据全错。从那以后我每次打开别人的课设项目,第一件事永远是核对三样东西:数据库建表脚本的列顺序、DAO 层实体类的字段定义、Excel 导入模板的表头顺序,三个对齐了才动代码。这个习惯帮我省下了大量“明明代码没问题但结果不对”的排障时间。这份资源本身质量在课设里算中上,但同样的坑它也有,你拿到手先做这一步对齐,后面能少流很多汗。希望帮到你。
本文还有配套的精品资源,点击获取