
简介这份资源是面向高校计算机专业学生与Java初学者的一套医院简易挂号管理系统课程设计完整源码基于Java与MySQL开发采用Swing或JavaFX构建图形界面帮助读者理解桌面应用从需求分析到编码测试的完整流程。压缩包共331个文件约3.43MB以html说明页、h与cpp头源文件、class编译文件、png与gif界面素材、ui与fxml布局文件、java源码及jar依赖库为主另含md文档与pro工程配置结构清晰便于按模块查阅。系统覆盖用户登录、号源展示、挂号缴费、记录查询、退号退款与统计打印等核心功能并区分医护与患者角色权限密码加密存储配合异常提示与数据库连接管理保障数据持久化。目前已有752人学习下载适合作为课程设计参考、Java GUI与MySQL综合练习的实战范本。1. 从一张挂号单说起JavaMySQL 做 GUI 挂号系统到底难在哪医院门诊大厅早上七点半挂号窗口前排了三十多人收费员一边翻纸质登记本一边问「哪个科、哪个医生」后面的人不停往前挤。这个场景里真正要解决的其实就三件事号源不能被重复占用、患者信息要能立刻查到、医生排班和挂号记录得对得上。基于 JavaMySQL 实现GUI医院简易挂号管理系统本质就是用桌面客户端把这三件事串起来——Java 负责界面和业务逻辑MySQL 负责把号源、患者、医生、挂号记录持久化GUI 负责让收费员不用记命令也能操作。这套东西适合两类人一类是课程设计或毕设需要交一个能跑、能演示、能讲清楚数据流的完整项目另一类是刚学完 Java 基础、MySQL 安装配置教程也照着走了一遍但没做过「界面连数据库」的开发者。它不追求高并发也不上微服务核心价值在于把 JDBC 连接、事务处理、Swing 事件分发这几块基础打穿。下面按「先能跑、再能改、最后能扛」的顺序拆开讲。2. 环境与库表设计先把 MySQL 跑起来再定四张核心表2.1 JDK、MySQL 与 GUI 工具链的版本选择Java 这边我一般用 JDK 8 或 JDK 17 这两个 LTS 版本JDK 8 兼容性最好网上 Java 环境变量配置详细教程一搜一大把出问题容易查JDK 17 语法新一些但 Swing 本身没变化用哪个都行。MySQL 建议 8.0 系列5.7 也能用但 8.0 默认字符集是 utf8mb4省去后面中文乱码的麻烦。GUI 用 Swing 而不是 JavaFX原因是 Swing 在 JDK 里自带不用额外配模块课程设计场景下依赖越少越稳。连接驱动用 mysql-connector-j版本要和 MySQL 服务端匹配。8.0 的服务端配 8.x 的驱动5.7 的服务端配 5.1.x 的驱动混用会报Unknown system variable或者时区错误。驱动 jar 直接放进项目的 lib 目录IDEA 里右键 Add as Library 即可不要用Class.forName之外的花哨加载方式。提示MySQL 8.0 连接串必须带serverTimezoneAsia/Shanghai否则The server time zone value报错会让你怀疑人生。2.2 四张核心表科室、医生、排班、挂号记录表设计不要一上来就搞十几张简易挂号系统四张表足够跑通主流程。科室表存科室名和位置医生表关联科室排班表决定「哪天哪个医生出诊、还剩多少号」挂号记录表落最终的业务数据。这样拆的好处是号源数量挂在排班行上扣减时锁一行就行不用去统计挂号记录条数。CREATE DATABASE hospital_reg DEFAULT CHARACTER SET utf8mb4; USE hospital_reg; -- 科室表 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL UNIQUE, location VARCHAR(100) ); -- 医生表 CREATE TABLE doctor ( doctor_id INT PRIMARY KEY AUTO_INCREMENT, doctor_name VARCHAR(50) NOT NULL, dept_id INT NOT NULL, title VARCHAR(30), FOREIGN KEY (dept_id) REFERENCES department(dept_id) ); -- 排班表一行代表某医生某天某时段的号源 CREATE TABLE schedule ( schedule_id INT PRIMARY KEY AUTO_INCREMENT, doctor_id INT NOT NULL, work_date DATE NOT NULL, time_slot VARCHAR(20) NOT NULL, -- 上午/下午 total_quota INT NOT NULL DEFAULT 0, left_quota INT NOT NULL DEFAULT 0, FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id), UNIQUE KEY uk_doc_date_slot (doctor_id, work_date, time_slot) ); -- 挂号记录表 CREATE TABLE registration ( reg_id INT PRIMARY KEY AUTO_INCREMENT, patient_name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL, schedule_id INT NOT NULL, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 1, -- 1 有效 0 已退号 FOREIGN KEY (schedule_id) REFERENCES schedule(schedule_id) );left_quota是这套设计的核心字段它和total_quota分开存退号时加回去挂号时减下去。uk_doc_date_slot这个唯一键防止同一天同一时段给同一个医生插两条排班避免号源翻倍。registration表里id_card不设唯一因为同一个人可能给家人挂号但实际业务里可以按需加索引。2.3 初始化数据与连接测试建完表先塞几条测试数据不然界面打开全是空的没法调。INSERT INTO department (dept_name, location) VALUES (内科, 门诊楼2层), (外科, 门诊楼3层), (儿科, 门诊楼1层); INSERT INTO doctor (doctor_name, dept_id, title) VALUES (张伟, 1, 主任医师), (李娜, 1, 副主任医师), (王强, 2, 主治医师); INSERT INTO schedule (doctor_id, work_date, time_slot, total_quota, left_quota) VALUES (1, CURDATE(), 上午, 20, 20), (2, CURDATE(), 下午, 15, 15), (3, CURDATE(), 上午, 10, 10);连接测试写一个最简单的 main 方法能打印出科室列表就说明 JDBC 通了。public class DbTest { public static void main(String[] args) throws Exception { String url jdbc:mysql://localhost:3306/hospital_reg ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; try (Connection conn DriverManager.getConnection(url, root, 你的密码); Statement st conn.createStatement(); ResultSet rs st.executeQuery(SELECT dept_name FROM department)) { while (rs.next()) { System.out.println(rs.getString(dept_name)); } } } }useSSLfalse在本地开发时关掉 SSL 握手省去证书配置characterEncodingutf8保证中文不乱码。如果这一步报Public Key Retrieval is not allowed在连接串后面加allowPublicKeyRetrievaltrue。跑通这一步再往下写界面否则后面报错你分不清是界面问题还是数据库问题。3. 挂号主流程Swing 界面怎么接 JDBC事务怎么保证不超卖3.1 用 Swing 搭出挂号窗口的最小骨架Swing 的布局管理是新手最容易翻车的地方我一般用BorderLayout做外层中间放JTable显示可挂号源底部放操作按钮。不要用绝对定位setBounds换个分辨率界面就散架。下面是一个能跑的最小挂号窗口骨架。public class RegFrame extends JFrame { private JTable scheduleTable; private DefaultTableModel tableModel; private JTextField nameField, idCardField; public RegFrame() { setTitle(医院挂号系统); setSize(800, 500); setDefaultCloseOperation(EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 表头排班ID、医生、科室、时段、剩余号 tableModel new DefaultTableModel( new Object[]{排班ID, 医生, 科室, 时段, 剩余号}, 0); scheduleTable new JTable(tableModel); add(new JScrollPane(scheduleTable), BorderLayout.CENTER); JPanel bottom new JPanel(new FlowLayout()); nameField new JTextField(8); idCardField new JTextField(14); JButton regBtn new JButton(挂号); bottom.add(new JLabel(姓名:)); bottom.add(nameField); bottom.add(new JLabel(身份证:)); bottom.add(idCardField); bottom.add(regBtn); add(bottom, BorderLayout.SOUTH); regBtn.addActionListener(e - doRegister()); loadSchedules(); setVisible(true); } }DefaultTableModel的好处是数据用二维数组或 Vector 直接塞刷新时setRowCount(0)清空再重填。loadSchedules()负责查排班表并填充doRegister()负责取选中行、校验输入、调业务方法。按钮事件用 lambda 写比匿名内部类清爽。3.2 查询可挂号源的 SQL 与界面刷新排班列表要显示医生名和科室名但schedule表里只有 ID所以查询要三表关联。private void loadSchedules() { tableModel.setRowCount(0); String sql SELECT s.schedule_id, d.doctor_name, dp.dept_name, s.time_slot, s.left_quota FROM schedule s JOIN doctor d ON s.doctor_id d.doctor_id JOIN department dp ON d.dept_id dp.dept_id WHERE s.work_date CURDATE() AND s.left_quota 0 ORDER BY s.time_slot, d.doctor_name; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { tableModel.addRow(new Object[]{ rs.getInt(schedule_id), rs.getString(doctor_name), rs.getString(dept_name), rs.getString(time_slot), rs.getInt(left_quota) }); } } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 加载排班失败: ex.getMessage()); } }WHERE s.left_quota 0让号源挂完的排班不显示收费员不会误点。DbUtil是一个简单工具类内部维护连接串和账号每次getConnection()返回新连接。这里没有用连接池简易系统并发低用完即关反而稳定。3.3 挂号事务扣号源和插记录必须绑在一起挂号的核心是「扣减号源」和「插入挂号记录」两步任何一步失败都会导致数据不一致。比如号扣了但记录没插进去患者白排记录插了号没扣号源超卖。所以必须放在一个事务里并且扣减时用left_quota 0做条件更新靠数据库行锁兜底。private void doRegister() { int row scheduleTable.getSelectedRow(); if (row 0) { JOptionPane.showMessageDialog(this, 请先选择排班); return; } String name nameField.getText().trim(); String idCard idCardField.getText().trim(); if (name.isEmpty() || idCard.isEmpty()) { JOptionPane.showMessageDialog(this, 姓名和身份证不能为空); return; } int scheduleId (int) tableModel.getValueAt(row, 0); String deductSql UPDATE schedule SET left_quota left_quota - 1 WHERE schedule_id ? AND left_quota 0; String insertSql INSERT INTO registration (patient_name, id_card, schedule_id) VALUES (?, ?, ?); try (Connection conn DbUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 try (PreparedStatement ps1 conn.prepareStatement(deductSql); PreparedStatement ps2 conn.prepareStatement(insertSql)) { ps1.setInt(1, scheduleId); int affected ps1.executeUpdate(); if (affected 0) { conn.rollback(); JOptionPane.showMessageDialog(this, 号源已挂完请刷新); return; } ps2.setString(1, name); ps2.setString(2, idCard); ps2.setInt(3, scheduleId); ps2.executeUpdate(); conn.commit(); JOptionPane.showMessageDialog(this, 挂号成功); loadSchedules(); // 刷新剩余号 } catch (SQLException ex) { conn.rollback(); JOptionPane.showMessageDialog(this, 挂号失败: ex.getMessage()); } } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 数据库连接失败); } }setAutoCommit(false)之后所有操作都在同一事务里affected 0说明号源已经被别人抢完直接回滚并提示。left_quota 0这个条件写在 UPDATE 的 WHERE 里而不是先查再判断是为了避免「查的时候有号、更新的时候没了」的竞态。这是防超卖最关键的一行很多人写成先 SELECT 再 UPDATE单机测试看不出问题一放到多窗口就翻车。3.4 退号与号源回滚退号就是把registration.status置 0同时把schedule.left_quota加回去同样要事务。public void cancelRegister(int regId, int scheduleId) throws SQLException { String sql1 UPDATE registration SET status 0 WHERE reg_id ? AND status 1; String sql2 UPDATE schedule SET left_quota left_quota 1 WHERE schedule_id ?; try (Connection conn DbUtil.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(sql1); PreparedStatement ps2 conn.prepareStatement(sql2)) { ps1.setInt(1, regId); if (ps1.executeUpdate() 0) { conn.rollback(); return; } ps2.setInt(1, scheduleId); ps2.executeUpdate(); conn.commit(); } catch (SQLException ex) { conn.rollback(); throw ex; } } }status 1这个条件防止重复退号退过一次的记录再退不会生效号源也不会被多加。退号界面可以单独做一个查询窗口按身份证号查有效记录选中后点退号。4. 避坑与排查这五个问题我几乎每次都能遇到4.1 中文乱码从建库到界面全链路排查现象是数据库里存进去的中文变成问号或者界面显示乱码。原因通常有三层建库时没指定utf8mb4、连接串没带characterEncodingutf8、Swing 组件字体不支持中文。解决顺序是先SHOW CREATE DATABASE hospital_reg确认字符集再检查连接串最后在main方法开头设置UIManager.setLookAndFeel为系统外观Windows 下默认字体能正常显示中文。三层都对了还乱码检查 MySQL 服务端的my.ini里character-set-server是不是 utf8mb4。4.2 号源超卖先查后改的经典翻车现象是剩余号显示 1两个人同时点挂号都成功了最后left_quota变成 -1。原因就是先SELECT left_quota判断大于 0再UPDATE两步之间没有锁。解决办法是把判断条件写进 UPDATE 的 WHERE 子句用executeUpdate的返回值判断是否成功如 3.3 节所示。如果业务要求更严格可以在SELECT时加FOR UPDATE但简易系统用条件更新就够了。4.3 界面卡死Swing 事件线程里做耗时操作现象是点挂号按钮后窗口白屏几秒或者点了没反应。原因是 Swing 的事件分发线程EDT里执行了数据库查询网络或磁盘慢的时候界面就冻结。解决办法是耗时操作放到SwingWorker的doInBackground里查询结果在done方法里更新界面。简易系统数据量小如果查询在 100ms 内返回直接写也能接受但一旦连远程数据库就必须改。4.4 驱动版本不匹配时区和 SSL 报错现象是启动时报Unknown system variable query_cache_size或The server time zone value ?D1ú±ê×?ê±?? is unrecognized。原因是驱动版本和服务端版本差太多或者连接串没指定时区。解决方法是 8.0 服务端用 8.x 驱动连接串加serverTimezoneAsia/Shanghai5.7 服务端用 5.1.x 驱动连接串加useUnicodetruecharacterEncodingutf8。SSL 报错就加useSSLfalse本地开发不需要加密连接。4.5 排班日期查询不到CURDATE 与 Java 日期的时差现象是数据库里明明有今天的排班界面查出来是空的。原因是 Java 程序里new java.util.Date()和 MySQL 的CURDATE()在跨时区或跨天时可能不一致尤其是服务器时间设为 UTC 的时候。解决办法是查询条件不要用CURDATE()改成从 Java 传一个java.sql.Date参数进去由程序统一控制「今天」是哪天。这样也方便做「查明天排班」的扩展。5. 进阶技巧把挂号系统做成能演示、能扩展的样子5.1 用 DAO 分层把 SQL 从界面里抽出来前面为了讲清楚流程SQL 都写在界面类里。实际交付前建议抽一层 DAOScheduleDao负责排班查询和号源扣减RegistrationDao负责挂号记录增删查界面类只调方法不写 SQL。这样做的好处是换数据库、加缓存、写单元测试都不用动界面代码。抽的时候注意事务边界一个业务操作涉及多张表时事务控制放在 Service 层DAO 只接收 Connection 参数不要每个 DAO 方法自己开事务。5.2 号源扣减的两种写法对比写法SQL 特征并发安全性适用场景先查后改SELECT 判断再 UPDATE低有竞态单机单用户测试条件更新UPDATE ... WHERE left_quota 0高行锁兜底多窗口、多收费员悲观锁SELECT ... FOR UPDATE高但锁持有时间长号源极少、争抢激烈乐观锁UPDATE ... WHERE left_quota 旧值高需重试读多写少简易挂号系统用条件更新足够代码量最少理解成本最低。如果面试被问到能说清楚这四种的区别和取舍比背八股文管用。5.3 一个验证事务是否生效的小技巧想确认事务真的起作用可以故意在ps2.executeUpdate()后面抛一个RuntimeException观察schedule.left_quota有没有被扣。如果扣了说明事务没生效或者rollback没执行如果没扣说明事务正常。这个测试做完记得把异常删掉。我一般还会在commit前打印conn.getAutoCommit()确认是 false。5.4 从简易系统往真实系统演进的三个方向第一是加登录和权限收费员只能挂号退号管理员才能排班用一张user表加角色字段就能实现。第二是加号别和费用挂号记录里加reg_level和fee字段退号时按规则退费。第三是把 Swing 换成 Web后端用 Spring Boot MyBatis前端用浏览器访问数据库表结构基本不用改。这三个方向里加权限最容易换 Web 工作量最大但核心的号源扣减逻辑是通用的。这套系统我前后写过三遍第一遍把 SQL 写在按钮事件里第二遍抽了 DAO 但事务忘了加第三遍才把条件更新和事务回滚都理顺。最深的教训是号源扣减那行 UPDATE 的 WHERE 条件比界面好不好看重要一百倍。界面丑一点没人投诉号超卖了患者能跟你急。希望帮到你。本文还有配套的精品资源点击获取