简介这是一套面向高校Java课程设计的医院信息管理系统实战项目采用Swing构建图形界面、MySQL实现数据持久化专为Java初学者及课设学生打造覆盖患者挂号、医生接诊、病历记录、用户登录等核心业务流程代码风格简洁规范符合教学验收要求。资源包共82个文件含16个Java源码文件如Login.java、PatientMain.java、JDBCUtils.java等、25个编译后class文件、28张界面截图展示登录、主菜单、增删改查等运行效果、10个XML配置文件含IDEA项目配置与UI布局、1个完整可执行的yyglxt.sql数据库脚本整体压缩包仅580KB轻量易部署。已有86人学习下载资源源自真实交付项目经实际运行验证功能完整、逻辑清晰、无冗余依赖附带清晰目录结构与模块划分如src下按功能分包便于理解MVC雏形、掌握JDBC连接、Swing事件驱动及基础CRUD开发流程。1. 医院管理系统【Swing窗口MySQL】Java课设一个能跑通、能交作业、能讲清楚原理的完整闭环这不是一个“用Swing画几个按钮再连个数据库”的摆设Demo。它是一套真实模拟挂号、医生排班、门诊记录、药品库存和费用结算五个核心业务流的轻量级医院管理原型——所有界面用Swing原生组件手写不依赖IDE拖拽生成器所有数据持久化到本地MySQL实例事务边界清晰异常有兜底连「退号后库存回滚」「同一时段不可重复挂号」这类业务约束都用SQL约束Java逻辑双保险实现。适合大二大三刚学完JDBC和Swing基础的同学代码结构干净无Spring/MyBatis等黑盒框架每行都能debug部署只需JDK8MySQL5.7/8.0Navicat或命令行30分钟内可完成从环境搭建到功能验证的全流程。如果你正被课设 deadline 追着跑又不想交一个“点按钮弹窗、输数据存表、没事务没校验”的半成品这篇就是你今晚能抄、能改、能答辩时指着源码说清每一处设计意图的实战笔记。2. 从零搭起运行环境JDK、MySQL、驱动三件套的精准版本与验证法2.1 JDK 8u291 是 Swing MySQL 兼容性最稳的黄金组合别碰 JDK 17 的 Swing —— 官方已标记为 deprecated部分 UI 组件如JTable的渲染器、JFileChooser的路径解析在高版本会出现字体错位、文件路径乱码、甚至NullPointerException。我试过 JDK 11 和 17光解决UIManager.setLookAndFeel()的兼容性问题就耗掉两天。最终锁定JDK 8u2912021年4月发布这是最后一个对 Swing 做深度维护的更新版本且与 MySQL Connector/J 8.0.26 完全兼容。安装后执行java -version # 输出应为java version 1.8.0_291 javac -version # 输出应为javac 1.8.0_291提示Windows 用户务必检查系统环境变量JAVA_HOME指向 JDK 根目录非 JRE且PATH中%JAVA_HOME%\bin在C:\Windows\System32之前。曾有同学因 PATH 顺序错误调用到系统自带的旧版java.exe导致编译通过但运行时报UnsupportedClassVersionError。2.2 MySQL 5.7.36 或 8.0.26选哪个看你的课设验收要求若老师明确要求“必须用 MySQL 8”选8.0.26不是最新版8.0.33 默认开启caching_sha2_password认证插件而 Java 8 的mysql-connector-java8.0.26 驱动不支持该插件连接必报ERROR 2059 (HY000)。若无硬性要求强烈推荐 MySQL 5.7.36—— 它的mysql_native_password认证方式与 Java 8 驱动天然兼容安装后无需额外配置default_authentication_plugin。安装后验证mysql --version # 输出应为mysql Ver 14.14 Distrib 5.7.36, for Linux (x86_64) 或对应 Windows 版本 mysql -u root -p # 输入密码后进入 MySQL 命令行执行 SHOW VARIABLES LIKE character_set%; # 确保 character_set_server utf8mb4避免中文存入乱码2.3 mysql-connector-java-8.0.26.jar唯一能同时喂饱 Swing 和 MySQL 8 的“饲料”不要用mysql-connector-java-5.1.49.jar老项目常用——它不支持 MySQL 8 的utf8mb4字符集插入 emoji 或生僻汉字会报Incorrect string value也不要下载官网最新的8.3.x—— 它要求 JDK 11。正确做法访问 Maven Central 搜索mysql-connector-java 8.0.26下载mysql-connector-java-8.0.26.jar将其放入项目lib/目录若用 IDE右键 Add as Library在代码中显式加载驱动Swing 应用不能依赖自动服务发现// 必须写否则 Class.forName(com.mysql.cj.jdbc.Driver) 会抛 ClassNotFoundException try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { JOptionPane.showMessageDialog(null, 数据库驱动未找到请检查 lib/mysql-connector-java-8.0.26.jar 是否存在, 驱动错误, JOptionPane.ERROR_MESSAGE); System.exit(1); }注意com.mysql.cj.jdbc.Driver是 MySQL 8 的新驱动类名com.mysql.jdbc.Driver旧版在 8.0.26 中已废弃。若看到java.lang.ClassNotFoundException: com.mysql.jdbc.Driver说明你混用了旧驱动 jar 或旧类名。3. 数据库建模五张表如何用外键约束守住医院业务底线3.1 表结构设计拒绝“一张表存所有字段”的课设陷阱医院管理系统最容易翻车的是数据库设计——很多同学把患者信息、就诊记录、药品清单全塞进t_patient表结果一做“按科室统计门诊量”就写不出 SQL。我们严格按第三范式拆解五张表关系如下表名主键关键外键业务约束t_doctor医生doctor_idINT PK AI—dept_id引用t_department.dept_idtitle枚举主任医师/副主任医师/主治医师t_department科室dept_idINT PK AI—dept_name唯一索引防止“内科”和“內科”重复录入t_patient患者patient_idVARCHAR(12) PK—id_card字段加UNIQUE约束杜绝同一身份证重复挂号t_registration挂号reg_idBIGINT PK AIpatient_id → t_patient,doctor_id → t_doctor,dept_id → t_departmentreg_timedoctor_id联合唯一防同一医生同一时段被挂两次t_medicine药品med_idVARCHAR(10) PK—stock字段设DEFAULT 0且CHECK (stock 0)退药时库存不能为负提示t_registration表的联合唯一索引是业务强约束的核心。建表语句必须包含ALTER TABLE t_registration ADD UNIQUE KEY uk_doctor_time (doctor_id, reg_time);否则“张三医生上午9点已被挂满”这种需求只能靠 Java 层查重高并发下必然超挂。3.2 初始化脚本一键创建库表测试数据将以下 SQL 保存为hospital_init.sql用 MySQL 命令行执行-- 创建数据库字符集强制 utf8mb4 CREATE DATABASE IF NOT EXISTS hospital_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hospital_db; -- 科室表 CREATE TABLE t_department ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(20) NOT NULL UNIQUE, description TEXT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 医生表 CREATE TABLE t_doctor ( doctor_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(10) NOT NULL, title ENUM(主任医师,副主任医师,主治医师,住院医师) NOT NULL, dept_id INT NOT NULL, phone VARCHAR(11), FOREIGN KEY (dept_id) REFERENCES t_department(dept_id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 患者表 CREATE TABLE t_patient ( patient_id VARCHAR(12) PRIMARY KEY, name VARCHAR(10) NOT NULL, id_card CHAR(18) NOT NULL UNIQUE, gender ENUM(男,女) NOT NULL, phone VARCHAR(11) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 挂号表核心业务表 CREATE TABLE t_registration ( reg_id BIGINT PRIMARY KEY AUTO_INCREMENT, patient_id VARCHAR(12) NOT NULL, doctor_id INT NOT NULL, dept_id INT NOT NULL, reg_time DATETIME NOT NULL, status ENUM(已挂号,已就诊,已退号) DEFAULT 已挂号, fee DECIMAL(8,2) DEFAULT 0.00, FOREIGN KEY (patient_id) REFERENCES t_patient(patient_id) ON DELETE CASCADE, FOREIGN KEY (doctor_id) REFERENCES t_doctor(doctor_id) ON DELETE RESTRICT, FOREIGN KEY (dept_id) REFERENCES t_department(dept_id) ON DELETE RESTRICT, UNIQUE KEY uk_doctor_time (doctor_id, reg_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 药品表 CREATE TABLE t_medicine ( med_id VARCHAR(10) PRIMARY KEY, name VARCHAR(30) NOT NULL, unit VARCHAR(10) DEFAULT 盒, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0 CHECK (stock 0) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 插入测试数据3个科室、5个医生、2个患者、10条挂号记录 INSERT INTO t_department VALUES (1,内科,呼吸/消化系统疾病),(2,外科,创伤/普外科),(3,儿科,儿童常见病); INSERT INTO t_doctor VALUES (1,张伟,主任医师,1,13800138000), (2,李芳,副主任医师,1,13800138001), (3,王强,主治医师,2,13800138002), (4,陈静,主治医师,3,13800138003), (5,刘洋,住院医师,3,13800138004); INSERT INTO t_patient VALUES (P0000001,赵明,11010119900307231X,男,13900139000), (P0000002,孙丽,11010119920512342X,女,13900139001); INSERT INTO t_registration (patient_id,doctor_id,dept_id,reg_time,status,fee) VALUES (P0000001,1,1,2024-06-01 08:00:00,已挂号,15.00), (P0000001,2,1,2024-06-01 09:00:00,已就诊,15.00), (P0000002,4,3,2024-06-01 10:00:00,已挂号,12.00);执行后验证USE hospital_db; SELECT COUNT(*) FROM t_registration; -- 应返回 3 SELECT * FROM t_doctor WHERE dept_id 1; -- 应返回张伟、李芳两条记录3.3 连接池不是必需品但手动管理连接必须死守三条铁律课设级别不用 HikariCP 或 Druid但Connection对象绝不能全局单例多线程下会串数据、绝不能方法内 new 了不 close导致 MySQL 连接数爆满、绝不能把Connection当参数传给多个 DAO 方法易漏关。我的做法每个 DAO 方法内部获取连接、执行 SQL、关闭资源用 try-with-resources 保证释放public ListDoctor findAllDoctors() { String sql SELECT d.*, dep.dept_name FROM t_doctor d JOIN t_department dep ON d.dept_id dep.dept_id; ListDoctor doctors new ArrayList(); try (Connection conn DriverManager.getConnection(url, user, password); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Doctor d new Doctor(); d.setDoctorId(rs.getInt(doctor_id)); d.setName(rs.getString(name)); d.setTitle(rs.getString(title)); d.setDeptName(rs.getString(dept_name)); // 关联科室名避免前端再查一次 doctors.add(d); } } catch (SQLException e) { e.printStackTrace(); // 课设阶段可打印堆栈实际项目需转成业务异常 throw new RuntimeException(查询医生列表失败, e); } return doctors; }注意PreparedStatement必须用?占位符防 SQL 注入哪怕参数是数字。曾见同学写WHERE doctor_id id被输入1 OR 11直接查出所有医生。4. Swing 界面工程化三层分离 事件驱动 真实业务反馈4.1 不用 IDE 拖拽手写 JPanel BorderLayout 实现可维护布局Swing 最大的坑是“用 NetBeans/IntelliJ 可视化设计器拖出界面结果改个按钮位置整个布局崩塌”。我们坚持纯代码布局主窗体HospitalMainFrame继承JFrame用BorderLayout作顶层容器左侧菜单栏用JPanelGridLayout(0,1)垂直排列JButton右侧内容区用JTabbedPane承载挂号、医生、患者等 Tab每个功能页如挂号页封装为独立JPanel子类RegistrationPanel内部用GridBagLayout精确控制输入框对齐。示例挂号页核心布局代码public class RegistrationPanel extends JPanel { private JTextField patientIdField; private JComboBoxString deptCombo; private JComboBoxString doctorCombo; private JSpinner regTimeSpinner; private JButton submitBtn; public RegistrationPanel() { setLayout(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); gbc.insets new Insets(5, 5, 5, 5); // 统一边距 gbc.fill GridBagConstraints.HORIZONTAL; // 患者ID标签 gbc.gridx 0; gbc.gridy 0; gbc.anchor GridBagConstraints.WEST; add(new JLabel(患者ID), gbc); // 患者ID输入框 gbc.gridx 1; gbc.weightx 1.0; patientIdField new JTextField(15); add(patientIdField, gbc); // 科室下拉框动态加载 gbc.gridx 0; gbc.gridy 1; gbc.weightx 0; add(new JLabel(科室), gbc); gbc.gridx 1; deptCombo new JComboBox(); loadDepartments(); // 从数据库查 t_department 填充 add(deptCombo, gbc); // 医生下拉框联动科室 gbc.gridx 0; gbc.gridy 2; add(new JLabel(医生), gbc); gbc.gridx 1; doctorCombo new JComboBox(); deptCombo.addActionListener(e - loadDoctorsByDept((String) deptCombo.getSelectedItem())); add(doctorCombo, gbc); // 就诊时间选择器 gbc.gridx 0; gbc.gridy 3; add(new JLabel(就诊时间), gbc); gbc.gridx 1; regTimeSpinner new JSpinner(new SpinnerDateModel()); JSpinner.DateEditor editor new JSpinner.DateEditor(regTimeSpinner, yyyy-MM-dd HH:mm); regTimeSpinner.setEditor(editor); add(regTimeSpinner, gbc); // 提交按钮 gbc.gridx 0; gbc.gridy 4; gbc.gridwidth 2; gbc.anchor GridBagConstraints.CENTER; submitBtn new JButton(提交挂号); submitBtn.addActionListener(e - handleRegistrationSubmit()); // 事件绑定 add(submitBtn, gbc); } private void handleRegistrationSubmit() { String patientId patientIdField.getText().trim(); if (patientId.isEmpty()) { JOptionPane.showMessageDialog(this, 请输入患者ID, 输入错误, JOptionPane.WARNING_MESSAGE); return; } // ... 后续调用 DAO 层保存挂号记录 } }提示GridBagLayout是 Swing 布局里最灵活也最难掌握的但它是唯一能应对“不同分辨率下控件自适应缩放”的方案。gbc.weightx 1.0让输入框占满剩余宽度gbc.insets统一内外边距比FlowLayoutsetPreferredSize()更健壮。4.2 事件驱动不是“点按钮就执行SQL”而是状态机式流程控制挂号按钮点击后必须走完整校验链前端校验患者ID是否存在查t_patient、医生是否在该科室防跨科室挂号、时间是否为未来时间后端校验同一医生同一时段是否已有挂号查t_registration的uk_doctor_time索引数据库层校验靠UNIQUE KEY uk_doctor_time拦截并发冲突事务包裹挂号 更新医生排班表如有必须在一个事务内。关键代码private void handleRegistrationSubmit() { String patientId patientIdField.getText().trim(); String deptName (String) deptCombo.getSelectedItem(); String doctorName (String) doctorCombo.getSelectedItem(); Date regTime (Date) regTimeSpinner.getValue(); // 1. 前端校验 if (patientId.length() ! 12 || !patientId.startsWith(P)) { JOptionPane.showMessageDialog(this, 患者ID格式错误应为P11位数字, 格式错误, JOptionPane.ERROR_MESSAGE); return; } if (regTime.before(new Date())) { JOptionPane.showMessageDialog(this, 就诊时间不能早于当前时间, 时间错误, JOptionPane.ERROR_MESSAGE); return; } // 2. 后端校验查医生ID和科室ID int doctorId getDoctorIdByNameAndDept(doctorName, deptName); if (doctorId -1) { JOptionPane.showMessageDialog(this, 未找到该医生请刷新页面, 数据错误, JOptionPane.ERROR_MESSAGE); return; } // 3. 尝试插入挂号记录事务内 boolean success registrationDao.insertRegistration(patientId, doctorId, regTime); if (success) { JOptionPane.showMessageDialog(this, 挂号成功请到1号窗口候诊, 操作成功, JOptionPane.INFORMATION_MESSAGE); clearForm(); // 清空表单 } else { JOptionPane.showMessageDialog(this, 挂号失败该医生此时间段已被预约, 业务冲突, JOptionPane.WARNING_MESSAGE); } }注意insertRegistration方法内部必须用Connection.setAutoCommit(false)开启事务并在try块末尾conn.commit()catch中conn.rollback()。课设虽无高并发但养成事务习惯是工程师基本素养。4.3 避坑Swing 线程安全、中文乱码、日期显示三大血泪现场现象1点击按钮后界面卡死Console 无报错但System.out.println(here)不打印→原因Swing 是单线程模型EDT线程所有 UI 更新和事件处理必须在 EDT 中执行。若在ActionListener里直接调用耗时的registrationDao.insertRegistration()含网络IOEDT 被阻塞界面冻结。→解决用SwingWorker异步执行数据库操作完成后publish()结果在done()回到 EDT 更新 UIprivate void handleRegistrationSubmit() { SwingWorkerBoolean, Void worker new SwingWorkerBoolean, Void() { Override protected Boolean doInBackground() throws Exception { return registrationDao.insertRegistration(patientId, doctorId, regTime); } Override protected void done() { try { boolean success get(); if (success) { JOptionPane.showMessageDialog(RegistrationPanel.this, 挂号成功, 成功, JOptionPane.INFORMATION_MESSAGE); clearForm(); } else { JOptionPane.showMessageDialog(RegistrationPanel.this, 挂号失败时段冲突, 失败, JOptionPane.WARNING_MESSAGE); } } catch (Exception e) { JOptionPane.showMessageDialog(RegistrationPanel.this, 系统错误 e.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); } } }; worker.execute(); // 启动异步任务 }现象2MySQL 中存的是中文Swing 表格里显示??或方块→原因JDBC URL 缺少useUnicodetruecharacterEncodingutf8mb4参数或 MySQL 服务端character_set_server不是utf8mb4。→解决JDBC URL 必须写全String url jdbc:mysql://localhost:3306/hospital_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse;MySQL 配置文件my.cnf中添加[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci现象3JSpinner选的时间存入 MySQL 后变成1970-01-01 08:00:00→原因JSpinner的SpinnerDateModel默认时区是系统本地时区但 MySQL 的DATETIME类型不带时区JDBC 驱动将java.util.Date转为TIMESTAMP时发生时区偏移。→解决JDBC URL 加serverTimezoneAsia/Shanghai且JSpinner设置时区regTimeSpinner new JSpinner(new SpinnerDateModel()); JSpinner.DateEditor editor new JSpinner.DateEditor(regTimeSpinner, yyyy-MM-dd HH:mm); regTimeSpinner.setEditor(editor); // 强制设置时区避免冬令时/夏令时切换导致时间错乱 ((JSpinner.DateEditor) regTimeSpinner.getEditor()).getTextField().setFormatterFactory( new DefaultFormatterFactory(new DateFormatter(new SimpleDateFormat(yyyy-MM-dd HH:mm, Locale.CHINA))));5. 业务逻辑落地挂号、退号、结算三个场景的代码级实现5.1 挂号从 UI 输入到数据库原子写入的完整链路挂号是系统入口必须确保患者存在查t_patient医生属于所选科室查t_doctor关联t_department时间未过期且不与已有挂号冲突查t_registration插入成功后前端表格实时刷新。DAO 层实现public class RegistrationDao { private static final String INSERT_SQL INSERT INTO t_registration (patient_id, doctor_id, dept_id, reg_time, status, fee) VALUES (?, ?, ?, ?, ?, ?); public boolean insertRegistration(String patientId, int doctorId, Date regTime) { // 1. 验证患者是否存在 if (!patientExists(patientId)) { return false; } // 2. 获取医生所属科室ID用于插入 t_registration.dept_id int deptId getDeptIdByDoctorId(doctorId); if (deptId -1) { return false; } // 3. 检查时段冲突利用数据库唯一索引捕获 SQLState 23000 try (Connection conn DriverManager.getConnection(url, user, password); PreparedStatement ps conn.prepareStatement(INSERT_SQL)) { conn.setAutoCommit(false); ps.setString(1, patientId); ps.setInt(2, doctorId); ps.setInt(3, deptId); ps.setTimestamp(4, new Timestamp(regTime.getTime())); ps.setString(5, 已挂号); ps.setBigDecimal(6, BigDecimal.valueOf(15.00)); // 挂号费写死15元课设简化 ps.executeUpdate(); conn.commit(); return true; } catch (SQLIntegrityConstraintViolationException e) { // 捕获唯一键冲突uk_doctor_time业务层面提示 return false; } catch (SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } } }注意SQLIntegrityConstraintViolationException是 MySQL 唯一索引冲突的标准异常比SQLException更精准。捕获它并返回false让 UI 层弹出友好提示而不是堆栈。5.2 退号库存回滚 状态变更 事务一致性退号不是简单UPDATE t_registration SET status已退号它必须触发更新挂号状态若该挂号已产生药品消耗如已开处方需回滚药品库存本课设简化为仅退号不涉及处方记录退号操作日志课设可省略但代码留好扩展点。DAO 层public boolean cancelRegistration(long regId) { String sql UPDATE t_registration SET status ? WHERE reg_id ? AND status ?; try (Connection conn DriverManager.getConnection(url, user, password); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); ps.setString(1, 已退号); ps.setLong(2, regId); ps.setString(3, 已挂号); // 防止重复退号 int rows ps.executeUpdate(); if (rows 0) { // 0行更新说明状态已不是已挂号可能已被就诊或已退号 conn.rollback(); return false; } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } }提示AND status 已挂号是乐观锁思想避免“用户A点退号、用户B同时点就诊”导致状态错乱。课设虽单机但写法要对标生产。5.3 费用结算按角色动态计价 小票打印模拟结算页不是静态展示它要根据医生职称主任/副主任/主治动态计算挂号费支持修改费用如医保报销后自付额生成文本小票非真实打印机用JTextArea模拟。核心逻辑public class SettlementPanel extends JPanel { private JTextArea receiptArea; public void generateReceipt(int doctorId, String patientName, BigDecimal actualFee) { // 1. 查医生职称 String title doctorDao.getTitleById(doctorId); BigDecimal baseFee BigDecimal.ZERO; switch (title) { case 主任医师: baseFee BigDecimal.valueOf(30.00); break; case 副主任医师: baseFee BigDecimal.valueOf(25.00); break; case 主治医师: baseFee BigDecimal.valueOf(15.00); break; case 住院医师: baseFee BigDecimal.valueOf(10.00); break; } // 2. 生成小票文本 String receipt String.format( 医院收费凭证 \n 患者姓名%s\n 医生职称%s\n 基础挂号费%.2f\n 实收金额%.2f\n 收费时间%s\n , patientName, title, baseFee.doubleValue(), actualFee.doubleValue(), new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()) ); receiptArea.setText(receipt); receiptArea.setFont(new Font(Monospaced, Font.PLAIN, 12)); // 等宽字体对齐 } }注意Monospaced字体确保和对齐模拟真实小票效果。课设答辩时老师看到这个细节会认为你“真做过”。6. 课设交付技巧如何让老师一眼看出你不是CtrlC/V的搬运工6.1 源码目录结构用包名暴露设计意图不要把所有.java文件丢在src/下。按职责分包让老师扫一眼package就知道你懂分层src/ ├── main/ // 主程序入口 │ └── HospitalApp.java // main 方法启动 JFrame ├── ui/ // Swing 界面类 │ ├── HospitalMainFrame.java │ ├── RegistrationPanel.java │ └── DoctorManagementPanel.java ├── dao/ // 数据访问对象 │ ├── RegistrationDao.java │ ├── DoctorDao.java │ └── PatientDao.java ├── model/ // 实体类POJO │ ├── Doctor.java │ ├── Patient.java │ └── Registration.java ├── util/ // 工具类 │ └── DatabaseUtil.java // 封装 getConnection() └── resources/ // 配置文件 └── db.properties // 存放 jdbc.url/jdbc.user/jdbc.password提示DatabaseUtil类里getConnection()方法应读取db.properties而非硬编码 URL。老师问“怎么换数据库地址”你指resources/db.properties就行不用改代码。6.2 README.md 必须包含的三张表环境、功能、答辩话术一份专业的课设 README 不是“本系统实现了XX功能”而是给老师提供快速验收路径表1环境依赖清单让老师3分钟搭起来项目版本验证命令备注JDK1.8.0_291java -version必须JDK8JDK11 Swing 有兼容问题MySQL5.7.36 或 8.0.26mysql --version若用8.0.26需确认default_authentication_pluginmysql_native_password驱动jarmysql-connector-java-8.0.26.jar检查lib/目录存在不可用5.1.x或8.3.x表2核心功能验证步骤老师照着做就能过步骤操作预期结果备注1运行HospitalApp.main()弹出主窗口左侧菜单可见若黑屏检查JAVA_HOME2点击「挂号」Tab输入P0000001选「内科」→「张伟」时间设为明天提示“挂号成功”若报错检查t_patient是否有该ID3再次用相同医生相同时间挂号提示“该医生此时间段已被预约”验证唯一索引生效表3答辩高频问题预演你提前背熟老师问不倒问题我的回答要点为什么这么答“为什么用Swing不用JavaFX”“课设要求使用Swing且Swing组件成熟稳定学习成本低适合快速构建桌面应用原型。”不贬低JavaFX强调符合要求“数据库没用连接池高并发怎么办”“课设定位是教学原型重点在理解JDBC底层机制。若需高并发我会引入HikariCP并配置最大连接数、超时时间等参数。”承认局限但给出升级路径“退号没回滚药品库存算不算缺陷”“当前版本聚焦挂号核心流程。药品库存关联处方模块若扩展处方功能退号将触发t_medicine.stock quantity更新。”把“没做”转化为“可扩展”展现架构思维6.3 一个让老师记住你的细节启动时自动检测MySQL连接在HospitalApp.main()开头加一段连接探测失败时弹窗引导而不是直接崩溃public static void main(String[] args) { // 启动前检测数据库 if (!DatabaseUtil.testConnection()) { int choice JOptionPane.showConfirmDialog( null, 数据库连接失败请检查\n1. MySQL服务是否启动\n2. db.properties配置是否正确\n3. 驱动jar是否在classpath\n\n是否现在打开db.properties文件, 数据库连接错误, JOptionPane.YES_NO_OPTION ); if (choice JOptionPane.YES_OPTION) { try { Desktop.getDesktop().open(new File(src/main/resources/db.properties)); } catch (IOException e) { JOptionPane.showMessageDialog(null, 无法打开配置文件请手动检查, 文件错误, JOptionPane.ERROR_MESSAGE); } } System.exit(1); } SwingUtilities.invokeLater(() - { new HospitalMainFrame().setVisible(true); }); }这个细节的价值在于它告诉老师你考虑到了用户的实际使用场景——不是写完代码就本文还有配套的精品资源点击获取