☰
JavaSwing+MySQL医院预约挂号系统课设:核心设计与避坑指南
2026/10/6 16:17:59 网站建设 项目流程

简介:这是一份基于Java Swing与MySQL的医院预约挂号系统源码,采用MVC架构,适合Java初学者、课程设计及毕业设计参考。项目主要面向需要完成桌面应用开发实训的学生或需快速搭建预约挂号后台管理的开发者,可帮助理解Swing界面编程、数据库连接与业务分层设计。源码共172个文件,含86个class、73个java源文件、6个png图片、4个jar依赖包、2个properties配置及1个md说明,压缩包仅3.54MB,结构完整便于导入IDE。代码注释详细,界面与后台数据逻辑分离,涉及用户视图切换监听器、医生/科室/房间信息管理面板等典型模块,适合按模块拆解学习。目前已有62人浏览学习,通过MySQL支撑数据持久化、Swing实现直观操作,能帮助读者掌握从界面事件到数据库增删改查的完整链路,是较好的实战练习素材。

1. 医院预约挂号系统,为什么我建议你用JavaSwing+Mysql做课程设计

很多同学第一次接触“(源码)基于JavaSwing和Mysql的医院预约挂号系统.zip”这个名字,第一反应是“又是一个老掉牙的课设”。但真把这个压缩包打开、把代码跑起来之后,你会发现它其实是一套把 Java 图形界面、关系型数据库、业务流程状态机串在一起的完整样本。JavaSwing 负责做桌面窗口,Mysql 负责把医生、科室、患者、预约记录持久化,两者通过 JDBC 连接。如果你正在做 Java 课程设计,或者想在公司内部快速搭一个单机版的预约演示系统,这套技术组合比 Spring Boot + Vue 轻太多,而且所有逻辑都摊在眼前,改起来没黑匣子。接下来我从业务建模、界面搭建、数据库对接、踩坑排查一路讲到怎么给它加分,目标是让新手照着复现,让熟手能避坑。

2. 先搞懂业务模型:预约挂号系统要拆成哪几个表、哪几条状态流

2.1 核心实体:科室、医生、患者、管理员,缺一张表都会卡壳

医院预约挂号的本质是“在合适的时间窗里,把医生的剩余号源分配给患者”。一个最小可用系统至少要覆盖四类人:患者自己、坐诊的医生、管理排班的科室,以及系统管理员。很多从网上下载的源码只做了医生表和预约表,结果登录时想区分普通用户和管理员都没办法,最后只能用同一个入口硬编码判断。

我一般建议把用户体系拆成两张表:user存登录账号和角色,patient存患者的基本信息。这样做的原因是预约列表需要展示患者姓名、手机号,而登录只需要账号密码和角色标记,混在一张表里会导致查询时频繁用LIKE '%张%'去模糊匹配,慢且容易误伤。角色字段用tinyint就行,0 管理员、1 患者、2 医生,不要用字符串。

科室表很简单,id、name、description。医生表要关联科室,还要包含职称、简介、排班时段。预约表是整个系统的核心,我把它和“号源”合并成一张表,不再单独设计号源库存表。原因后面讲事务时会提到,合并表能大幅减少并发预约时的死锁概率。

2.2 预约状态机:从可约、已约到已就诊,状态流转别只靠一个字段

预约不是“插入一条记录就完事”。一个患者操作预约后,状态要经历:可约(还没有人预约)→ 已预约(患者交了订单但未就诊)→ 已就诊(医生确认过)。如果医生停诊,还要有“已取消”状态。

很多入门源码只用status字段存一个数字,界面上用 if-else 去判断状态。这样做短期能跑,但一旦你要做“取消预约后释放号源”“医生点击开始看诊后自动改状态”这两个功能,代码就会散乱。正确做法是定义一个状态常量类,把流转规则集中管理:

public class AppointmentStatus { public static final int AVAILABLE = 0; public static final int BOOKED = 1; public static final int COMPLETED = 2; public static final int CANCELED = 3; }

然后每次状态变更都走同一个方法:

