
简介这份资源是面向高校学生与Java初学者的医院管理系统课设完整源码包基于Swing窗口与MySQL数据库开发可直接用于课程设计、毕业设计或自学练手。压缩包共82个文件约580KB包含16个java源文件、25个class编译文件、10个xml配置、1个sql数据库脚本以及28张png界面截图覆盖登录注册、医生管理、患者管理、挂号记录查询与增删改查等核心模块源码结构清晰便于对照理解JDBC连接与界面事件处理逻辑。资源来自实际接单交付项目界面风格朴素更贴近学生真实水平运行稳定适合直接参考或二次修改。目前已有86人学习下载可作为Java课设的完整实现方案帮助快速搭建数据库、梳理功能模块并完成答辩演示。1. 医院管理系统用 Swing MySQL 做课设到底在练什么很多同学接到「医院管理系统【Swing窗口MySQL】Java课设」这个题目第一反应是去网上找一份现成源码改个标题就交。但答辩老师随便问一句「你这个挂号模块的并发怎么处理的」「MySQL 事务在哪一层开的」当场就露馅。这个课设真正练的不是界面画得多花哨而是三件事用 Swing 把桌面端的窗口、表格、表单串起来用 JDBC 把 Java 对象和 MySQL 表结构对上用事务保证挂号、收费、退号这些操作不会把数据搞脏。它适合刚学完 Java 基础、面向对象编程、MySQL 常用命令想找一个完整项目把知识点串起来的学生也适合工作几年但没正经写过桌面端 CRUD 的 Java 工程师拿来补课。下面我按自己带学生做课设的节奏把选型、建库、写窗口、避坑、进阶一条线讲清楚你照着能跑出一个能答辩的版本。2. 选型先定死Swing 配 MySQL 的边界在哪2.1 为什么课设阶段 Swing 比 JavaFX 更稳Swing 是 JDK 自带的不用额外配模块javac和java直接能跑这对课设环境是最大的优势。JavaFX 从 JDK 11 开始被剥离出标准库你得单独下 SDK、配--module-path机房电脑或者老师笔记本上大概率没配好演示时直接翻车。Swing 的组件模型虽然老但JTable、JTextField、JButton这套东西足够撑起医院管理系统的所有界面挂号窗口用表单加表格医生排班用下拉框加日期选择收费窗口用表格加合计标签。常见做法是用JFrame做顶层容器JPanel做分区BorderLayout和GridBagLayout混着用别一上来就上GroupLayout那玩意儿手写坐标能把你逼疯。MySQL 这边课设用 5.7 或 8.0 都行但要注意驱动包版本。MySQL 8.0 的驱动类是com.mysql.cj.jdbc.Driver5.7 是com.mysql.jdbc.DriverURL 里 8.0 要加serverTimezoneAsia/Shanghai不然时间字段会报时区错误。热搜里一堆「mysql安装教程8.0」「mysql 5.7.44 安装过程详细」说明大家卡在环境上。我的建议是课设统一用 MySQL 8.0安装时记住 root 密码字符集选utf8mb4排序规则用utf8mb4_general_ci这样中文姓名和地址不会乱码。2.2 三层结构怎么切才不返工课设最怕写到一半发现表结构不够用回头改代码。所以先把三层切清楚界面层Swing 窗口只负责收集输入和展示结果不写 SQL业务层Service负责校验和事务比如挂号时先查该医生该时段是否已满数据层DAO只负责增删改查一个方法对应一条 SQL。这样切的好处是答辩时老师问「事务加在哪」你直接指 Service 层逻辑清晰。具体到包结构我一般这样分src/ com/hospital/ ui/ # Swing 窗口类 service/ # 业务逻辑事务边界 dao/ # 数据库访问 entity/ # 实体类和表一一对应 util/ # DBUtil 连接工具 Main.java # 入口DBUtil用静态方法返回Connection课设阶段不用连接池直接DriverManager.getConnection就行但记得在finally里关掉不然连接数一多 MySQL 会报Too many connections。热搜里「mysql数据库常用命令」「mysql事务处理」这些词落到代码里就是setAutoCommit(false)、commit()、rollback()三个方法后面第 4 章会展开。2.3 建库建表的最小可用脚本医院管理系统的表不用多五张核心表就能撑起答辩科室表、医生表、患者表、挂号表、收费表。下面这份 SQL 可以直接在 MySQL 命令行或 Navicat 里跑注意先建库再建表。-- 建库字符集用 utf8mb4避免中文乱码 CREATE DATABASE hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital_db; -- 科室表 CREATE TABLE department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, dept_desc VARCHAR(200) ); -- 医生表外键关联科室 CREATE TABLE doctor ( doctor_id INT PRIMARY KEY AUTO_INCREMENT, doctor_name VARCHAR(50) NOT NULL, dept_id INT, title VARCHAR(30), reg_fee DECIMAL(8,2) DEFAULT 0.00, FOREIGN KEY (dept_id) REFERENCES department(dept_id) ); -- 患者表 CREATE TABLE patient ( patient_id INT PRIMARY KEY AUTO_INCREMENT, patient_name VARCHAR(50) NOT NULL, gender CHAR(1), phone VARCHAR(20), id_card VARCHAR(18) ); -- 挂号表状态字段控制流程 CREATE TABLE registration ( reg_id INT PRIMARY KEY AUTO_INCREMENT, patient_id INT, doctor_id INT, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0 COMMENT 0待就诊 1已就诊 2已退号, FOREIGN KEY (patient_id) REFERENCES patient(patient_id), FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id) ); -- 收费表 CREATE TABLE charge ( charge_id INT PRIMARY KEY AUTO_INCREMENT, reg_id INT, amount DECIMAL(8,2), pay_time DATETIME, pay_type VARCHAR(20), FOREIGN KEY (reg_id) REFERENCES registration(reg_id) );建表时几个参数要留意AUTO_INCREMENT让主键自增省得手动算 IDDECIMAL(8,2)存金额别用FLOAT否则 0.10.2 会出现精度问题status用TINYINT加注释比VARCHAR省空间也方便判断。热搜里「mysql设置默认值为0」对应的就是DEFAULT 0这种写法。跑完脚本用SHOW TABLES;确认五张表都在再插两条测试数据后面写窗口时才有东西显示。3. 从登录窗口到挂号界面Swing 窗口的落地步骤3.1 登录窗口和数据库校验的最小闭环登录窗口是答辩第一个看到的界面别做太复杂两个输入框加两个按钮就够。关键是把「输入 → 查库 → 判断 → 跳转」这条链路跑通。下面是一个可运行的登录窗口代码用了JFrame、JLabel、JTextField、JPasswordField和JButton。package com.hospital.ui; import javax.swing.*; import java.awt.*; import java.sql.*; public class LoginFrame extends JFrame { private JTextField userField; private JPasswordField passField; public LoginFrame() { setTitle(医院管理系统 - 登录); setSize(350, 220); setLocationRelativeTo(null); // 居中显示 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); gbc.insets new Insets(8, 8, 8, 8); // 组件间距 gbc.gridx 0; gbc.gridy 0; add(new JLabel(用户名), gbc); gbc.gridx 1; userField new JTextField(15); add(userField, gbc); gbc.gridx 0; gbc.gridy 1; add(new JLabel(密 码), gbc); gbc.gridx 1; passField new JPasswordField(15); add(passField, gbc); gbc.gridx 0; gbc.gridy 2; JButton loginBtn new JButton(登录); add(loginBtn, gbc); gbc.gridx 1; JButton resetBtn new JButton(重置); add(resetBtn, gbc); loginBtn.addActionListener(e - doLogin()); resetBtn.addActionListener(e - { userField.setText(); passField.setText(); }); setVisible(true); } private void doLogin() { String user userField.getText().trim(); String pass new String(passField.getPassword()).trim(); if (user.isEmpty() || pass.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } // 课设阶段直接查 admin 表实际项目应加密存储 String sql SELECT * FROM admin WHERE username? AND password?; try (Connection conn com.hospital.util.DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, user); ps.setString(2, pass); ResultSet rs ps.executeQuery(); if (rs.next()) { dispose(); // 关掉登录窗 new MainFrame(); // 打开主窗口 } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, 数据库连接失败 ex.getMessage()); } } public static void main(String[] args) { SwingUtilities.invokeLater(LoginFrame::new); } }这段代码的逻辑说明GridBagLayout负责把标签和输入框排成两列Insets控制间距setLocationRelativeTo(null)让窗口在屏幕居中。doLogin里用PreparedStatement而不是字符串拼接这是防 SQL 注入的基本功答辩时老师大概率会问。try-with-resources自动关闭Connection和PreparedStatement省得写一堆finally。参数方面JTextField(15)里的 15 是列数不是像素实际宽度由布局管理器决定。SwingUtilities.invokeLater保证界面在事件分发线程里创建避免偶发的界面卡死。注意admin表要提前建好并插一条数据INSERT INTO admin(username,password) VALUES(admin,123456);。课设阶段密码明文存能跑通但答辩时最好提一句「生产环境会用 BCrypt 加密」显得你有边界意识。3.2 主窗口用 JTabbedPane 挂载各业务模块登录成功后进主窗口别把所有功能堆在一个界面里。用JTabbedPane分页签挂号、医生管理、患者管理、收费各占一页代码清晰演示也好讲。主窗口的骨架大概这样package com.hospital.ui; import javax.swing.*; public class MainFrame extends JFrame { public MainFrame() { setTitle(医院管理系统); setSize(900, 600); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTabbedPane tabs new JTabbedPane(); tabs.addTab(挂号管理, new RegistrationPanel()); tabs.addTab(医生管理, new DoctorPanel()); tabs.addTab(患者管理, new PatientPanel()); tabs.addTab(收费管理, new ChargePanel()); add(tabs); setVisible(true); } }每个Panel继承JPanel自己负责布局和数据加载。这样切的好处是哪个模块出问题只改那个类不会牵一发动全身。JTabbedPane的addTab第一个参数是页签标题第二个是组件顺序就是显示顺序。窗口大小设 900×600 是因为挂号表格列比较多太小会挤成一团。3.3 挂号面板JTable 绑定查询结果挂号面板是课设的核心要展示医生列表、患者列表还要能提交挂号。JTable配合DefaultTableModel是最省事的做法查出来的ResultSet一行行塞进 model表格自动刷新。package com.hospital.ui; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.sql.*; public class RegistrationPanel extends JPanel { private DefaultTableModel doctorModel; private JTable doctorTable; public RegistrationPanel() { setLayout(new BorderLayout()); // 表头定义列名和数据库字段对应 String[] columns {医生ID, 姓名, 科室, 职称, 挂号费}; doctorModel new DefaultTableModel(columns, 0) { Override public boolean isCellEditable(int row, int col) { return false; // 表格只读防止误改 } }; doctorTable new JTable(doctorModel); add(new JScrollPane(doctorTable), BorderLayout.CENTER); JButton refreshBtn new JButton(刷新医生列表); refreshBtn.addActionListener(e - loadDoctors()); add(refreshBtn, BorderLayout.SOUTH); loadDoctors(); // 初始化时加载一次 } private void loadDoctors() { doctorModel.setRowCount(0); // 清空旧数据 String sql SELECT d.doctor_id, d.doctor_name, dept.dept_name, d.title, d.reg_fee FROM doctor d LEFT JOIN department dept ON d.dept_id dept.dept_id; try (Connection conn com.hospital.util.DBUtil.getConnection(); Statement st conn.createStatement(); ResultSet rs st.executeQuery(sql)) { while (rs.next()) { doctorModel.addRow(new Object[]{ rs.getInt(doctor_id), rs.getString(doctor_name), rs.getString(dept_name), rs.getString(title), rs.getBigDecimal(reg_fee) }); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, 加载医生列表失败); } } }逻辑说明DefaultTableModel的构造函数第二个参数传 0 表示初始无数据addRow逐行追加。重写isCellEditable返回false防止用户直接在表格里改数据这是很多新手忽略的点答辩时表格能编辑会被扣分。LEFT JOIN保证即使医生没分配科室也能查出来dept_name为 null 时表格显示空白。setRowCount(0)在每次刷新前清空不然点一次刷新追加一次数据会重复。参数上JScrollPane必须包住JTable否则表头不会显示这是 Swing 的经典坑。挂号提交的逻辑放在按钮事件里从表格选中行拿到doctor_id从患者下拉框拿到patient_id然后INSERT INTO registration。这一步要开事务因为可能同时要更新医生号源第 4 章细讲。4. 事务、并发和 JDBC 参数把数据搞不脏的关键4.1 挂号操作为什么必须开事务假设一个医生上午只有 20 个号两个患者同时点挂号如果不加事务和锁可能出现第 21 个号也挂上了。课设虽然并发量小但答辩老师爱问这个。正确做法是在 Service 层把「查余号」和「插挂号」放在一个事务里用SELECT ... FOR UPDATE锁住医生行。public boolean register(int patientId, int doctorId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 锁定医生行防止并发超挂 String checkSql SELECT COUNT(*) FROM registration WHERE doctor_id? AND status0 FOR UPDATE; PreparedStatement check conn.prepareStatement(checkSql); check.setInt(1, doctorId); ResultSet rs check.executeQuery(); rs.next(); if (rs.getInt(1) 20) { conn.rollback(); return false; // 号满 } String insertSql INSERT INTO registration(patient_id, doctor_id, status) VALUES(?,?,0); PreparedStatement insert conn.prepareStatement(insertSql); insert.setInt(1, patientId); insert.setInt(2, doctorId); insert.executeUpdate(); conn.commit(); // 两步都成功才提交 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return false; } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException ignored) {} } }逻辑说明setAutoCommit(false)是事务的起点之后所有 SQL 都在同一个事务里。FOR UPDATE是行级锁锁住符合条件的行另一个事务再查同一医生时会等待。commit()提交rollback()回滚。finally里把autoCommit恢复成true再关连接因为连接可能被复用虽然课设没用连接池但养成习惯。参数上status0表示待就诊退号时改成 2这样余号计算才准。热搜里「mysql锁的分类」「mysql事务处理」对应的就是这里行级锁、排他锁、事务隔离级别答辩能说清楚InnoDB默认REPEATABLE READ就够了。4.2 JDBC URL 和驱动参数的常见配法MySQL 8.0 的 JDBC URL 有几个参数必须写对不然连不上或者中文乱码参数作用推荐值useSSL是否用 SSL 连接false课设本地serverTimezone时区设置Asia/ShanghaicharacterEncoding字符编码utf8allowPublicKeyRetrieval允许公钥检索true8.0 首次连接需要完整的 URL 长这样jdbc:mysql://localhost:3306/hospital_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue。DBUtil里加载驱动用Class.forName(com.mysql.cj.jdbc.Driver)8.0 驱动 jar 包要放到项目的lib目录并加到 classpath。热搜里「mysql安装配置教程」「mysql在windows10上怎么安装」说明环境问题占了大头建议装完 MySQL 先用命令行mysql -u root -p能进去再写 Java 代码不然分不清是数据库没起还是代码写错。4.3 退号和收费的状态流转退号不是删记录是把status从 0 改成 2这样历史可查。收费是在charge表插一条记录同时把registration.status改成 1。这两个操作也要放一个事务里保证要么都成功要么都不做。代码结构和上面挂号类似把INSERT和UPDATE包在同一个conn里最后一起commit。这里有个细节UPDATE要判断影响行数executeUpdate()返回 0 说明没更新到得回滚。很多同学写完不检查返回值结果状态没改成功但收费记录插进去了数据就对不上。5. 课设答辩前最容易翻车的五个坑5.1 中文乱码现象是姓名显示问号原因是字符集不统一现象挂号表格里患者姓名显示成???或者插入中文时报Incorrect string value。原因通常是三处字符集不一致MySQL 建库时用了latin1JDBC URL 没写characterEncodingutf8或者 Swing 源文件编码不是 UTF-8。解决建库时明确DEFAULT CHARACTER SET utf8mb4URL 加characterEncodingutf8IDEA 里File → Settings → Editor → File Encodings全部设成 UTF-8。改完重建库重跑别在旧库上改容易残留。5.2 驱动加载失败现象是 ClassNotFoundException原因是 jar 没进 classpath现象运行时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因就一个MySQL 驱动 jar 没加到项目依赖里。解决去 MySQL 官网下mysql-connector-j-8.x.jar在 IDEA 里File → Project Structure → Libraries加进去或者 Maven 项目加依赖mysql:mysql-connector-java:8.0.33。注意 8.0 的驱动类名带cj写com.mysql.jdbc.Driver会警告 deprecated虽然还能跑但不推荐。5.3 窗口卡死现象是点按钮没反应原因是耗时操作占了 EDT现象点「刷新医生列表」后界面卡住几秒按钮变灰。原因是数据库查询直接在事件分发线程EDT里跑Swing 的界面刷新和事件处理都在这个线程一阻塞整个窗口就假死。解决把查库操作放到SwingWorker的doInBackground里查完在done里更新表格。课设数据量小不一定要改但答辩时能说出「耗时操作要离开 EDT」是加分项。5.4 事务没回滚现象是挂号失败但号少了原因是异常被吞现象挂号时如果插registration成功但更新医生号源失败数据不一致。原因是catch块里只打印了异常没rollback或者rollback写在try里被后续异常跳过。解决rollback必须放在catch里且判断conn ! null。另外setAutoCommit(false)之后如果忘了恢复true连接归还连接池时会带着未提交事务这是隐藏 bug。5.5 表格数据重复现象是刷新一次多一批原因是没清空 model现象点两次「刷新」医生列表出现两遍相同数据。原因是loadDoctors里只addRow没清空。解决在查询前调doctorModel.setRowCount(0)。这个坑几乎每个新手都踩血泪经验就是「刷新前先清空」。另外如果用了Vector存数据记得clear()不然内存里也越堆越多。6. 让课设多拿十分的两个进阶技巧第一个技巧是给表格加实时搜索。答辩时老师常问「患者多了怎么快速找」你可以在患者面板顶部加一个JTextField监听DocumentEvent每输入一个字符就过滤TableRowSorter。代码很短TableRowSorterDefaultTableModel sorter new TableRowSorter(patientModel); patientTable.setRowSorter(sorter); searchField.getDocument().addDocumentListener(new DocumentListener() { public void insertUpdate(DocumentEvent e) { filter(); } public void removeUpdate(DocumentEvent e) { filter(); } public void changedUpdate(DocumentEvent e) { filter(); } private void filter() { String text searchField.getText().trim(); sorter.setRowFilter(text.isEmpty() ? null : RowFilter.regexFilter(text)); } });TableRowSorter是 Swing 自带的排序过滤工具regexFilter支持正则输入「张」就能筛出所有姓张的患者。注意DocumentListener三个方法都要实现少一个编译不过。这个功能代码量小但演示效果好老师一看就知道你懂 Swing 的事件模型。第二个技巧是用JFreeChart或纯Graphics2D画一个简单的收费统计柱状图。课设不要求但加上去答辩能讲「数据可视化」。纯Graphics2D不用额外 jar 包在JPanel的paintComponent里根据收费总额画矩形就行。我一般会先查SELECT dept_name, SUM(amount) FROM charge JOIN registration USING(reg_id) JOIN doctor USING(doctor_id) JOIN department USING(dept_id) GROUP BY dept_name把结果存成Map再在paintComponent里遍历画柱。这样既练了多表联查又练了自定义绘制一举两得。最后说个我自己的习惯每次改完 DAO 层的 SQL先在 MySQL 命令行里把这条 SQL 单独跑一遍确认结果对了再写进 Java。这样能把「SQL 写错」和「Java 传参错」分开排查省一半调试时间。课设代码不用追求多优雅但事务边界、字符集、驱动版本这三样必须对不然演示时当场翻车后悔药都没得吃。希望帮到你。本文还有配套的精品资源点击获取