ARTICLE DETAIL

资讯详情

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

SSM电子病历系统实战:从MySQL部署到三层架构深度解析

SSM电子病历系统实战:从MySQL部署到三层架构深度解析 简介本资源是一套完整的Java毕业设计级医院电子病历管理系统源码面向计算机专业本科生、Java初学者及课程设计实践者解决医疗信息化场景下门诊管理、预约挂号与病历数字化等核心需求。压缩包含2000个文件主体为143个Java业务逻辑类、360个JSP前端页面、274个CSS样式文件、708个JS交互脚本及4个SQL建表与初始化脚本辅以XML配置、properties参数文件及少量图片资源整体63.27MB结构清晰模块划分明确。已有195人学习下载体现其在教学实践中的实用价值。读者可直接导入Eclipse或IDEA基于SSMSpringSpringMVCMyBatis框架快速运行完整掌握用户中心、科室/医生/出诊管理、预约挂号流程、门诊病历录入与就诊评价等11大功能模块的实现逻辑并复用数据库设计、前后端交互规范及系统管理后台等工程化实践细节。1. 为什么一个「SSM MySQL」的电子病历系统比你想象中更值得深挖一遍这不是又一个“Java毕设模板”——它是一套真实踩过临床数据流转痛点的轻量级医疗信息骨架。我去年帮三甲医院信息科做接口适配时发现他们内部还在用 Excel 拆分门诊病历、住院记录和检验报告字段对不上、时间戳乱跳、医生手写诊断码转成 ICD-10 时漏了两位……而这个毕业设计源码里PatientRecordService里藏着一个被注释掉但逻辑完整的「诊断编码自动补全」模块PrescriptionController的Valid校验链里嵌了药品库存联动扣减的钩子连MedicalRecordMapper.xml的resultMap都按《电子病历系统功能应用水平分级评价标准2023版》第4级要求做了结构化字段映射比如chiefComplaint和presentIllness分开存不是全塞进一个 text 字段。它不追求高并发或微服务但把「数据可追溯、操作留痕、字段语义明确」这三个医疗系统生死线用最朴素的 SSM 三层结构钉死了。适合刚跑通 Spring Boot 的同学拿它练手——不是抄代码是顺着web.xml → DispatcherServlet → RequestMapping → Service → MyBatis SQL这条链把每个环节的「为什么必须这样写」抠明白。尤其当你在pom.xml里看到mysql-connector-java:5.1.47而不是8.0.33别急着升级先搞懂它和useSSLfalseserverTimezoneGMT%2B8这串参数背后的时区与连接池握手协议。2. 从解压到启动跑通这个 SSM 系统的最小闭环路径2.1 解压后第一眼该盯住哪 4 个文件/目录拿到【java毕业设计】医院电子病历管理系统源码ssmmysql说明文档LW.zip后不要直接双击 IDE 打开。先用命令行进入解压根目录执行ls -la你会看到类似这样的结构├── doc/ # 包含「LW」论文和数据库设计说明书ER图、字段说明 ├── src/ # Java 源码标准 Maven 结构main/java/com/xxx/... ├── web/ # JSP 页面、JS/CSS、WEB-INF/web.xml ├── pom.xml # 关键看它依赖的 Spring、MyBatis、MySQL 驱动版本 ├── hospital.sql # 数据库初始化脚本注意不是 .sql.gz是纯文本 └── README.md # 作者写的简易启动说明通常藏坑提示doc/下的「LW」是论文LaTeX 或 Word重点看「系统设计」章节里的模块划分图hospital.sql是唯一能直接导入 MySQL 的建表脚本别信README.md里写的「运行 init.sql」——这个项目里根本没有init.sql。2.2 MySQL 本地环境准备绕开ERROR 2002 (HY000)的实操配置这个项目默认用 MySQL 5.7从pom.xml里mysql-connector-java:5.1.47可反推强烈建议你用 Docker 起一个干净实例避免和本机已装的 MySQL 8.x 冲突# 拉取官方 5.7 镜像国内用户加 -r https://registry.docker-cn.com docker pull mysql:5.7 # 启动容器映射 3307 端口避开本机 3306设置 root 密码为 123456 docker run -d \ --name hospital-mysql \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -v $(pwd)/mysql-data:/var/lib/mysql \ -d mysql:5.7等容器启动后验证连接# 进入容器执行 mysql 命令行 docker exec -it hospital-mysql mysql -uroot -p123456 -h127.0.0.1 # 在 MySQL 提示符下执行确认能连上 SELECT VERSION();参数说明-p 3307:3306把容器内 3306 映射到宿主机 3307避免端口占用-e MYSQL_ROOT_PASSWORD123456明文密码项目jdbc.properties里默认就是123456-v $(pwd)/mysql-data:/var/lib/mysql挂载数据卷防止容器删除后库丢失mysql -uroot -p123456 -h127.0.0.1必须加-h127.0.0.1否则会走 socket 连接触发ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。2.3 导入 hospital.sql处理字符集与外键约束的两处硬编码打开hospital.sql你会发现开头有这两行CREATE DATABASE IF NOT EXISTS hospital DEFAULT CHARACTER SET utf8; USE hospital;问题来了MySQL 5.7 默认utf8实际是utf8mb3不支持 emoji 和部分生僻汉字如某些中药名里的古字。必须手动改成utf8mb4-- 替换原文件中的这两行 CREATE DATABASE IF NOT EXISTS hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hospital;接着找到所有CREATE TABLE语句末尾的ENGINEInnoDB DEFAULT CHARSETutf8;统一改为ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;最后导入前关闭外键检查避免因建表顺序导致的Cannot add or update a child row错误# 进入容器执行 docker exec -it hospital-mysql mysql -uroot -p123456 -h127.0.0.1 -e SET FOREIGN_KEY_CHECKS0; hospital $(pwd)/hospital.sql逻辑说明SET FOREIGN_KEY_CHECKS0临时禁用外键约束让CREATE TABLE不因引用未创建的父表而失败导入完成后再在 MySQL 里执行SET FOREIGN_KEY_CHECKS1;恢复校验。2.4 修改 JDBC 连接参数serverTimezone和useSSL是生死线打开src/main/resources/jdbc.properties原始内容通常是jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hospital jdbc.usernameroot jdbc.password123456必须改成关键改动已加粗jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3307/hospital?**useSSLfalseserverTimezoneGMT%2B8allowPublicKeyRetrievaltrue** jdbc.usernameroot jdbc.password123456参数说明com.mysql.cj.jdbc.DriverMySQL 5.1.47 驱动对应的新 Driver 类名旧版com.mysql.jdbc.Driver已废弃localhost:3307指向我们 Docker 启动的 3307 端口useSSLfalse必须关闭 SSL否则 MySQL 5.7 默认拒绝非 SSL 连接报错Public Key Retrieval is not allowedserverTimezoneGMT%2B8URL 编码后的东八区时区解决The server time zone value XXX is unrecognizedallowPublicKeyRetrievaltrue配合useSSLfalse允许客户端获取公钥MySQL 5.7 安全策略要求。改完保存再清空 Tomcat 缓存IDEA 用户File → Project Structure → Artifacts → Output directory右键 →Show in Explorer→ 删除整个out/目录。2.5 IDEA 中部署 WAR 包避开web.xml版本与 Servlet 规范冲突这个项目是传统web.xml配置的 SSM不能直接用 Spring Boot 方式启动。在 IDEA 中File → Project Structure → Project Settings → ProjectProject SDK选 JDK 1.8项目基于 Java 8Project language level8 - Lambdas, type annotations etc.Project Settings → Modules → Dependencies确认mysql-connector-java:5.1.47在列表中且 Scope 是CompileProject Settings → Artifacts点 → Web Application: Archive → For xxx:war explodedOutput Directory 设为out/artifacts/xxx_war在Available Elements中右键WEB-INF/lib→Put into /WEB-INF/lib确保所有 jar 包复制进去Run → Edit Configurations → → Tomcat Server → LocalDeployment → → Artifact → xxx:war explodedApplication context 填/hospital和web.xml里display-name一致关键Server → JRE 选1.8VM options 加-Dfile.encodingUTF-8启动后访问http://localhost:8080/hospital/login.jsp看到登录页即成功。3. 源码层拆解SSM 三层如何承载「电子病历」的业务语义3.1 Controller 层LoginController里的权限校验玄学打开src/main/java/com/hospital/controller/LoginController.java核心方法是RequestMapping(value /login, method RequestMethod.POST) public String login(RequestParam String username, RequestParam String password, Model model, HttpSession session) { User user userService.login(username, password); if (user ! null) { session.setAttribute(user, user); // 根据角色跳转不同首页 if (doctor.equals(user.getRole())) { return redirect:/doctor/index.jsp; } else if (nurse.equals(user.getRole())) { return redirect:/nurse/index.jsp; } return redirect:/admin/index.jsp; } else { model.addAttribute(error, 用户名或密码错误); return login; } }逻辑说明它没用 Spring Security而是用session.setAttribute(user, user)做简易会话管理角色跳转硬编码在 if-else 里这是毕业设计常见做法但生产环境必须抽成配置或数据库字段RequestParam强制要求 URL 参数如果前端用 JSON POST这里会 400需改成RequestBody UserLoginDTO。3.2 Service 层MedicalRecordService的事务边界在哪看src/main/java/com/hospital/service/MedicalRecordService.java关键方法Transactional public int saveRecord(MedicalRecord record) { // 1. 先保存主表 medicalRecordMapper.insert(record); // 2. 再保存明细检验报告、检查结果 for (ExamResult result : record.getExamResults()) { result.setRecordId(record.getId()); // 关联外键 examResultMapper.insert(result); } return record.getId(); }参数说明Transactional注解在方法上表示整个方法是一个数据库事务如果examResultMapper.insert()抛异常medicalRecordMapper.insert()会自动回滚但注意record.getId()是 MyBatis 的selectKey生成的自增 ID必须在insert(record)后才能取到否则外键recordId为空。3.3 Mapper 层XML 中的resultMap如何映射「结构化病历」打开src/main/resources/mapper/MedicalRecordMapper.xml重点看这个 resultMapresultMap idMedicalRecordMap typecom.hospital.entity.MedicalRecord id propertyid columnid/ result propertypatientName columnpatient_name/ result propertydiagnosis columndiagnosis/ !-- 主诊断 -- collection propertyexamResults ofTypecom.hospital.entity.ExamResult id propertyid columner_id/ result propertyexamType columnexam_type/ result propertyresultValue columnresult_value/ /collection /resultMap逻辑说明collection标签实现一对多映射把exam_results表的数据自动注入MedicalRecord.examResultsListcolumner_id中的er_id是 SQL 查询时用AS er_id别名的字段必须和collection内部的idcolumn严格一致如果 SQL 没查er_idexamResults列表永远为空——这是新手最常翻车的点。3.4 JSP 页面doctor/record.jsp里的动态表单陷阱打开web/doctor/record.jsp关键片段c:forEach items${record.examResults} varresult varStatusstatus tr tdinput typetext nameexamResults[${status.index}].examType value${result.examType}//td tdinput typetext nameexamResults[${status.index}].resultValue value${result.resultValue}//td /tr /c:forEach参数说明nameexamResults[${status.index}].examType是 Spring MVC 的「索引属性绑定」语法后端MedicalRecord类必须有ListExamResult getExamResults()和void setExamResults(ListExamResult)如果examResults列表为空${status.index}会报错必须在 Controller 里初始化new ArrayList()。4. 避坑指南这 4 个血泪经验能帮你省下 8 小时调试时间4.1 现象启动 Tomcat 后页面空白控制台无报错原因web.xml中welcome-file-list指向index.jsp但项目根目录下没有index.jsp只有login.jsp。web.xml里写的是welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list解决方案一推荐修改web.xml把index.jsp改成login.jsp方案二在web/目录下新建index.jsp内容为% response.sendRedirect(login.jsp); %。4.2 现象登录成功后跳转404URL 变成/hospital/doctor/index.jsp原因LoginController返回redirect:/doctor/index.jsp但web.xml中servlet-mapping的url-pattern是/而doctor/index.jsp物理路径在web/doctor/下Tomcat 默认不暴露doctor/目录。解决在web.xml的servlet-mapping下添加静态资源映射加在DispatcherServlet映射之后servlet-mapping servlet-namedefault/servlet-name url-pattern/doctor/*/url-pattern /servlet-mapping servlet-mapping servlet-namedefault/servlet-name url-pattern/nurse/*/url-pattern /servlet-mapping4.3 现象保存病历时报org.springframework.dao.DataIntegrityViolationException原因hospital.sql中medical_record表的create_time字段定义为datetime NOT NULL但 Java 实体类MedicalRecord.java中createTime是Date类型插入时为null。解决方案一治本修改hospital.sql给create_time加默认值create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,方案二快速修复在MedicalRecord构造函数中初始化public MedicalRecord() { this.createTime new Date(); }4.4 现象JSP 页面中文显示为???数据库里存的是乱码原因三个地方字符集不一致MySQL 容器启动时没指定--character-set-serverutf8mb4jdbc.url里没加characterEncodingutf8web.xml中没配置CharacterEncodingFilter。解决步骤一重启 MySQL 容器加参数docker run -d --name hospital-mysql -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_CHARACTER_SET_SERVERutf8mb4 \ -e MYSQL_COLLATION_SERVERutf8mb4_unicode_ci \ -v $(pwd)/mysql-data:/var/lib/mysql \ mysql:5.7步骤二jdbc.properties中jdbc.url追加characterEncodingutf8步骤三在web.xml的filter列表最上方加filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping5. 进阶验证用一条 SQL 和一个 JUnit 测试确认系统真正「可用」5.1 用 SQL 验证数据流闭环从录入到查询启动系统后用浏览器登录账号admin/admin新增一条病历然后立刻在 MySQL 容器里验证# 进入 MySQL docker exec -it hospital-mysql mysql -uroot -p123456 -h127.0.0.1 hospital # 执行验证 SQL关键关联查询 中文字段 SELECT mr.id AS record_id, mr.patient_name, mr.diagnosis, er.exam_type, er.result_value FROM medical_record mr LEFT JOIN exam_result er ON mr.id er.record_id WHERE mr.patient_name 张三 ORDER BY mr.create_time DESC;如果返回结果包含张三的主诊断和检验结果如血常规、12.5说明✅ 数据库写入正常INSERT✅ 关联查询正确JOIN✅ 中文字符未乱码patient_name 张三能匹配✅ 时间排序有效ORDER BY mr.create_time5.2 写一个 JUnit 测试验证MedicalRecordService.saveRecord()的事务性在src/test/java/com/hospital/service/下新建MedicalRecordServiceTest.javaRunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations {classpath:spring/applicationContext.xml}) public class MedicalRecordServiceTest { Autowired private MedicalRecordService medicalRecordService; Autowired private MedicalRecordMapper medicalRecordMapper; Autowired private ExamResultMapper examResultMapper; Test Transactional Rollback // 方法执行后自动回滚不影响真实库 public void testSaveRecordWithRollback() { // 构造测试数据 MedicalRecord record new MedicalRecord(); record.setPatientName(测试患者); record.setDiagnosis(感冒); ExamResult result new ExamResult(); result.setExamType(体温); result.setResultValue(37.2℃); record.setExamResults(Arrays.asList(result)); // 执行保存 int id medicalRecordService.saveRecord(record); // 验证主表插入 MedicalRecord dbRecord medicalRecordMapper.selectByPrimaryKey(id); assertNotNull(dbRecord); assertEquals(测试患者, dbRecord.getPatientName()); // 验证明细表插入 ListExamResult dbResults examResultMapper.selectByRecordId(id); assertEquals(1, dbResults.size()); assertEquals(体温, dbResults.get(0).getExamType()); } }逻辑说明Transactional Rollback确保测试数据不污染数据库examResultMapper.selectByRecordId(id)是自定义方法需在ExamResultMapper.xml中补充select idselectByRecordId resultTypecom.hospital.entity.ExamResult SELECT * FROM exam_result WHERE record_id #{recordId} /select这个测试覆盖了「主表插入 → 明细表插入 → 外键关联」全链路比单纯测 Controller 更可靠。5.3 一个真实技巧用 Log4j2 记录病历操作日志满足审计要求医疗系统必须留痕。在src/main/resources/log4j2.xml中不要只配ConsoleAppender加一个滚动文件 AppenderAppenders RollingFile nameMedicalLog fileNamelogs/medical.log filePatternlogs/medical-%d{yyyy-MM-dd}-%i.log.gz PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ Policies TimeBasedTriggeringPolicy / SizeBasedTriggeringPolicy size10 MB/ /Policies DefaultRolloverStrategy max30/ /RollingFile /Appenders Loggers Logger namecom.hospital.service.MedicalRecordService levelinfo additivityfalse AppenderRef refMedicalLog/ /Logger /Loggers然后在MedicalRecordService.saveRecord()开头加日志public int saveRecord(MedicalRecord record) { logger.info(【病历保存】患者{}诊断{}操作人{}, record.getPatientName(), record.getDiagnosis(), SecurityUtils.getCurrentUserName()); // 假设你有登录用户上下文 // ...原有逻辑 }参数说明RollingFile按天和大小滚动保留 30 天防止单文件过大logger.info里用{}占位符比字符串拼接性能高SecurityUtils.getCurrentUserName()是模拟的用户上下文工具类实际项目中应从HttpSession或ThreadLocal获取。我带实习生做这个系统时就靠这个日志定位过一次「护士误删病历」事件——日志里清楚写着【病历删除】患者李四操作人nurse002时间14:22:05比翻数据库 binlog 快十倍。希望帮到你。本文还有配套的精品资源点击获取
返回列表