多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Java Swing + MySQL 医院预约挂号系统:课程设计源码全解析

Java Swing + MySQL 医院预约挂号系统:课程设计源码全解析 简介基于Java Swing和MySQL的医院预约挂号系统源码包面向计算机专业初学者、Java课程设计学生以及需要快速搭建桌面管理系统的开发者可帮助理解预约挂号业务从界面交互到数据库落地的完整实现过程。系统采用MVC架构前端以Swing组件构建登录、主视图及医生/用户管理面板后台通过MySQL存储科室、医生、用户与预约数据代码注释详细、命名规范适合阅读学习与二次改造。压缩包共172个文件其中以73个Java源文件与86个class编译文件为主另有6张界面示意图片、4个依赖jar包、2个properties配置文件和1个说明文档整体大小约3.54MB目录结构清晰便于按视图层、控制层、数据访问层分类检索。该资源已有62人学习下载具象展示了用户视图切换监听、医生信息维护、科室查询等模块可帮助读者快速掌握桌面应用开发中的界面事件处理、DAO数据操作与预约挂号流程设计。1. Java Swing MySQL 医院预约挂号系统一份能直接跑的课程设计源码Java Swing MySQL 医院预约挂号系统是计算机专业课程设计和毕业设计里出现频率很高的题目。这个源码包把用户端、医生端、管理端三个角色都做成了独立的 Swing 窗口数据访问层用 DaoImpl 类统一封装整体是教科书标准的 MVC 结构。对你来说它不是一个只能看不能跑的半成品而是一套可以直接导入 IDE、建库、运行、继续改的完整程序。适合三类人正愁课程设计题目的学生、想把桌面应用和 MySQL 配合练熟的开发者、需要快速搭一套管理后台做演示的从业者。拿到手先别急着改界面先把运行链路跑通后面再谈加功能。2. 拆解源码结构从类名还原模块、角色和 MVC 分工2.1 从类名看系统边界三个主视图对应三种操作角色拿到源码先不要一头扎进代码最高效的方式是把类名列出来对着功能猜一遍再进代码验证。这个包的类名写得很直白几乎可以把业务模块直接还原出来。我把主要类按角色做了个映射类名推测角色职责范围UserMainView患者/普通用户端登录后选择科室和医生发起预约挂号查看个人挂号记录DoctorMainView医生端查看当天的出诊安排和对应预约患者列表ManagerMainView管理员端维护医生信息、科室房间、用户账号查看全局挂号数据DoctorAddView管理员新增/编辑医生的子窗口录入姓名、科室、职称、号源费、排班信息DoctorMessageJPanel医生信息展示面板通常嵌在管理端或用户端的 Tab 页里以表格形式展示医生列表UserMessagePanel用户信息展示面板展示用户列表或个人资料由管理员或用户自己触发刷新UserViewChangeListener界面切换监听器监听 Tab 标签页切换、按钮点击联动刷新面板数据DoctorDaoImpl / UserDaoImpl / RoomDaoImpl / DoDaoImpl数据访问实现分别负责医生、用户、科室房间、挂号业务的增删改查这种按角色划分窗口的设计是桌面项目的常见套路好处是每个窗口只负责自己的事件代码看起来思路清晰坏处是如果复用不到位代码量会偏大。课程设计阶段这套逻辑完全够用。你打开 ManagerMainView 之后可以先看它的布局代码找到 JTabbedPane 和 addChangeListener基本上就能把管理端的界面树理清楚。2.2 MVC 落地View、Controller、Model 在代码里长什么样摘要描述里明确写了这是 MVC 架构但 Swing 里的 MVC 和 Web 项目里的 Spring MVC 不是一回事。Swing 的 View 就是 JFrame、JPanel、JTable 这一堆界面组件Model 是实体类加 DAO 数据访问而 Controller 往往不单独设类而是压缩成 ActionListener、ChangeListener 这些监听器对象直接挂在按钮和标签页上。源码里的 UserViewChangeListener 就是其中一个典型代表。public class UserViewChangeListener implements ChangeListener { private UserMainView view; public UserViewChangeListener(UserMainView view) { this.view view; } Override public void stateChanged(ChangeEvent e) { // 获取当前选中的Tab页索引 int index view.getTabbedPane().getSelectedIndex(); if (index 1) { // 切到第二个标签页时重新查库并刷新医生表格 view.refreshDoctorTable(); } } }这段代码的逻辑很典型用户在主界面上点击 Tab 标签切换时Swing 会触发 stateChanged 事件监听器拿到当前选中页的索引然后决定要不要刷新数据。这里的 getSelectedIndex() 返回值对应你在 JTabbedPane 里添加页面的顺序从 0 开始。如果用户端第一个标签是“科室列表”第二个标签是“医生排班”那 index 1 就代表切到了医生排班页此时从数据库重新加载数据保证表格内容是新的。这种用监听器做 Controller 的方式适合中小型桌面应用。它不需要引入额外的框架学习成本低调试的时候直接看事件回调就行。缺点是业务一旦复杂监听器会越挂越多界面类容易膨胀所以后面你扩展功能时不要把全部逻辑都写在 MainView 里建议把真正干活的部分放到 Service 或 DAO 层。2.3 数据访问层四个 DaoImpl 分别管哪些表从类名可以推测这个系统至少有四类核心数据用户、医生、科室房间、挂号记录。RoomDaoImpl 对应科室和房间DoctorDaoImpl 对应医生UserDaoImpl 对应用户DoDaoImpl 则很可能是挂号业务主表的数据入口。拿到源码后你最好先找到数据库脚本看一遍建表语句再读 DAO否则容易对着字段名猜错含义。按照这类系统的常规设计表结构一般是下面这个样子我用标准命名给出一套参考CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_room ( room_id INT PRIMARY KEY AUTO_INCREMENT, room_name VARCHAR(50), dept VARCHAR(50) ); CREATE TABLE t_doctor ( doc_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, dept VARCHAR(50) NOT NULL, title VARCHAR(50), room_id INT, reg_fee DECIMAL(10,2) DEFAULT 0 ); CREATE TABLE t_appointment ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, doc_id INT NOT NULL, appt_date DATE NOT NULL, appt_time VARCHAR(20), status VARCHAR(20) DEFAULT 已预约, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );这段 SQL 有几点说明用户表的 username 加了唯一约束防止重复注册医生表通过 room_id 和房间表建立逻辑关联这里没有加物理外键因为很多课程设计源码为了省事只用逻辑关联只要数据在业务层保持一致就够了挂号记录表用 status 字段区分“已预约、已就诊、已取消”。如果你手里的源码脚本字段名和这个不一样以脚本为准DAO 里的 SQL 会和表结构对齐。看到这里你基本能明白这份资源不是那种把界面截图堆在一起的水货而是真的把三层结构拆开了。3. 把项目跑起来环境、导入、建库、启动四个步骤3.1 环境准备JDK 和 MySQL 版本怎么搭配这种老牌 Swing 项目最稳妥的组合是 JDK 8 MySQL 5.7。JDK 8 对 Swing 的支持最稳定编译出来的 class 文件兼容性也最好。如果你手里只有新版 JDK比如 17 或更高大概率也能跑但要注意两点一是 mysql-connector-java 驱动包需要换成支持新版 JDBC 的版本二是如果编译报错说某些 Swing 内部类找不到多半是模块化系统对内部 API 做了限制这时候退回 JDK 8 最省心。MySQL 5.7 或 8.0 都可以但 8.0 的认证方式和 5.7 不一样见后面避坑章节。3.2 建库和授权先让 MySQL 准备好接待这个项目代码都写好了但数据库还是空的所以第一步是把库建好、把账号建好。我一般习惯单独建一个业务账号而不是直接用 root这样既安全也方便排查问题。打开 MySQL 命令行执行下面的语句mysql -u root -pCREATE DATABASE IF NOT EXISTS hospital_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER hospitallocalhost IDENTIFIED BY hospital123; GRANT ALL PRIVILEGES ON hospital_db.* TO hospitallocalhost; FLUSH PRIVILEGES;第一句创建数据库这里的核心是 charset 和 collate 都指定成 utf8mb4否则后面往表里写中文名字会变成问号。第二句创建了一个名叫 hospital 的账号密码设置为 hospital123。第三句把这个账号的权限限制在 hospital_db 库内部这样项目里的 JDBC 连接串只要用这个账号就够了。最后一句刷新权限让新账号立即生效。然后你需要找到源码里自带的 SQL 脚本一般是 .sql 结尾的文件用 source 命令导入USE hospital_db; SOURCE /你的绝对路径/sql/hospital_db.sql;导入之后用 SHOW TABLES; 看一眼如果能看到 user、doctor、room、appointment 之类的表说明脚本执行成功。3.3 导入 IDE 并修正 classpath接下来打开你常用的 Java IDE用“从已有源码导入项目”的方式把这个包加载进来。这里有个容易翻车的点源码包里如果有 .class 文件而没有对应的 .java 文件IDE 会识别得不够干净。我建议先新建一个普通 Java 项目再把源码目录拖进 src 目录最后把 MySQL 驱动 jar 包放到项目根目录新建的 lib 文件夹里右键 Add to Build Path。这一步卡住的人很多表现是编译时报找不到 com.mysql.cj.jdbc.Driver 或 java.sql 相关类的 NoClassDefFoundError。如果你拿到手的压缩包里有 build 或 out 目录先忽略它不要试图直接运行里面的 class 文件因为本机 classpath 和数据库环境未必一致。正确做法是重新编译一遍源码让 IDE 把所有依赖重新关联好。3.4 启动入口找到带 main 方法的类Swing 项目必然会有一个类带 main 方法通常是某个 MainView 或单独的 MainApp 类。如果不确定就在 IDE 里全局搜索 “public static void main”。下面是一个典型的启动代码结构很多课程设计项目都会写成这样public class MainApp { public static void main(String[] args) { // Swing 组件必须在事件调度线程中创建直接写会存在并发风险 SwingUtilities.invokeLater(() - { // 首选进入用户端主界面 new UserMainView().setVisible(true); }); } }这里的核心是把界面创建动作封装进 SwingUtilities.invokeLater让 Swing 的事件调度线程来执行。如果你直接在主线程里 new JFrame在快速点击按钮和刷新表格时有可能出现组件状态不同步的偶发问题。哪一天你觉得界面“时好时坏”优先检查启动入口有没有包这一层。启动入口选择哪个 View取决于业务入口是用户端还是管理端。如果源码里设置了登录窗口那就从 LoginView 开始如果没有通常默认先进 UserMainView。第一次启动如果弹出一个窗口说明环境链路已经通了。4. 核心业务链路从登录到预约挂号每一步代码长什么样4.1 登录验证UserDaoImpl 里最常见的写法预约挂号的第一步是用户登录。UserDaoImpl 里最核心的方法是按用户名查密码再交给界面层判断。下面这段代码是典型实现public User login(String username, String password) { String sql SELECT * FROM t_user WHERE username ? AND password ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setUserId(rs.getInt(user_id)); user.setUsername(rs.getString(username)); user.setRealName(rs.getString(real_name)); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; }代码逻辑不复杂但有两个细节值得注意。第一个是 PreparedStatement 的 ? 占位符它把参数通过 setString 传进 SQL而不是直接拼字符串能有效避免 SQL 注入。课程设计里如果看到有人用 Statement 加字符串拼接建议顺手改掉这也是答辩时老师喜欢问的点。第二个是 try-with-resources 写法PreparedStatement 和 ResultSet 都实现了 AutoCloseable用 try 块包住之后能自动关闭资源减少连接泄漏。在这个场景里连接对象 conn 是类的成员变量属于长期复用所以没有在 finally 里关闭但你要确保程序退出时能释放连接。4.2 按科室查询医生DoctorDaoImpl 的 PreparedStatement 参数用户登录进来以后需要先选科室再选医生。DoctorDaoImpl 里通常有一个根据科室查医生列表的方法核心代码长这样public ListDoctor findDoctorsByDept(String dept) { String sql SELECT * FROM t_doctor WHERE dept ?; ListDoctor list new ArrayList(); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, dept); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Doctor d new Doctor(); d.setDocId(rs.getInt(doc_id)); d.setName(rs.getString(name)); d.setDept(rs.getString(dept)); d.setTitle(rs.getString(title)); d.setRegFee(rs.getBigDecimal(reg_fee)); list.add(d); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这里 setString(1, dept) 中的 1 是占位符序号第一个 ? 就是 dept。如果你的源码里 SQL 是动态拼接的比如 “WHERE dept ” dept “”那就要小心了一旦科室名里出现单引号整个 SQL 就废了。这种写法也是课程设计答辩的重灾区老师通常会问“你防止 SQL 注入了吗”所以读源码时把这一点看清楚并记住。从代码结构看DoctorDaoImpl 把查询结果一行行转成 Doctor 对象这种手写映射方式虽然繁琐但零依赖初学者能看懂每一步。4.3 生成挂号记录DoDaoImpl 在这条链路上做了什么DoDaoImpl 是这个系统里最像业务核心的类。挂号动作本质上要做三件事查一下医生当前有没有余号、扣掉一个号源、插入一条挂号记录。常见实现思路如下用两个 SQL 串起来public boolean bookAppointment(int userId, int docId, String apptDate) { String updateSql UPDATE t_doctor SET remain remain - 1 WHERE doc_id ? AND remain 0; String insertSql INSERT INTO t_appointment(user_id, doc_id, appt_date, status) VALUES(?, ?, ?, 已预约); try (PreparedStatement ps1 conn.prepareStatement(updateSql)) { ps1.setInt(1, docId); int rows ps1.executeUpdate(); if (rows 0) { return false; // 没有余号扣减失败 } try (PreparedStatement ps2 conn.prepareStatement(insertSql)) { ps2.setInt(1, userId); ps2.setInt(2, docId); ps2.setString(3, apptDate); return ps2.executeUpdate() 0; } } catch (SQLException e) { e.printStackTrace(); return false; } }这段代码是简化后的业务链路实际源码可能把逻辑拆到多个方法里但重点在于 UPDATE 语句里的 remain 0 条件。这个条件保证扣号时不会扣成负数相当于在最底层做了一个简单的库存保护。不过这里有个隐藏问题两个 SQL 不在同一个事务里如果 UPDATE 成功而 INSERT 失败号源会少一个但挂号记录没有产生。课程设计阶段这个问题不明显但等你以后接真实项目一定要把这两步放进同一个事务具体做法在最后一章讲。你现在读代码时先搞清楚 DoDaoImpl 的方法列表再找到这几个 SQL就基本掌握了这个系统的核心流程。5. 避坑指南导入、运行、二次开发最容易翻车的五个地方5.1 现象一数据库连接报 Access denied 或 Communications link failure启动时控制台直接抛 SQLException内容要么是 Access denied for user要么是 Communications link failure。前者的原因十有八九是你在连接串里写的账号密码和 MySQL 里的真实账号不一致或者账号权限没授予到指定库。解决方法是重新执行第二章里的 CREATE USER 和 GRANT 语句或者干脆先把连接串改成 root 账号调试通再换回普通账号。后者 Communications link failure 多半是 MySQL 服务没启动或者连接串里的端口写错默认是 3306检查一下本机 MySQL 是否在运行。5.2 现象二MySQL 8.x 连不上提示 Public Key Retrieval is not allowed用新版 MySQL 跑老项目是常见组合但也最容易出认证问题。MySQL 8 默认的认证插件是 caching_sha2_password老版本驱动在初次连接时无法自动拿公钥就会报 Public Key Retrieval is not allowed。解决办法有两个。第一个是在 JDBC 连接串里加上 allowPublicKeyRetrievaltrue比如jdbc:mysql://localhost:3306/hospital_db?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaicharacterEncodingutf8第二个是把账号的认证插件改成 mysql_native_password执行ALTER USER hospitallocalhost IDENTIFIED WITH mysql_native_password BY hospital123;两种方式都能解决我一般优先用第二个因为它不依赖客户端参数换环境也稳定。5.3 现象三编译或运行时提示 UnsupportedClassVersionError 或 NoClassDefFoundErrorUnsupportedClassVersionError 表示你用来运行程序的 JDK 版本低于源码编译时的版本。比如源码是在 JDK 8 下编译的你拿 JDK 7 跑就会报这个错。解决方法是统一 JDK 版本或者让 IDE 重新编译源码用你当前 JDK 的编译级别生成 class。NoClassDefFoundError 则多半是缺失依赖包最常见的是 mysql-connector-java 没加进 Build Path。检查流程先看项目 Properties 里的 Libraries 是否包含驱动包再看运行时是否把驱动包加进了 classpath。5.4 现象四界面和数据库里的中文全变成问号中文乱码是 Swing MySQL 项目的老问题原因通常是三层编码不一致。第一层数据库和表不是 utf8mb4 编码第二层JDBC 连接串没有指定 characterEncodingutf8第三层IDE 的源文件编译编码不是 UTF-8。我见过最离谱的情况是数据库建错了表也建错了代码文件还是 GBK三层全不统一改谁都救不回来。最省事的做法是重新建库按第二章的 SQL 把库、表全部做成 utf8mb4连接串也带上 characterEncodingutf8再把 IDE 的全局默认编码设为 UTF-8这样中文基本不会乱。5.5 现象五IDE 里大量红叉源码导入后发现 Java 文件找不到包红叉的原因多半是源码包里的目录结构和包名对不上。比如类文件里写着 package com.hospital.view但你在 IDE 里把它放到了 src 根目录而不是 com/hospital/view 目录下编译必然失败。解决办法新建一个 Java 项目后把源码里的 src 目录整体复制到项目的 src 目录不要只拖单个 .java 文件。有些源码包会把 .class 和 .java 混在一起导入前先用压缩工具打开看一眼如果里面有 bin 目录和 src 目录只导入 src忽略 bin。全部处理完后按 CtrlShiftT 搜一下 ManagerMainView能找到类且不报错说明项目结构已经理顺。6. 让它更接近生产连接池、密码加密和号源扣减事务现在你已经能把系统跑通也知道界面、DAO、监听器各自的位置了。如果还想往上走一步我建议从三个点入手。第一把 DbUtil 的每次 getConnection 换成连接池写法常见实现是使用 HikariCP 这类库核心效果是避免频繁创建数据库连接。第二密码不能明文存至少要做一次不可逆散列比如用 BCrypt 或 SHA-256 加盐。第三第四章提到的“扣号源 插入记录”两步必须放进同一个事务保证不会出现号没了但记录没生成的情况。事务代码可以用下面的方式改造Connection conn DbUtil.getConnection(); try { conn.setAutoCommit(false); // 扣减号源 PreparedStatement ps1 conn.prepareStatement(UPDATE t_doctor SET remain remain - 1 WHERE doc_id ? AND remain 0); ps1.setInt(1, docId); if (ps1.executeUpdate() 0) { conn.rollback(); return false; } // 插入挂号记录 PreparedStatement ps2 conn.prepareStatement(INSERT INTO t_appointment(user_id, doc_id, appt_date, status) VALUES(?,?,?,已预约)); ps2.setInt(1, userId); ps2.setInt(2, docId); ps2.setString(3, apptDate); ps2.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { conn.rollback(); e.printStackTrace(); return false; } finally { conn.setAutoCommit(true); conn.close(); }这段代码把两个 SQL 包在同一个事务里setAutoCommit(false) 之后所有操作都暂时不落库只有 commit 才生效中间任何一步失败就 rollback 回滚。这是在真实项目里必须养成的习惯。密码加密的话你可以在 UserDaoImpl 的注册方法里把明文密码先算成摘要再入库登录时同样做一次摘要比对别再用裸密码去和数据库比对了。从那以后我每次拿到这类课程设计源码都会先做一遍环境核对、数据库脚本核对、编码核对三步再谈改功能。这三步看着基础但能挡掉八成的启动翻车问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表