ARTICLE DETAIL

资讯详情

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

Java医院管理系统源码实战:挂号门诊药房住院数据一致性

Java医院管理系统源码实战:挂号门诊药房住院数据一致性 简介这是一套基于Java开发的医院管理系统完整源码与数据库面向计算机专业学生、Java开发者及医疗信息化学习者可用于课程设计、毕业设计或企业级项目练手。系统覆盖挂号、门诊、药房、住院等核心业务模块并涉及权限控制、接口集成与系统测试等环节帮助读者理解医疗行业业务逻辑与Java技术栈的落地方式。压缩包共1236个文件约8.91MB以791个java源码为主体辅以106个xml配置、84个js与37个html前端页面、17个yml及13个dockerfile部署文件另有7个sql脚本和多个properties、ftl模板结构完整便于二次开发。目前已有681人学习下载。通过研读源码读者可掌握Spring Boot、MyBatis等框架在真实项目中的分层设计与数据库表结构积累从挂号并发控制到药房库存管理的实战经验是提升Java工程能力与医疗业务认知的实用参考。1. 从一份 Java 医院管理系统源码说起挂号、门诊、药房、住院到底怎么串起来很多做 Java 课程设计或者接私活的同学第一次拿到「医院管理系统源码数据库」这类资源时最容易犯的错是直接双击启动类看到登录页就以为跑通了。结果点进挂号模块发现科室列表是空的再点门诊医生排班对不上药房库存扣减和住院押金流水完全对不上账。这套系统真正的难点从来不是「能不能启动」而是挂号、门诊、药房、住院这四条业务线怎么在同一个数据库里保持数据一致。这份源码通常基于 Spring Boot MyBatis-Plus MySQL 这套主流组合前端可能是 Thymeleaf 或者 Vue 前后端分离。它解决的核心问题是把医院里最典型的四个场景——患者挂号、医生门诊接诊、药房发药退药、住院登记与费用结算——用一套表结构和状态机串起来。适合谁适合正在做 Java 课程设计、毕业设计或者想拿一个真实业务系统练手 MyBatis-Plus 和事务控制的开发者。下面我按实际落地顺序把这份源码从环境配置到核心业务链路拆开讲中间会穿插我踩过的坑和参数设置建议。2. 环境配置与数据库初始化别急着改代码先把这四步做对2.1 JDK、Maven、MySQL 的版本匹配不是玄学拿到源码后第一件事不是打开 IDEA而是看 pom.xml 里的 Spring Boot 版本。常见的是 2.7.x 或 3.x这两个版本对 JDK 要求完全不同。Spring Boot 2.7 配 JDK 8 或 11 都行Spring Boot 3.x 必须 JDK 17 起步。我见过太多人用 JDK 8 去跑 Spring Boot 3 的项目启动直接报 Unsupported class file major version然后开始怀疑源码有问题。# 查看当前 JDK 版本确认是否匹配 pom.xml 中的 spring-boot-starter-parent 版本 java -version # 如果输出是 1.8.0_xxx而项目用的是 Spring Boot 3.x需要切换到 JDK 17 # 查看 Maven 版本建议 3.6 以上 mvn -v逻辑说明先确认 JDK 大版本再决定是否需要调整。参数上如果项目 pom.xml 里写了java.version17/java.version那 JDK 8 一定跑不起来。MySQL 建议 5.7 或 8.0两者在驱动类名上有区别5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver而且 8.0 的 URL 必须带时区参数。2.2 数据库建表与初始数据导入的先后顺序源码包里一般会有sql文件夹里面通常是一个hospital.sql或者分文件的建表语句。正确顺序是先建库再导表结构最后导初始数据。很多人直接把整个 sql 文件一次性执行结果因为外键依赖顺序报错。-- 先创建数据库字符集用 utf8mb4否则中文药品名会乱码 CREATE DATABASE hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital_db; -- 再执行源码包里的建表语句注意先建基础表科室、医生、药品字典 -- 最后导入初始数据比如默认管理员账号、科室列表、药品库存逻辑说明建库时字符集必须是 utf8mb4因为医院系统里会有患者姓名生僻字、药品化学名特殊符号。初始数据里通常有一个默认管理员账号密码在sys_user表里常见的是 admin/123456 或者 admin/admin123登录后第一件事就是改密码。如果导入后登录报「用户名或密码错误」先查sys_user表的 password 字段是不是明文有些源码用了 BCrypt 加密你直接输明文当然对不上。2.3 application.yml 里必须改的四个参数配置文件是连接代码和数据库的桥梁以下四个参数不改项目必翻车。spring: datasource: url: jdbc:mysql://localhost:3306/hospital_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的实际密码 driver-class-name: com.mysql.cj.jdbc.Driver # 文件上传路径药品图片、患者头像会用到 servlet: multipart: max-file-size: 10MB max-request-size: 10MB逻辑说明serverTimezoneAsia/Shanghai不加的话MySQL 8.0 会报时区错误。useSSLfalse在本地开发时避免证书警告。max-file-size根据实际需要调整药房模块如果支持上传药品说明书 PDF10MB 可能不够。改完配置后先跑一个单元测试连数据库别急着启动整个项目。2.4 启动类扫描路径与 Mapper 扫描的常见遗漏Spring Boot 启动类上通常有MapperScan(com.xxx.mapper)如果包名和实际不一致MyBatis 会找不到 Mapper 接口启动时报Invalid bound statement (not found)。这个错误我见过太多次血泪经验就是先看启动类注解再看 Mapper XML 的 namespace 是否和接口全限定名一致。SpringBootApplication MapperScan(com.hospital.mapper) // 这里的包名必须和实际 Mapper 接口所在包一致 public class HospitalApplication { public static void main(String[] args) { SpringApplication.run(HospitalApplication.class, args); } }逻辑说明如果 Mapper XML 放在resources/mapper目录下还要在 application.yml 里配置mybatis-plus.mapper-locations: classpath*:mapper/**/*.xml。参数上classpath*:和classpath:的区别在于前者会扫描所有类路径后者只扫描第一个多模块项目里必须用classpath*:。3. 挂号与门诊模块状态机设计和排班冲突怎么处理3.1 挂号表的核心字段与状态流转挂号不是简单插一条记录它涉及号源扣减、排班关联、患者信息绑定。核心表通常是registration关键字段包括patient_id、doctor_id、schedule_id、registration_fee、status、visit_time。状态一般有0-待就诊、1-已就诊、2-已取消、3-已退号。-- 挂号时先查号源余量再扣减最后插入挂号记录三步必须在同一个事务里 START TRANSACTION; SELECT remaining_slots FROM doctor_schedule WHERE id ? FOR UPDATE; UPDATE doctor_schedule SET remaining_slots remaining_slots - 1 WHERE id ? AND remaining_slots 0; INSERT INTO registration (patient_id, doctor_id, schedule_id, status, visit_time) VALUES (?, ?, ?, 0, ?); COMMIT;逻辑说明FOR UPDATE是行锁防止两个患者同时抢最后一个号。remaining_slots 0是乐观校验即使锁没生效也不会把号源扣成负数。参数上visit_time建议存具体时间段比如「上午 9:00-9:30」而不是只存日期否则门诊叫号没法排序。3.2 门诊接诊与处方开具的联动医生登录后进入门诊模块看到的是自己当天的挂号列表。点击「接诊」后状态从 0 变成 1同时生成一条medical_record记录。开处方时处方明细prescription_detail要关联药品 ID 和数量并且实时校验药房库存。// 开处方时校验库存库存不足直接抛异常避免处方开了药房发不出药 Transactional public void createPrescription(Long recordId, ListPrescriptionItem items) { for (PrescriptionItem item : items) { Integer stock medicineMapper.selectStockById(item.getMedicineId()); if (stock null || stock item.getQuantity()) { throw new BusinessException(药品库存不足 item.getMedicineName()); } } prescriptionMapper.insert(recordId, items); // 注意这里不扣库存扣库存是药房发药时的事否则医生开了处方但患者没缴费库存就白扣了 }逻辑说明处方开具和库存扣减必须分开。我见过有源码在医生开处方时就扣库存结果患者没缴费、没取药库存却少了药房盘点时对不上账。正确做法是药房发药时才扣库存处方只做库存预校验。参数上Transactional的 rollbackFor 建议显式写成rollbackFor Exception.class否则遇到检查异常不回滚。3.3 排班冲突检测一个 SQL 就能搞定医生排班表doctor_schedule里同一个医生同一天同一时段不能有两条记录。与其在 Java 里查一遍再插不如直接用数据库唯一索引兜底。-- 在 doctor_schedule 表上建唯一索引防止重复排班 ALTER TABLE doctor_schedule ADD UNIQUE KEY uk_doctor_date_slot (doctor_id, schedule_date, time_slot);逻辑说明time_slot存的是时段标识比如AM、PM或者更细的09:00-09:30。有了唯一索引即使前端重复提交数据库也会报 Duplicate entry后端捕获这个异常返回友好提示即可。参数上如果业务允许一个医生上午在两个科室出诊那唯一索引就要加上department_id。4. 药房与住院模块库存扣减和费用结算的坑最深4.1 药房发药时的库存扣减与批次管理药房模块的核心表是medicine_stock如果支持批次管理还会有medicine_batch。发药时按先进先出扣减批次库存同时更新总库存。-- 发药时按批次先进先出扣减避免药品过期 UPDATE medicine_batch SET quantity quantity - ? WHERE medicine_id ? AND quantity ? ORDER BY production_date ASC LIMIT 1; -- 再更新总库存表 UPDATE medicine_stock SET total_quantity total_quantity - ? WHERE medicine_id ?;逻辑说明ORDER BY production_date ASC保证先扣早批次减少过期损耗。quantity ?防止扣成负数。参数上如果源码没有批次管理只有总库存那至少要在medicine_stock表上加一个version字段做乐观锁防止并发发药时超卖。4.2 住院登记与押金流水的一致性住院模块涉及admission住院登记、deposit押金、hospitalization_expense住院费用。患者办理住院时先交押金押金记录插入deposit表同时更新admission表的押金余额。出院结算时总费用从押金里扣多退少补。// 交押金插入流水 更新余额必须在同一事务 Transactional(rollbackFor Exception.class) public void payDeposit(Long admissionId, BigDecimal amount) { depositMapper.insert(new Deposit(admissionId, amount, new Date())); admissionMapper.updateDepositBalance(admissionId, amount); }逻辑说明押金流水和余额更新必须原子操作。我踩过的坑是源码里先更新余额再插流水结果插流水失败但余额已经加了患者没交钱却显示有押金。参数上BigDecimal的金额字段在数据库里用DECIMAL(10,2)Java 里用BigDecimal千万别用double否则 0.10.2 不等于 0.3 的问题会在费用结算时让你怀疑人生。4.3 住院费用每日清单的生成逻辑住院费用通常包括床位费、诊疗费、药品费。每日清单生成一般靠定时任务凌晨跑批计算前一天的费用。Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public void generateDailyExpense() { ListAdmission activeAdmissions admissionMapper.selectActive(); for (Admission admission : activeAdmissions) { BigDecimal bedFee calculateBedFee(admission); BigDecimal medicineFee calculateMedicineFee(admission.getId()); expenseMapper.insertDailyExpense(admission.getId(), bedFee, medicineFee); } }逻辑说明cron表达式0 0 2 * * ?表示每天 2:00 执行。参数上如果医院有多个院区定时任务要加分布式锁否则多实例部署时会重复生成费用。常见做法是用 Redis 的SETNX加锁或者用数据库唯一索引兜底。5. 避坑与排查这五个问题我几乎每次都能遇到5.1 启动报「Invalid bound statement (not found)」现象项目启动成功但访问任何带数据库操作的接口都报这个错。原因MyBatis 的 Mapper 接口和 XML 文件没有正确映射。解决检查三个地方——启动类MapperScan的包名、application.yml 里mapper-locations的路径、XML 文件里的namespace是否等于 Mapper 接口全限定名。三者缺一不可。5.2 挂号时号源扣成负数现象两个患者同时挂最后一个号结果都挂号成功号源变成 -1。原因查询和扣减之间没有加锁或者用了SELECT再UPDATE的非原子操作。解决用UPDATE ... WHERE remaining_slots 0的原子写法或者SELECT ... FOR UPDATE加行锁。如果并发量不大唯一索引加乐观锁也能兜住。5.3 药房发药后库存对不上现象发药记录显示扣了 10 盒但库存只少了 8 盒。原因扣减批次库存和总库存不在同一事务或者批次扣减失败但总库存扣了。解决把两个 UPDATE 放在同一个Transactional方法里并且检查批次扣减的返回值如果影响行数为 0主动抛异常回滚。5.4 住院押金余额和流水不一致现象患者交了 5000 押金流水里有记录但住院登记表里的余额还是 0。原因更新余额的 SQL 条件写错了比如WHERE id ?传成了admission_id。解决先查流水表确认插入成功再查登记表的deposit_balance字段对比deposit表的SUM(amount)是否一致。不一致就写个补偿脚本修数据然后检查代码里事务是否生效。5.5 中文乱码从数据库到前端全链路排查现象药品名显示成问号或者乱码。原因数据库字符集、连接 URL 字符集、前端页面编码三者不一致。解决数据库用utf8mb4连接 URL 加characterEncodingutf8前端 HTML 加meta charsetUTF-8。如果用了 Thymeleaf还要在application.yml里配spring.thymeleaf.encoding: UTF-8。全链路统一成 UTF-8基本不会乱码。6. 进阶技巧用 MyBatis-Plus 代码生成器反向生成实体类再对照源码查漏补缺6.1 为什么建议你反向生成一遍实体类拿到源码后别急着改业务逻辑。先用 MyBatis-Plus 的代码生成器连上你导入的数据库反向生成一遍 Entity、Mapper、Service。这样做有两个好处一是能快速验证数据库表结构是否完整二是能对照源码里的实体类看有没有字段遗漏或者类型不匹配。我一般会把这个生成结果和源码里的entity包做 diff经常能发现源码里少写了某个字段的TableField注解导致查询时该字段永远为 null。// MyBatis-Plus 代码生成器核心配置 FastAutoGenerator.create(jdbc:mysql://localhost:3306/hospital_db, root, password) .globalConfig(builder - builder.author(dev).outputDir(System.getProperty(user.dir) /src/main/java)) .packageConfig(builder - builder.parent(com.hospital).moduleName(system)) .strategyConfig(builder - builder.addInclude(registration, medical_record, prescription, medicine_stock, admission, deposit) .entityBuilder().enableLombok().enableTableFieldAnnotation() .mapperBuilder().enableBaseResultMap().enableBaseColumnList()) .execute();逻辑说明addInclude里列出核心业务表生成器会为每张表生成 Entity、Mapper、Service、Controller。enableTableFieldAnnotation会生成TableField注解方便你对照源码检查字段映射。参数上outputDir建议指向一个临时目录别直接覆盖源码生成后手动对比。6.2 用数据库同步工具做表结构比对如果你手上有多个版本的医院管理系统源码或者想确认自己的表结构和标准版差异可以用数据库同步工具做 schema diff。常见做法是把两个库都连上工具会自动列出字段差异、索引差异、字符集差异。我习惯在改完表结构后用这个方式生成一份变更 SQL留作后悔药。注意同步工具只比对结构不比对数据数据迁移要另外写脚本。6.3 一个具体技巧用 SQL 直接查挂号到发药的完整链路想验证挂号、门诊、药房是否串通了不用点页面直接写一条关联查询就能看出来。SELECT r.id AS 挂号ID, p.name AS 患者, d.name AS 医生, mr.diagnosis AS 诊断, m.name AS 药品, pd.quantity AS 数量 FROM registration r JOIN patient p ON r.patient_id p.id JOIN doctor d ON r.doctor_id d.id LEFT JOIN medical_record mr ON mr.registration_id r.id LEFT JOIN prescription_detail pd ON pd.record_id mr.id LEFT JOIN medicine m ON pd.medicine_id m.id WHERE r.visit_time CURDATE();逻辑说明这条 SQL 从挂号表出发左连门诊记录、处方明细、药品表能一次性看到「谁挂了谁的号、诊断了什么、开了什么药、开了多少」。如果某个环节为 null说明业务链路断了。参数上CURDATE()只看当天排查历史数据可以改成具体日期范围。我每次接手一套新源码都会先跑这条 SQL比点页面快得多。这套医院管理系统源码的价值不在于代码有多复杂而在于它把挂号、门诊、药房、住院四个模块的数据一致性讲清楚了。你把这四条链路跑通再遇到类似的业务系统基本都能套用同样的思路去排查。希望帮到你。本文还有配套的精品资源点击获取
返回列表