简介:这是一套面向Java初学者的医院病房管理系统课程设计源码包,采用JavaGUI与MySQL实现,适合用作数据库课程设计、大作业或初期项目立项参考。压缩包共45个文件,以27个Java源码为核心,辅以SQL建库脚本、XML配置、项目说明文档及界面预览图,整体大小约1.02MB,便于快速下载与本地调试。源码覆盖实体类、JDBC连接、操作员功能、UI界面等模块,目录结构清晰,README与SQL脚本可帮助理解表结构和运行方式。目前已有106人学习浏览,适合具备一定Java基础并希望练习Swing界面开发与MySQL联调的学习者。资源定位为参考资料而非定制成品,需要自行调试报错并扩展功能,但作为课程设计或工程实训的脚手架,仍有较好的启发性与复用价值。
1. 数据库课设为什么挂在一行 JDBC 上
很多人的数据库课程设计不是死在 SQL 设计上,而是死在「按钮一点,程序白屏」这最后一公里。医院病房管理系统这种课设题,本质上就是一张床位表、一张患者表、一张入住记录表,再加几个增删改查窗口。真正的分水岭在于:实体类、JDBC 连接和 Swing 事件这三层是怎么粘合的。如果连接串写死、结果集不关闭、SQL 拼接用户输入,那运行起来就是行走的报错现场。
这套基于 JavaGUI + Mysql 的病房管理系统源码,文件结构很典型:src 下分 entity、OperatorFunction、JdbcConnect、UI 四个包,外加一个 Hospital.sql 建库脚本。项目难度不大,但结构完整性比大多数只交一个 Main.java 的课设好得多,适合做课程设计、大作业参考。前提是你得会读代码,而不是复制粘贴完就直接点运行。我拆这套代码的时候,先把依赖关系画出来,再一步步走通从登录窗口到数据库返回结果的全链路,这篇文章就按这个思路来讲。
2. 从 Hospital-master 目录反推系统分层结构
拿到压缩包先别急着导入 IDE,先把目录结构读一遍。这个项目的 src 包组织方式非常直白,看似简单,实际上已经把 Java 课设该有的分层做出来了。
2.1 四个包各自的职责边界
先看顶层目录,Hospital.sql 是数据库初始化脚本,README.md 是说明文档,Hospital.iml 是 IDEA 的模块配置。真正的代码在 src 底下,分成四个包,我用表格总结一下:
| 包名 | 典型类 | 职责 |
|---|---|---|
| entity | Patient.java、Bed.java | 对应数据库表的 Java 对象,只保存字段和 getter/setter |
| JdbcConnect | JdbcConnect.java | 负责加载驱动、获取 Connection、提供通用的增删改查方法 |
| OperatorFunction | OperatorFunction.java | 业务操作层,把 UI 动作翻译成对 JDBC 的调用 |
| UI | Login.java、MainFrame.java | Swing 窗口,只负责界面布局和事件监听 |
这个划分对应了 Java 课设里最常见的传统三层结构,entity 是数据模型,OperatorFunction 是业务逻辑,UI 是表现层,JdbcConnect 是数据访问的基础设施。实际开发中可能还会拆 service、dao,但课设里这种「实体 + 连接 + 操作 + 界面」的四包结构,已经足够把SELECT * FROM patient到table.setValueAt()这条链路讲清楚了。
2.2 实体类设计决定数据库表能查得多顺
打开 entity 包下的类,你会发现设计逻辑很朴素,一个类映射一张表。比如患者实体里通常有 patient_id、patient_name、gender、age、bed_id 等字段,床位的实体则包含 bed_id、ward_number、bed_status。字段名基本和数据库列名一一对应,没有引入 MyBatis 或 ORM 框架,全靠手写rs.getInt("patient_id")做映射。
实体类不写满 Lombok,你会看到一堆手写的 getter、setter,这就是课设代码的特点。有个好处,调试时能一眼看到每个字段怎么被填充;坏处是字段一多,代码会显得很冗长。项目代码里我看到实体类保持了 JavaBean 规范,所以即使你想引入 Lombok 简化,也只加注解就够了,改造空间很大。
2.3 一个病房系统的核心表和关系梳理
Hospital.sql 脚本是整栋楼的地基,你不先跑这个脚本,Java 代码无论如何都连不进去。常见的表结构大概是这几张:
-- 病房表 CREATE TABLE ward ( ward_id INT PRIMARY KEY AUTO_INCREMENT, ward_no VARCHAR(20) NOT NULL, ward_type VARCHAR(50), total_beds INT DEFAULT 4, available_beds INT ); -- 床位表 CREATE TABLE bed ( bed_id INT PRIMARY KEY AUTO_INCREMENT, ward_id INT NOT NULL, bed_no VARCHAR(10), bed_status VARCHAR(10) DEFAULT '空闲', -- 空闲/占用/维护 FOREIGN KEY (ward_id) REFERENCES ward(ward_id) ); -- 患者表 CREATE TABLE patient ( patient_id INT PRIMARY KEY AUTO_INCREMENT, patient_name VARCHAR(50) NOT NULL, gender VARCHAR(4), age INT, phone VARCHAR(20), bed_id INT, FOREIGN KEY (bed_id) REFERENCES bed(bed_id) );这三张表构成了医院病房管理最简模型:病房包含多张床位,患者入住时被分配到一个床位,床位状态实时联动。设计的关键在于可用床位数不要单独维护,应该通过统计关联床位状态得出,这样减少数据不一致的风险。如果你的脚本里没有外键约束,建议课设演示前手动补上,因为答辩老师大概率会问你「病房满了怎么办」。
3. JdbcConnect 与 Hospital.sql 联动的完整链路
很多同学拿着课设源码第一步就把 JdbcConnect.java 打开,然后开始改用户名密码。改完直接运行,控制台抛ClassNotFoundException或者Communications link failure。这两个异常的成因完全不同,前者是驱动 jar 没放在正确位置,后者是 MySQL 服务没启动或端口不对。这一章把从 SQL 脚本到 Java 连接对象的全过程拆开讲。
3.1 加载驱动和建立连接的两种写法
课设年代主流写法还是Class.forName("com.mysql.jdbc.Driver")。如果你装的是 MySQL 8.x,这个写法会直接报错,因为 8.x 的驱动类名变成了com.mysql.cj.jdbc.Driver,同时连接串必须带上时区参数。代码里 JdbcConnect 的核心方法大概是下面这个样子:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class JdbcConnect { private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/hospital?" + "useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }静态代码块里加载驱动,是 Java 课设里最常见的方案,优点是只加载一次,所有连接复用这一个加载动作。getConnection()每次调用都会建立一个物理连接,课设规模下完全够用,如果你以后上了生产环境,这里就是需要改造成数据库连接池的位置。关于serverTimezone=Asia/Shanghai,很多人不知道这参数干嘛的,其实就是告诉驱动用中国时区解析 DATETIME,不加的话 MySQL 8.x 连查询都会抛异常。
3.2 通用的增删改方法怎么写最省事
OperatorFunction 包里的操作类一般会封装几个通用方法,executeUpdate(String sql, Object... params)和executeQuery(String sql, Object... params)。用可变参数接收 SQL 里的占位符值,这是课设代码里性价比最高的做法,调用方只需要传 SQL 和参数数组,内部统一用 PreparedStatement 处理。
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class BaseOperator { public int executeUpdate(String sql, Object... params) { try (Connection conn = JdbcConnect.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } public ResultSet executeQuery(String sql, Object... params) { try { Connection conn = JdbcConnect.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } return ps.executeQuery(); } catch (SQLException e) { e.printStackTrace(); return null; } } }try-with-resources语法要求实现 AutoCloseable 接口,Connection 和 PreparedStatement 都满足,所以能用 try 块包住自动关闭。但executeQuery里不能直接关闭连接,因为返回的 ResultSet 在方法外还要继续使用,关闭连接会让结果集失效。常见做法是返回 ResultSet 后由调用方释放,或者更进一步把 ResultSet 映射成实体对象再返回,后一种更安全,推荐在课设中直接把查询结果封装成 List 集合返回。
3.3 SQL 脚本里的初始化数据决定了演示效果
Hospital.sql 除了建表语句,通常还包含INSERT INTO初始化语句。课设演示时,界面上有没有数据全看这一部分。我习惯先把脚本里的插入语句读一遍,确认里面有几张病床、几个科室、是否预置了演示账号。如果只有表结构没有数据,登录窗口将无法通过账号校验,那就需要手工往数据库里补一条管理员记录。
| 初始数据点 | 建议值 | 用途 |
|---|---|---|
| 管理员账号 | admin / admin123 | 登录窗口验证 |
| 病房示例数据 | 3 间病房,每间 4 张床 | 主界面床位分布展示 |
| 患者示例数据 | 2~3 条记录 | 列表查询和编辑演示 |
| 床位状态 | 至少一个「占用」 | 演示床位分配与释放差异 |
如果脚本里没有管理员表,很多课设是直接在登录窗口写死admin字符串判断。这种做法不是不行,但答辩时容易挨问「为什么不走数据库验证」,建议还是加一张 user 表。
4. Swing 界面与 OperatorFunction 的联动实现
UI 包是课设最容易被人忽视的部分,实际上它直接决定了答辩效果。这个项目的 UI 包里有 login 背景图、操作面板和主窗口,BackGrounds 类承担了窗口背景图绘制。从源码结构看,每个窗口类都通过事件监听器绑定按钮动作,监听器内部再调用 OperatorFunction 里的方法。
4.1 窗口初始化和登录验证的前端逻辑
登录窗口一般包含用户名输入框、密码输入框和登录按钮,点击登录时触发actionPerformed方法。下面是登录验证的逻辑骨架,和大多数 Swing 课设的写法一致:
import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class LoginFrame extends JFrame { private JTextField userText; private JPasswordField passwordField; private OperatorFunction operator; public LoginFrame() { operator = new OperatorFunction(); setTitle("医院病房管理系统登录"); setSize(400, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); initUI(); } private void initUI() { // 布局和控件初始化省略 loginButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String username = userText.getText().trim(); String password = new String(passwordField.getPassword()).trim(); if (operator.checkLogin(username, password)) { new MainFrame().setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(null, "用户名或密码错误"); } } }); } }new String(passwordField.getPassword())是把字符数组还原成字符串,注意不能用passwordField.getText(),因为 JPasswordField 的 getText 已被标记为过时。登录成功后把当前登录窗口dispose()释放掉,同时打开主窗口,两个窗口不能叠加保留,否则 Alt+Tab 一切换就会出现多个旧窗口。这里的主窗口创建可以传当前登录用户的角色信息,方便主界面按角色显示不同功能按钮。
4.2 主界面表格数据刷新的具体写法
主界面的核心是一张病房或患者信息表,一般用 Swing 的JTable组件承载。数据刷新链路是:点击「查询」按钮 → OperatorFunction 调用executeQuery→ 返回结果集 → 把每一行数据填充进DefaultTableModel。整个逻辑看着简单,但最容易翻车的地方是表格列的标题和 SQL 查询列的对应关系。
import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.Vector; public class PatientTablePanel { private DefaultTableModel tableModel; public void refreshPatientData() { Vector<String> columnNames = new Vector<>(); columnNames.add("患者ID"); columnNames.add("姓名"); columnNames.add("性别"); columnNames.add("年龄"); Vector<Vector<Object>> data = new Vector<>(); String sql = "SELECT patient_id, patient_name, gender, age, phone FROM patient"; try (ResultSet rs = new BaseOperator().executeQuery(sql)) { while (rs.next()) { Vector<Object> row = new Vector<>(); row.add(rs.getInt("patient_id")); row.add(rs.getString("patient_name")); row.add(rs.getString("gender")); row.add(rs.getInt("age")); row.add(rs.getString("phone")); data.add(row); } tableModel.setDataVector(data, columnNames); } catch (SQLException e) { e.printStackTrace(); } } }这是课设中非常标准的「查询按钮 + 表格刷新」写法。Vector数据结构虽然老,但DefaultTableModel的构造方法只认它,你换ArrayList反而会编译报错。手动逐列 add 数据虽然繁琐,但胜在直观,答辩时你可以指着代码说「查询结果集里每个字段对应表格的一列」。很多新手会把tableModel.setDataVector放在 while 循环里,这样每次循环都重设一次数据模型,界面会闪,数据量大了还会卡死,必须在循环外面统一设置。
4.3 床位分配操作的前后关联逻辑
床位分配是整个病房管理系统里最有业务味道的功能。基本流程是:从患者列表选中一个患者,点击「分配床位」,弹出一个对话框显示所有空闲床位,选一个床位后更新患者表的 bed_id 字段,同时把床位状态改为「占用」。这两个更新操作必须在同一个事务里完成,否则会出现患者有床位但床位还是空闲的数据错乱。
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class AssignBedService { public boolean assignBed(int patientId, int bedId) { String updatePatient = "UPDATE patient SET bed_id = ? WHERE patient_id = ?"; String updateBed = "UPDATE bed SET bed_status = '占用' WHERE bed_id = ? AND bed_status = '空闲'"; try (Connection conn = JdbcConnect.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps1 = conn.prepareStatement(updatePatient); PreparedStatement ps2 = conn.prepareStatement(updateBed)) { ps1.setInt(1, bedId); ps1.setInt(2, patientId); int affected1 = ps1.executeUpdate(); ps2.setInt(1, bedId); int affected2 = ps2.executeUpdate(); if (affected1 > 0 && affected2 > 0) { conn.commit(); return true; } else { conn.rollback(); return false; } } catch (SQLException e) { conn.rollback(); throw e; } } catch (SQLException e) { e.printStackTrace(); return false; } } }这段代码的关键在第 6 行到第 12 行:先关闭自动提交,然后依次执行两个 UPDATE,只有两次受影响行数都大于 0 才提交。第二个 UPDATE 的 WHERE 条件带上了bed_status = '空闲',这是防止两个患者被同时分配到同一张床的兜底逻辑。affected2为 0 意味着床位不是空闲状态,此时回滚整个事务,患者表也不会被改。这个细节能体现你对并发控制有没有概念,答辩加分项。课设里事务的写法是很多人的盲区,如果你时间有限,只把这一段看懂背熟就值回票价了。
5. 课程设计答辩与部署中最容易踩的六个坑
一套课设源码光跑通还不够,换个环境就崩才是最常见的事。无论是自己练手,还是帮同学看问题,下面这些排查路径基本覆盖了八成报错场景,重点说排错思路。
5.1 驱动版本与 MySQL 版本不匹配
ClassNotFoundException: com.mysql.jdbc.Driver是最高频的报错。先检查lib目录下有没有 mysql-connector jar,再确定版本。MySQL 5.x 对应 5.1.x 的驱动,MySQL 8.x 必须用 8.0.x 的驱动,连接串也改成com.mysql.cj.jdbc.Driver。在命令行执行mysql --version确认数据库版本,不要凭直觉判断。
5.2 时区和 SSL 配置导致建立连接超时
The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,看到这行乱码不要慌,这是 MySQL 8.x 的时区配置问题。解决方案是在 JDBC URL 末尾追加serverTimezone=Asia/Shanghai。如果只是本地课设不涉及敏感数据,可以额外加useSSL=false跳过 SSL 握手,减少首次连接耗时。
5.3 控制台中文乱码
这个问题几乎每个课设都会碰到。JVM 内部默认 UTF-8,MySQL 表默认可能是 latin1。需要注意三个环节:连接串里的characterEncoding=utf8、MySQL 建库语句里的DEFAULT CHARSET=utf8、Idea 的 File Encoding 设置为 UTF-8。三者缺一,轻则查询结果中文乱码,重则插入数据直接报Incorrect string value。推荐统一贴一下这三处的配置:
| 检查点 | 正确配置 |
|---|---|
| IDEA 全局编码 | Settings -> File Encodings:全部 UTF-8 |
| 连接串 | useUnicode=true&characterEncoding=utf8 |
| 建库语句 | CREATE DATABASE hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci |
注意utf8mb4和utf8的区别,前者是后者的超集,可以完整存入 emoji 和生僻字,课设阶段直接用utf8mb4一步到位。
5.4 端口占用导致 SQLNonTransientConnectionException
MySQL 默认 3306 端口。如果本机装了多个 MySQL 实例,或者上一次程序异常退出没释放端口,你的 Java 程序会连接到一个没有 MySQL 监听的端口。命令行执行netstat -ano | findstr 3306查看占用情况,如果不需要某个服务就杀掉对应 PID。更稳妥的做法是在连接串里写明端口号,不要依赖默认值。
5.5 ResultSet 提前关闭导致表格数据为空
很多课设的executeQuery方法长这个样子:在方法内部开启连接、执行 SQL、拿到结果集、关闭连接、返回结果集。问题就在这里,你拿到的是已经被标记关闭的 ResultSet,数据读不出来。解决方案是不要在查询方法内关闭 Connection,把连接生命周期交给上层管理,或者直接改成把结果映射为 List 返回。我个人推荐直接映射为 List,因为这样就不用手动管理 ResultSet 的生命周期了。
5.6 多条 SQL 操作没有事务包裹导致数据不对
这是最容易和「功能正常」混淆的地方。就像前面床位分配的例子,患者表和床位表的状态是联动的,一个成功一个失败并不会报错,但数据对不上。检查项目代码中凡是有「先改 A 后改 B」逻辑的地方,是否都开启了setAutoCommit(false)。很多课程设计的代码里直接省略了事务控制,没并发时看不出毛病,一旦多条记录同时操作就会出现不可复现的脏数据,这类问题最难查,建议你拿到代码的第一步就把所有多写操作排查一遍。
整套源码里的资源文件、包的命名和注释都保留了原项目结构,照着这一层的思路去读,你会发现在不引入框架的情况下,JDBC 的手写事务控制、JavaBean 实体映射、MVC 界面分离,其实和框架的原理是相通的。把这些小细节研究透,比单纯跑通一个系统有意思得多。
本文还有配套的精品资源,点击获取