ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java+MySQL挂号系统课程设计:Swing+JDBC实现与事务避坑指南

Java+MySQL挂号系统课程设计:Swing+JDBC实现与事务避坑指南 简介这是一套面向高校计算机专业学生与Java初学者的课程设计级项目源码基于Java与MySQL实现医院简易挂号管理系统采用Swing或JavaFX构建图形界面覆盖登录验证、挂号缴费、记录查询、退号退款及统计打印等核心流程并区分医护与患者角色权限密码加密存储配合异常提示与数据库持久化设计适合作为课程设计参考或桌面应用练手案例。压缩包共331个文件约3.43MB以144个html、45个h、39个class、37个png及13个cpp等为主另含7个java源文件、7个ui界面文件、2个fxml与2个jar涵盖界面资源、编译产物与依赖库便于直接运行与二次修改。目前已有752人学习下载。项目完整呈现需求分析、系统设计、编码实现到测试优化的开发链路读者可借此理解GUI开发、数据库表结构设计与SQL增删改查、用户认证与权限控制等知识点并参考其目录组织与异常处理思路快速搭建自己的挂号管理应用。1. 从一份 class 文件清单说起这个 JavaMySQL 挂号系统到底能不能跑拿到这个包的时候我第一眼看到的不是源码目录而是一串.class文件清单SqlUse.class、PatientControl.class、DateChooser.class、Doctor.class还有一堆PatientControl$4.class、PatientControl$6.class这样的匿名内部类产物。做过 Java 课程设计的人都知道这说明项目是编译过的源码大概率也在包里只是被 class 文件盖住了风头。编号 100011071典型的课程设计资源编号Java MySQL GUI 三件套医院简易挂号管理系统。它解决的是一个非常具体的问题用桌面窗口把挂号、退号、查号、统计打印这条链路串起来数据落到 MySQL角色分医护和患者。适合谁正在做 Java 课程设计、需要一份能跑通、能改、能讲清楚数据库设计的学生以及想拿它当 Swing JDBC 练手模板的初学者。不适合谁想直接上生产的人——这是教学项目不是医院信息系统。下面我按「先跑起来、再看结构、最后改得动」的顺序拆。2. 环境与数据库把 MySQL 和 JDBC 这条线先接通2.1 为什么是 Swing JDBC而不是 JavaFX 框架这个项目的技术选型很「课程设计」GUI 用 Swing数据库访问用原生 JDBC没有 Spring、没有 MyBatis。很多人第一反应是「太老了」但从复现角度讲这恰恰是优点。Swing 是 JDK 自带的不需要额外配 Maven 依赖就能编译JDBC 直连 MySQLSQL 语句全写在SqlUse这类工具类里逻辑透明出问题能一眼看到是哪条 SQL 挂了。如果换成 JavaFX你还得处理模块化配置换成 MyBatisXML 映射文件一多新手反而找不到北。从 class 清单能反推出结构SqlUse是数据库工具类负责连接和增删改查PatientControl是患者端的控制逻辑里面那些$4、$6、$7、$9是匿名内部类通常是按钮的ActionListener或者线程DateChooser是日期选择控件挂号肯定要选日期Doctor是医生实体类。这个分层不复杂但够用。2.2 建库建表三张核心表先立住数据库设计是这个项目的骨架。按摘要描述至少要有用户表、号源表、挂号记录表。我一般会先建一个hospital_reg库字符集用utf8mb4避免中文乱码。下面是我按这个项目场景补全的建表语句字段名和类型贴合挂号业务-- 创建数据库字符集必须指定否则中文姓名会变问号 CREATE DATABASE hospital_reg DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital_reg; -- 用户表区分医护和患者密码不存明文 CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存 MD5 或 SHA-256 摘要 real_name VARCHAR(32), role TINYINT NOT NULL DEFAULT 0, -- 0患者 1医护 phone VARCHAR(20) ); -- 号源表号种、医生、日期、总号数、剩余号数、挂号费 CREATE TABLE t_source ( source_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(32) NOT NULL, -- 科室 doctor_name VARCHAR(32) NOT NULL, visit_date DATE NOT NULL, total_num INT NOT NULL DEFAULT 0, left_num INT NOT NULL DEFAULT 0, fee DECIMAL(8,2) NOT NULL DEFAULT 0.00 ); -- 挂号记录表谁、挂了哪个号、什么时候挂的、状态 CREATE TABLE t_reg ( reg_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, source_id INT NOT NULL, reg_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, status TINYINT NOT NULL DEFAULT 1, -- 1已挂号 0已退号 FOREIGN KEY (user_id) REFERENCES t_user(user_id), FOREIGN KEY (source_id) REFERENCES t_source(source_id) );逻辑说明t_user的role字段是权限控制的根登录后拿这个值决定进哪个界面。t_source的left_num是退号时回滚的关键挂号减一、退号加一必须和t_reg的插入/更新放在同一个事务里。t_reg的status用软删除而不是物理删除退号后记录还在统计报表才能算清楚当天挂了多少、退了多少。参数说明password给到 64 位是为了放 SHA-256 的十六进制摘要fee用DECIMAL(8,2)而不是FLOAT金额绝不能用浮点visit_date用DATE而不是字符串方便按日期范围查统计。2.3 JDBC 连接SqlUse里最容易翻车的地方SqlUse.class是这个项目的数据库入口。原生 JDBC 连接 MySQL 8.x 和 5.7 的驱动类名、URL 参数都不一样这是新手第一个坑。下面是我一般会写的连接工具类骨架import java.sql.*; public class SqlUse { // MySQL 8.x 用 com.mysql.cj.jdbc.Driver5.7 用 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // useSSLfalse 关掉 SSL 警告serverTimezone 必须设否则时间差 8 小时 private static final String URL jdbc:mysql://localhost:3306/hospital_reg?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD 你的密码; static { try { Class.forName(DRIVER); // 加载驱动只做一次 } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 统一的关闭方法顺序不能反ResultSet - Statement - Connection public static void close(ResultSet rs, Statement st, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (st ! null) st.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }逻辑说明静态代码块里Class.forName只执行一次避免每次连接都反射加载。getConn每次返回新连接课程设计规模下够用不用上连接池。close的关闭顺序必须是 ResultSet 先、Statement 次、Connection 最后反了会抛异常。参数说明serverTimezoneAsia/Shanghai是 MySQL 8.x 的硬性要求不写会报The server time zone value is unrecognizeduseSSLfalse只是消掉控制台警告本地开发无所谓characterEncodingutf8配合库的utf8mb4保证中文姓名和科室名不乱码。提示如果你的 MySQL 是 5.7驱动类名换成com.mysql.jdbc.DriverURL 里可以不加serverTimezone但加了也不报错。驱动 jar 包mysql-connector-java必须放进项目的 classpath否则ClassNotFoundException直接教你做人。3. 挂号与退号事务、并发和left_num的加减逻辑3.1 挂号不是一条 INSERT是「查余号 扣减 写记录」很多人写挂号直接INSERT INTO t_reg就完事结果号源扣没扣、扣得对不对全凭运气。正确的挂号是一个事务里的三步先查left_num是否大于 0再UPDATE t_source SET left_num left_num - 1最后INSERT INTO t_reg。任何一步失败就回滚。下面是我按这个项目场景写的挂号核心方法public boolean register(int userId, int sourceId) { Connection conn null; PreparedStatement psCheck null, psUpdate null, psInsert null; ResultSet rs null; try { conn SqlUse.getConn(); conn.setAutoCommit(false); // 开启事务关掉自动提交 // 1. 查余号用 FOR UPDATE 锁住这一行防止并发超挂 psCheck conn.prepareStatement( SELECT left_num FROM t_source WHERE source_id ? FOR UPDATE); psCheck.setInt(1, sourceId); rs psCheck.executeQuery(); if (!rs.next() || rs.getInt(left_num) 0) { conn.rollback(); return false; // 号已满回滚 } // 2. 扣减余号 psUpdate conn.prepareStatement( UPDATE t_source SET left_num left_num - 1 WHERE source_id ?); psUpdate.setInt(1, sourceId); psUpdate.executeUpdate(); // 3. 写挂号记录 psInsert conn.prepareStatement( INSERT INTO t_reg(user_id, source_id, status) VALUES(?, ?, 1)); psInsert.setInt(1, userId); psInsert.setInt(2, sourceId); psInsert.executeUpdate(); conn.commit(); // 三步都成功才提交 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } SqlUse.close(rs, psCheck, null); SqlUse.close(null, psUpdate, null); SqlUse.close(null, psInsert, conn); } }逻辑说明setAutoCommit(false)是事务的开关不关掉自动提交每条 SQL 都会立刻落库回滚就失效了。FOR UPDATE是行级锁锁住当前号源行防止两个患者同时读到left_num1然后都扣成功导致超挂。commit只在三步全成功后调用任何异常走rollback。参数说明sourceId是号源主键userId是当前登录患者的主键。FOR UPDATE必须在事务里才有意义自动提交模式下它锁完立刻释放等于没锁。课程设计单机跑并发不高但加上这一句是习惯面试被问到「怎么防超卖」也能答。3.2 退号回滚余号 软删除记录退号是挂号的逆操作但有个细节不能物理删除t_reg记录否则统计报表里「今日退号数」就没了。正确做法是把status改成 0同时把left_num加回去。同样要放在一个事务里public boolean cancel(int regId) { Connection conn null; PreparedStatement psGet null, psUpdReg null, psUpdSrc null; ResultSet rs null; try { conn SqlUse.getConn(); conn.setAutoCommit(false); // 1. 查出这条挂号对应的号源且必须是「已挂号」状态 psGet conn.prepareStatement( SELECT source_id FROM t_reg WHERE reg_id ? AND status 1); psGet.setInt(1, regId); rs psGet.executeQuery(); if (!rs.next()) { conn.rollback(); return false; } int sourceId rs.getInt(source_id); // 2. 记录置为已退号 psUpdReg conn.prepareStatement( UPDATE t_reg SET status 0 WHERE reg_id ?); psUpdReg.setInt(1, regId); psUpdReg.executeUpdate(); // 3. 余号加回 psUpdSrc conn.prepareStatement( UPDATE t_source SET left_num left_num 1 WHERE source_id ?); psUpdSrc.setInt(1, sourceId); psUpdSrc.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } SqlUse.close(rs, psGet, null); SqlUse.close(null, psUpdReg, null); SqlUse.close(null, psUpdSrc, conn); } }逻辑说明第一步的status 1条件很关键防止同一条记录被退两次。第二步和第三步的顺序无所谓但必须在同一事务里否则可能出现「记录退了但号没加回」的脏数据。参数说明regId是挂号记录主键从查询界面选中某条记录后传进来。退号成功后界面要刷新余号显示否则用户看到的是旧数据。3.3 权限控制role字段怎么落到界面上登录成功后t_user.role决定进哪个主界面。患者只能看自己的t_reg医护能看全部。这个逻辑在PatientControl里应该体现为查询条件的不同// 患者查询只查自己的记录 String sqlPatient SELECT r.reg_id, s.dept_name, s.doctor_name, s.visit_date, s.fee FROM t_reg r JOIN t_source s ON r.source_id s.source_id WHERE r.user_id ? AND r.status 1; // 医护查询查全部可按日期过滤 String sqlDoctor SELECT r.reg_id, u.real_name, s.dept_name, s.doctor_name, s.visit_date, s.fee FROM t_reg r JOIN t_source s ON r.source_id s.source_id JOIN t_user u ON r.user_id u.user_id WHERE r.status 1 AND s.visit_date ?;逻辑说明两条 SQL 的差别就是WHERE条件患者版多一个user_id过滤医护版多一个日期过滤。用JOIN而不是分两次查减少数据库往返。参数说明user_id从登录会话里取不能从界面输入否则患者能查别人记录。visit_date是医护端统计用的日期参数。4. 避坑与排查class 文件、乱码、驱动版本这三座大山4.1 现象双击运行报NoClassDefFoundError找不到SqlUse原因class 文件清单里SqlUse.class是编译产物但如果你只拷了部分 class或者包名目录结构不对JVM 按包路径找类就找不到。Java 的包名和文件夹层级必须严格对应com.hospital.dao.SqlUse必须放在com/hospital/dao/下。解决优先找源码.java文件重新编译而不是直接跑 class。如果只有 class用javap -p SqlUse.class反编译看包名然后按包名建目录。命令行编译用javac -d . *.java-d .会按包名自动生成目录结构。4.2 现象中文姓名、科室名在界面上显示成问号或方块原因三个环节任一没设对都会乱码——数据库字符集、JDBC URL 的characterEncoding、Swing 组件的字体。最常见的是数据库建库时用了默认latin1。解决先SHOW CREATE DATABASE hospital_reg;看字符集不是utf8mb4就ALTER DATABASE hospital_reg CHARACTER SET utf8mb4;。JDBC URL 里加characterEncodingutf8。Swing 的JLabel、JTextField如果还乱检查 IDE 的编码设置是不是 UTF-8Eclipse 默认可能是 GBK。4.3 现象连接 MySQL 报Unknown system variable query_cache_size或时区错误原因驱动版本和 MySQL 版本不匹配。MySQL 8.x 移除了query_cache_size老驱动5.1.x连 8.x 会报这个反过来新驱动连 5.7 如果不写serverTimezone会报时区未识别。解决MySQL 8.x 用mysql-connector-java 8.0.xURL 带serverTimezoneAsia/ShanghaiMySQL 5.7 用5.1.x驱动URL 可以不带时区。驱动 jar 和数据库版本对不上是血泪经验里排第一的翻车点。4.4 现象挂号时提示成功但号源余数没变原因UPDATE t_source和INSERT t_reg没放在同一事务或者setAutoCommit(false)写了但某条 SQL 抛异常被 catch 吞掉没走 rollback 也没走 commit。解决在catch块里必须显式conn.rollback()并且把异常打印出来不要空 catch。调试时可以在每条 SQL 后打印executeUpdate()的返回值看影响行数是不是 1。4.5 现象退号后余号加回但同一条记录能退两次原因退号 SQL 没加status 1条件或者加了但查询和更新之间没有事务隔离两次点击都读到了status 1。解决UPDATE t_reg SET status 0 WHERE reg_id ? AND status 1然后看executeUpdate()返回值等于 0 说明已经被退过直接返回失败。界面上的退号按钮点击后要禁用防止连点。5. 统计打印与二次开发把DateChooser用起来让报表能落地统计打印是这个项目里最容易被忽略、但答辩时最容易被问的部分。摘要里提到「每日挂号数量、收入等可打印报表」落到代码上就是按visit_date分组聚合。下面这条 SQL 是统计某天各科室的挂号数和收入SELECT s.dept_name, COUNT(r.reg_id) AS reg_count, SUM(CASE WHEN r.status 1 THEN s.fee ELSE 0 END) AS income FROM t_reg r JOIN t_source s ON r.source_id s.source_id WHERE s.visit_date ? GROUP BY s.dept_name ORDER BY reg_count DESC;逻辑说明COUNT(r.reg_id)统计挂号笔数SUM(CASE WHEN...)只累加未退号的费用退号的记录不计入收入。GROUP BY dept_name按科室汇总ORDER BY让报表按热度排序。参数说明?是日期参数从DateChooser控件取。DateChooser是 class 清单里的日期选择类通常返回java.util.Date传给 JDBC 前要转成java.sql.Date用new java.sql.Date(date.getTime())。打印功能用 Swing 的Printable接口实现把JTable的内容画到Graphics上。我一般会先table.print()试一下能弹出打印对话框就说明通了。如果打印出来是空白检查JTable是否已经setSize并且数据模型非空。二次开发方向有三个一是把SqlUse里的硬编码连接参数抽到db.properties文件改配置不用重编译二是给t_source加一个dept_id外键把科室单独建表方便扩展多科室三是把密码的 MD5 换成加盐 SHA-256答辩时能多讲一个安全点。从那以后我每次拿到这种课程设计包都强制先做三件事找源码而不是跑 class、核对驱动和数据库版本、把事务边界画在纸上再写代码。这三步走完后面基本不会翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表