public void changeStatus(int appointmentId, int fromStatus, int toStatus, Connection conn) { String sql = "UPDATE appointment SET status = ? WHERE id = ? AND status = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, toStatus); ps.setInt(2, appointmentId); ps.setInt(3, fromStatus); int rows = ps.executeUpdate(); if (rows == 0) { throw new RuntimeException("状态已变更,请刷新界面"); } } catch (SQLException e) { throw new RuntimeException("更新预约状态失败:" + e.getMessage()); } }

这里用WHERE status = ?作为乐观锁条件,避免两个窗口同时把一个号源约掉。

2.3 建表脚本:一版可以跑起来的 Mysql 结构

下面这份 SQL 是我在自己课设项目里实际用过的,改成你们学校的库名就行。注意要设置utf8mb4字符集,否则后面中文乱码会折腾你一整晚。

CREATE DATABASE hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital_db; CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(64) NOT NULL, `role` TINYINT NOT NULL DEFAULT 1 COMMENT '0-admin 1-patient 2-doctor' ) ENGINE=InnoDB; CREATE TABLE department ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `description` VARCHAR(255) ) ENGINE=InnoDB; CREATE TABLE doctor ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT, `dept_id` INT, `name` VARCHAR(50) NOT NULL, `title` VARCHAR(30), `introduction` VARCHAR(500), FOREIGN KEY (`user_id`) REFERENCES `user`(`id`), FOREIGN KEY (`dept_id`) REFERENCES `department`(`id`) ) ENGINE=InnoDB; CREATE TABLE patient ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT, `name` VARCHAR(50) NOT NULL, `phone` VARCHAR(20), `id_card` VARCHAR(30), FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ) ENGINE=InnoDB; CREATE TABLE appointment ( `id` INT PRIMARY KEY AUTO_INCREMENT, `doctor_id` INT NOT NULL, `patient_id` INT, `appointment_date` DATE NOT NULL, `time_slot` VARCHAR(20) NOT NULL COMMENT '如 08:00-08:30', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0可约 1已约 2已完成 3已取消', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_doctor_date_slot` (`doctor_id`, `appointment_date`, `time_slot`) ) ENGINE=InnoDB;

要点是appointment表里那个唯一键uk_doctor_date_slot。doctor_id+ 日期 + 时段一旦重复,数据库层面就拒绝插入,这就是防超卖的底裤。没有这个唯一键,你再怎么写并发控制都有漏洞。

3. 从零搭建 JavaSwing 界面:登录、主页、挂号三步走

3.1 项目结构怎么摆:用两个包把数据访问和界面分开

网上下载的源码大部分是“一个包塞几十个类”。我拿到压缩包后的第一件事是先看包结构。哪怕别人写得很乱,我也会自己重排一遍。推荐按view、dao、model、util四层拆,Swing 组件全部放view,JDBC 代码全部放dao,实体类放model。

src ├── com.xxx.hospital.view // JFrame、JPanel、监听器 ├── com.xxx.hospital.dao // DbUtil、UserDao、AppointmentDao ├── com.xxx.hospital.model // User、Doctor、Appointment └── com.xxx.hospital.util // 常量、状态枚举、时间格式化

好处有两个:一是以后想把 Swing 换成本地 JavaFX 或者做 Web 接口,只需要替换view层;二是写dao的时候不用关心按钮长什么样,用 Mysql 命令去测 SQL 逻辑,测通了再往上挂界面。

3.2 登录窗口与数据库连接的固定写法

Swing 的老套路是JFrame上放JLabel、JTextField、JPasswordField、JButton,然后给按钮加ActionListener。这里最需要抄作业的是数据库连接工具类。我见过很多同学把连接参数写死在每个 DAO 里,改一次密码要改五个文件。用下面这个DbUtil一次性解决:

public class DbUtil { private static final String URL = "jdbc:mysql://localhost:3306/hospital_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError("Mysql驱动加载失败,检查lib目录下是否有mysql-connector-java.jar"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r != null) { try { r.close(); } catch (Exception e) { e.printStackTrace(); } } } } }

注意连接串里的serverTimezone=Asia/Shanghai不能删。Mysql 8.x 驱动要求显式指定时区,否则会报Server returns invalid timezone。useSSL=false是关掉加密连接,本地开发不用 SSL,开着反而因为证书问题连不上。

登录按钮的校验逻辑就是从user表按用户名查记录,比对密码,然后根据role跳转对应窗口。密码比对不要直接拿字符串equals,至少用 MessageDigest 做一次 SHA-256,不然你把项目交给老师时,库里明文密码就是社死现场。

3.3 挂号主界面:用 JTable 展示号源,用状态字段控制按钮

挂号界面的核心控件是JTable。你需要先查出一个医生在指定日期的所有号源,例如上午四个时段、下午四个时段,每个时段对应一条appointment记录。查询 SQL 建议这么写:

SELECT a.id, d.name AS doctor_name, dep.name AS dept_name, a.appointment_date, a.time_slot, a.status FROM appointment a JOIN doctor d ON a.doctor_id = d.id JOIN department dep ON d.dept_id = dep.id WHERE a.appointment_date = ? ORDER BY a.time_slot ASC;

ORDER BY a.time_slot ASC是一种基于字符串的排序,因为time_slot格式是"08:00-08:30",字典序和真实时间顺序一致,所以能直接排。如果你把时段的起始时间改成"8:00"这种不带前导零的格式,排序就会错乱——这是 Mysql 排序里容易翻车的小细节。

把查询结果放进DefaultTableModel,然后给“预约”按钮写监听器。这时关键一步:预约操作既要插入预约记录,又要修改状态。千万别写完 INSERT 再 UPDATE,因为一旦第二步失败,数据就变成“有记录但没状态”。我用一个事务方法把两步包起来:

public void bookAppointment(int appointmentId, int patientId) { String updateSql = "UPDATE appointment SET patient_id = ?, status = 1 WHERE id = ? AND status = 0"; try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(updateSql)) { conn.setAutoCommit(false); ps.setInt(1, patientId); ps.setInt(2, appointmentId); int rows = ps.executeUpdate(); if (rows == 0) { conn.rollback(); throw new RuntimeException("该号源已被抢占,请刷新后重试"); } conn.commit(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("预约失败:" + e.getMessage()); } }

这里只做了一次 UPDATE,没有单独 INSERT,因为appointment表里已经预置了每个时段的可约记录。这种“预先生成号源”的模式省掉了插入动作,事务里也少一条语句,排错更容易。如果你的源码做的是插入式预约,那要保证 INSERT 前查一次是否已有同一医生、日期、时段的记录,并且把查询和插入放在同一个事务中。

4. Mysql 与 JavaSwing 对接的坑:驱动、时区、中文乱码、事务提交

4.1 驱动版本与连接串的玄学,90% 的报错都出在这里

JavaSwing 项目里 Mysql 报错,最常见的一句话是No suitable driver found。原因要么是mysql-connector-java.jar没放进lib目录,要么是驱动名写错了。Mysql 5.x 的驱动类是com.mysql.jdbc.Driver,Mysql 8.x 改成com.mysql.cj.jdbc.Driver。你的 Mysql 是 8.0,连接串里还写 5.x 的useSSL=true,就会看到一连串 SSL 警告。

我给你的建议是先确认本机 Mysql 版本。Windows 上在命令提示符里执行mysql -V,看到8.0.xx就按 8.x 的配置写。mysql-connector-java这个 jar 包的版本最好也选 8.x,避免驱动类加载失败。

另外,Mysql 8.x 驱动默认要求连接串带上allowPublicKeyRetrieval=true,尤其是在用了caching_sha2_password认证插件时。如果报Public Key Retrieval is not allowed,就是少了这个参数。完整推荐连接串:

jdbc:mysql://localhost:3306/hospital_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8

4.2 中文乱码的根源:不是代码的问题,是三层字符集没对齐

很多同学把系统跑起来后,数据库里汉字正常,但 Swing 界面上显示的是问号。或者界面正常,但写入数据库变成问号。这个问题我一共碰到过四次,每次都是下面三个原因之一。

第一,数据库/表不是utf8mb4。建库时如果用了默认的latin1,你怎么设置连接都没用。检查办法是执行SHOW CREATE DATABASE hospital_db;,看到DEFAULT CHARACTER SET latin1就重建成 utf8mb4。

第二,连接串里没有characterEncoding=utf8。这一条专门负责告诉 Mysql “我用 UTF-8 来编码你的 SQL 字符串”,如果缺失,JDBC 驱动会用系统默认编码,在中文 Windows 上通常是 GBK,两边对不上就乱码。

第三,JFrame 的字体问题。Swing 默认字体在中文环境下可能缺字,表现为界面上的文字是方框。这个不算程序逻辑错误,在代码启动入口设置一下全局字体:

UIManager.put("Label.font", new Font("Microsoft YaHei", Font.PLAIN, 14));

建议所有JButton、JTable、JTextField都统一用Microsoft YaHei,至少在 Windows 上不会出方块。

4.3 事务与并发预约:一个按钮连点两下的惨案

预约系统的并发量虽然低,但“连点两下”这种人为并发反而最常见。你的“预约”按钮如果没有在监听器里禁用,用户点了第一次弹窗没反应,又点第二次,就会产生两条相同时段记录。解决方式有两种。

第一种,从 UI 层禁止重复点击:第一次点击后立即button.setEnabled(false),等事务结束后再恢复。第二种,数据库层加上唯一键(前面表结构里已经加了),配合事务里的 UPDATE 语句中带WHERE status = 0,只有第一条能修改成功,第二条影响行数为 0,回滚并提示“号源已占用”。

不要小看这个设计,很多网上下载的源码没有这个保护,预约主键还是用AUTO_INCREMENT,两张表没有唯一约束,演示时翻车概率极大。

5. 避坑/常见问题排查:从导入源码到跑通系统的 5 条血泪经验

5.1 现象:Eclipse 或 IDEA 导入后项目红叉一片

原因:压缩包里的项目很可能没有.classpath和.project文件,或者.idea目录是别人机器上的绝对路径。缺少这些文件,IDE 不会把src识别为源码根目录,更不会自动加载lib下的 jar。

解决:在 IDEA 里丢掉导入操作,直接在磁盘上建一个空项目,然后把src目录复制进来,右键Mark Directory as Sources Root。接着把lib里的mysql-connector-java.jar通过File -> Project Structure -> Libraries手动添加。这一步虽然绕开了一键导入,但能避免无数路径报错。

5.2 现象:连接数据库报 SSL 或时区错误

原因:Mysql 版本和驱动版本不一致,连接串参数少了。最典型的是The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,这是 JDBC 驱动不认识系统时区。

解决:在连接串后面加serverTimezone=Asia/Shanghai,并把useSSL=false加上。如果报的是Public Key Retrieval is not allowed,再加allowPublicKeyRetrieval=true。按照 4.1 那条完整连接串改,一次全解决。

5.3 现象:写入 Mysql 的中文字符全部变成 ???

原因:连接串没有characterEncoding=utf8,并且数据库默认字符集可能是latin1。

解决:确认建库语句是DEFAULT CHARACTER SET utf8mb4;确认连接串带characterEncoding=utf8;在 JDBC 驱动加载后,执行一条SET NAMES utf8mb4;兜底。这里有个执行顺序问题——SET NAMES必须在任何查询之前执行,所以我一般放在DbUtil.getConnection()里一起做。

5.4 现象:点击预约按钮,程序没报错,但列表状态没变

原因:最常见的是没有conn.commit()。你执行了 UPDATE,但事务没有提交,数据改动还停留在当前会话里。另一个可能是 UPDATE 语句里WHERE条件没匹配到任何行,影响行数是 0,代码里也没有检查rows == 0。

解决:用conn.setAutoCommit(false)时,记得在try块结束前显式conn.commit()。同时检查rows == 0的情况,输出一句明确的提示,而不是默默跳过。

5.5 现象:窗口拉大后布局乱成一团,控件挤在一起

原因:Swing 默认布局是FlowLayout,它不会根据窗口大小自动伸展。号源列表一旦记录多,滚动条不出现,界面就崩了。

解决:把主面板的布局设为BorderLayout,中间放JScrollPane包裹的JTable,底部放操作按钮。这只是基本操作,另外要设置JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF),硬限制列宽,否则时间列会被拉得很宽。

6. 让课程设计从 60 分到 85 分:加号源校验、操作日志、导出功能

如果你的系统能跑通,其实已经超过一半的网上下载源码了,因为很多人连数据库都连不上。但想拿高分,还要加三个老师一眼能看到的点。

第一个是号源池校验。做一个“可约”统计查询,窗口打开时显示今天剩余号源总数,预约成功后实时刷新。这个功能只需要一个COUNT(*)加一个 Swing 的JLabel刷新,但能体现你理解了状态管理。

String sql = "SELECT COUNT(*) FROM appointment WHERE appointment_date = ? AND status = 0";

第二个是操作日志。给预约、取消、完成三个动作各写一行日志到log表。注意log表不要外键关联user表,直接存username字符串,防止删除用户时日志被级联删掉。没有哪家医院会删预约日志。

第三个是导出功能。用JFileChooser选择保存路径,把JTable的内容按 CSV 格式写文件。CSV 一行就是一条记录,用英文逗号分割,中文用 BOM 头防止 Excel 打开乱码。这个功能在桌面应用里是痛点和亮点,很多工作场景都靠它交差。

我在做这类系统时,最后养成的一个习惯是:所有 DAO 方法尽量只接受普通参数,不接受Connection参数。因为一旦把连接传进函数,事务边界就变得含糊,你会在一个方法里commit,然后在另一个地方close,出了异常连回滚都找不到入口。把事务逻辑统一放在 Service 层,用try (Connection conn = DbUtil.getConnection())包住,再交给 DAO,才能保证“要么全成功,要么全回滚”。

如果你照着这个思路把网上下载的源码从“能跑”改到“能说清楚”,答辩的时候就会发现,自己不再是背代码,而是在解释几个关键设计决定。希望这篇笔记帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询