Java养老院管理系统源码实战解析:Spring Boot+MyBatis-Plus完整实现

发布时间:2026/8/29 6:49:14
Java养老院管理系统源码实战解析:Spring Boot+MyBatis-Plus完整实现 简介在JavaWeb开发中将框架知识与完整业务系统结合是提升工程能力的关键。通过分层架构、事务控制与数据库设计开发者能系统理解企业级应用的构建原理。本文以养老院管理场景为切入点介绍基于Spring Boot、MyBatis-Plus、Thymeleaf和MySQL实现的完整管理系统涵盖老人档案、床位分配、护理记录、费用结算等核心模块帮助学习者掌握从零搭建可运行项目的完整链路也适用于课程设计与毕业设计参考。 做了这么多年Java开发也带过不少新人发现很多学Java的朋友到中期都会卡在一个问题上语法都懂但一提到“独立做个系统”就不知道从哪里下手。带着这个问题我整理出了这套基于Java的养老院管理系统设计源码。它不是那种只讲概念的玩具项目而是一套能跑通、能交作业、能写到简历上的完整业务系统。这套系统解决的是养老机构日常管理中最头疼的信息化问题——老人档案、床位分配、护理记录、餐饮安排、健康监测、费用结算这些原本靠Excel和纸质台账完成的工作统一收进一个Web系统里管理。对于正在做课程设计、毕业设计或想系统学习JavaWeb实战的同学它能帮你把Spring Boot、MyBatis、Thymeleaf、MySQL这些零散知识点串成一条线对于想快速搭建信息化管理后台的非技术运营者这套源码也能提供比较清晰的业务参考。老规矩先把整体的设计思路和核心实现讲透再把能直接照抄的代码和步骤放出来。1. 项目整体设计与思路拆解动手写代码之前我想先聊聊这套系统的设计思路。很多人一上来就建表、写接口结果写到一半发现表结构不合理、模块耦合严重返工成本极高。一个养老院管理系统表面上看起来只是“增删改查”但真正落地时会涉及多个角色的协作、状态的流转、费用的计算这些都是需要提前想清楚的。1.1 系统角色与权限划分先看使用者。养老院日常运营中至少有三类人要和系统打交道管理员负责全局配置包括员工账号管理、房间床位设置、收费标准定义、系统参数维护。护理人员护工/护士负责老人的日常护理记录、健康数据录入、饮食反馈这是系统使用频率最高的角色。前台/财务人员负责老人入住登记、家属来访登记、费用收缴与账单查询。这套源码在设计上做了角色区分不同账号登录后看到的功能菜单不同。权限这块采用的是基于拦截器的简单鉴权方案——没有引入Spring Security这样重型的框架而是通过Session中保存的登录用户角色来判断菜单可见性和接口可访问性。这样做的好处是代码逻辑直白非常适合理解“登录-鉴权-授权”的完整链路。如果后面业务复杂了再升级到Spring Security JWT也不迟核心业务代码不用大改。1.2 核心业务模块划分从业务流程上拆一个养老院的核心业务链条是这样的老人咨询入住 - 评估健康状况 - 分配房间床位 - 签订协议 - 日常护理记录 - 餐饮与医疗管理 - 费用月度结算 - 退住/转院。围绕这条链条我把系统划分成六个核心模块模块核心功能对应数据表老人档案管理入住登记、档案修改、退住办理old_man床位与房间管理房间配置、床位分配、状态变更room, bed护理工作台护理计划、护理记录、健康指标录入nursing_record, health_record餐饮管理每周食谱、特殊饮食标注、用餐统计diet_plan, diet_order费用管理收费标准、月度账单、缴费记录fee_item, fee_bill系统管理用户管理、角色权限、操作日志sys_user, sys_role, sys_log每个模块内部的逻辑我后面会展开讲这里先给个整体的业务地图。这样设计的好处是边界清晰每个模块可以独立开发和测试符合实际养老院的部门分工不会出现一个模块里塞了太多不相关功能后续扩展时比如加一个“家属小程序端”只需要在现有接口之上做适配不用推翻重来。1.3 技术选型背后的考虑这套源码采用的技术栈是Spring Boot 2.7 MyBatis-Plus MySQL 5.7 Thymeleaf Layui Maven。为什么这么选先说Spring Boot。如果是几年前主流还是SSH或SSM手写配置一堆XML对于新手来说光是环境配置就能劝退一半人。Spring Boot通过自动配置把大量样板配置消灭了一个mvn spring-boot:run就能启动项目对学习者和中小型项目都非常友好。MyBatis-Plus是在MyBatis之上做的增强保留了手写SQL的灵活度又提供了BaseMapper内置的增删改查方法。像分页查询这类高频操作用MyBatis-Plus的Page对象加一个拦截器就能搞定省掉手写PageHelper或自己封装分页的麻烦。前端选Thymeleaf Layui不是随便选的。Thymeleaf作为服务端渲染模板能让Java开发者不依赖Node.js环境快速开发出带页面交互的完整系统Layui则是一个轻量级的前端UI框架组件风格偏后端审美表格、表单、弹窗都很完整最重要的是无需构建工具一个layui.js文件引入就能用。注意这套系统没有做前后端分离。我知道现在业界主流是Vue Spring Boot但对于课程设计或中小型养老院内部管理系统服务端渲染的维护成本低很多。不需要单独起前端工程不需要处理跨域打成一个jar包就能部署。等你的场景到了“需要专业前端团队配合”的量级再改造也不迟。2. 数据库设计与核心配置详解业务模块理清楚之后最重要的就是数据库设计了。我见过太多人一上来就建二三十张表建完发现字段冗余、关联混乱。我主张“按业务链条建表先跑通主流程再加细节”。这套源码的表结构一共九个核心表接下来挑重点说。2.1 数据库物理表结构规划先看最核心的几张表。老人档案表old_man是系统的数据基座。字段包括老人编号、姓名、性别、身份证号、出生日期、联系人电话、入住时间、紧急联系人、既往病史、过敏药物、当前状态在住/已退住等。注意身份证号要用varchar而不是bigint因为身份证号可能包含X而且长度超长日期字段统一用date类型金额字段用decimal(10,2)避免浮点精度问题。房间床位表room, bed体现的是“房间-床位”的一对多关系。room表记录房间号、楼层、房间类型单人间/双人间/多人间、房间状态空闲/部分占用/满员bed表记录床位号、所属房间ID、床位状态空闲/占用、当前老人ID。之所以把床位单独拆表是为了支持按床位精细化分配和状态追踪这是养老院区别于普通酒店管理的关键点。护理记录表nursing_record是使用频率最高的表。每个护理人员的每次护理操作都对应一条记录字段包括老人ID、护理类型翻身/喂药/洗澡/量血压等、护理内容、护理时间、护理人ID、备注。护理记录不允许修改和删除只允许新增这是为了保留操作痕迹符合养老行业的安全审计要求。费用相关表fee_item, fee_bill我单独说一下。fee_item是收费项目定义表比如床位费、护理费、餐饮费、医疗费fee_bill是账单表记录老人ID、账单月份、各收费项目金额、总金额、缴费状态。费用计算逻辑在Service层通过循环当月护理记录和基础费率完成不在数据库里做复杂计算方便后期调整规则。2.2 关键设计决策与字段规范在设计这套表结构时我遵循了几条硬性规范。第一主键统一用自增id不用业务字段做主键。比如不能用老人身份证号做主键因为老人可能变更身份证信息虽然少见而且一旦业务表引用这张表改主键会牵连所有外键。第二所有表都要有create_time和update_time字段。这不是凑数而是排查问题时最重要的抓手。比如老人家属投诉“护理记录漏了”翻create_time就知道是什么时候录的谁录的。MyBatis-Plus提供了MetaObjectHandler自动填充字段的机制不用每条插入语句手动写时间。第三状态字段用tinyint表示不用字符串。比如“当前状态”用0表示在住、1表示已退住。用数字的好处是查询效率高而且可以通过定义常量类来保证代码可读性例如public static final Integer STATUS_LIVING 0;第四凡是涉及金额的字段一律decimal禁止用double/float。这一点老生常谈了但真的很重要。二进制浮点数无法精确表示0.1累计多次之后会出现0.30000000000000004这样的奇葩结果在财务场景下是不可接受的。2.3 环境配置与初始化要点数据库结构规划好之后环境配置这一块同样关键。项目采用标准的Spring Boot工程结构你克隆源码后需要先做几件事创建数据库CREATE DATABASE nursing_home DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;统一用utf8mb4而不是utf8因为utf8mb4才是完整的UTF-8实现能存emoji和生僻字老人的姓名中经常会有生僻字用utf8会报错乱码。导入sql脚本源码包中提供了nursing_home.sql里面包含了建表语句和初始数据。我建议先用source命令导入而不是在Navicat里复制粘贴执行因为脚本文件较大时控制台source的编码处理更稳定。修改application.yml配置主要是数据库连接信息、端口号、MyBatis-Plus配置。端口默认是8080如果你本机的8080被占用改server.port即可。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/nursing_home?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password thymeleaf: cache: false mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0注意datasource连接串中的serverTimezoneAsia/Shanghai 必须加上否则新版MySQL驱动会报时区错误。如果MySQL是8.0以上还要确保pom.xml中引用的是mysql-connector-java 8.0.x的版本驱动类名是com.mysql.cj.jdbc.Driver而非com.mysql.jdbc.Driver。3. 核心功能模块实现与关键代码讲解环境准备好之后就可以进入核心代码实现了。这一部分我会按照Controller - Service - Mapper的路线逐个模块讲清楚重点说代码结构和关键逻辑不贴太多重复代码。3.1 项目工程结构与分层设计先看工程目录结构我按约定俗成的三层架构组织src/main/java/com/nursing/home ├── controller │ ├── OldManController.java │ ├── RoomController.java │ ├── NursingRecordController.java │ ├── DietController.java │ ├── FeeController.java │ └── LoginController.java ├── service │ ├── OldManService.java │ ├── RoomService.java │ └── ... ├── mapper │ ├── OldManMapper.java │ ├── RoomMapper.java │ └── ... ├── entity │ ├── OldMan.java │ ├── Bed.java │ └── ... ├── config │ ├── MybatisPlusConfig.java │ └── WebMvcConfig.java ├── common │ ├── Result.java │ └── PageResult.java └── NursingHomeApplication.java为什么要严格分层Controller只负责接收请求和返回结果不写业务逻辑Service存放具体的业务处理逻辑比如“分配床位时先判断床状态再更新老人状态”Mapper只做数据库操作。这个分层不是教条而是实际开发和维护中的硬性需求。我见过很多课程设计的代码业务逻辑全堆在Controller里一个方法写几百行看起来“一步到位”但实际上改一个bug会牵连出三个新问题。一个典型的流程是前端页面点击“新增老人” - 请求到达OldManController的add方法 - 调用OldManService.addOldMan - 先做参数校验再调用BaseMapper.insert插入数据 - 返回Result.success()给前端 - 前端提示操作成功并刷新表格。3.2 登录模块与全局会话管理登录模块是整个系统的门户虽然逻辑不复杂但承载着安全链路的第一关。User表存储了用户名、密码MD5加密存储、角色ID、状态。登录流程如下接收前端提交的用户名和密码先按用户名查询用户若不存在返回“用户名或密码错误”将前端传入的密码做MD5加密后与数据库比对不一致返回同样的错误提示这是安全常识不能提示“密码错误”这么精确的信息防止攻击者探测用户名是否存在校验用户状态禁用账号直接拒绝登录登录成功后将用户ID、用户名、角色存入Session并写入操作日志。PostMapping(/login) public String login(String username, String password, HttpSession session) { SysUser user userService.findByUsername(username); if (user null || !user.getPassword().equals(Md5Utils.md5(password))) { return 用户名或密码错误; } if (user.getStatus() 0) { return 账号已被禁用请联系管理员; } session.setAttribute(loginUser, user); return redirect:/index; }登录的下一步是访问控制。我实现了一个简单的HandlerInterceptor在请求进入Controller之前检查Session中是否有登录用户没有就直接重定向到登录页。同时通过用户角色判断是否有权限访问某些管理接口比如只有管理员角色才能访问用户管理模块。这个拦截器在WebMvcConfig中注册并把登录页、静态资源路径排除掉。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); SysUser user (SysUser) session.getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } return true; } }3.3 老人档案与床位分配的业务逻辑老人入住流程是核心中的核心。它的完整链路是录入老人基本信息 - 选择空闲床位 - 老人状态变为在住 - 床位状态变为占用 - 生成初始账单。这个流程里坑最多的是“床位分配”这一步。如果两个人同时申请同一个床位就存在并发覆盖问题。省事的做法是在查询空闲床位时加条件and status 0然后在更新时再校验一次update bed set status 1 where id ? and status 0利用数据库的行锁和乐观锁来兜底。这是很值得学习的业务处理思维不只是在当前项目里管用。床位管理的核心查询——分页展示房间床位列表直接用了MyBatis-Plus的分页插件public PageResultRoomDTO getRoomPage(int page, int limit, String keyword) { PageRoom p new Page(page, limit); LambdaQueryWrapperRoom wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(Room::getRoomName, keyword); } PageRoom roomPage roomMapper.selectPage(p, wrapper); ListRoomDTO dtoList roomPage.getRecords().stream().map(room - { RoomDTO dto new RoomDTO(); BeanUtils.copyProperties(room, dto); dto.setBedCount(bedMapper.selectCount(new LambdaQueryWrapperBed() .eq(Bed::getRoomId, room.getId()))); dto.setOccupiedBedCount(bedMapper.selectCount(new LambdaQueryWrapperBed() .eq(Bed::getRoomId, room.getId()).eq(Bed::getStatus, 1))); return dto; }).collect(Collectors.toList()); return PageResult.success(dtoList, roomPage.getTotal()); }这里有一个实践心得不直接返回实体类给前端而是用DTO数据传输对象承接页面展示需要的数据。原因很简单比如床位表里存的是status1但页面上要显示“已占用/空闲”的中文文本这个转换放到Controller层会污染业务逻辑放到实体类上又会造成实体类肥胖放在DTO里转换最合适。3.4 护理记录的高频写入与查询优化护理工作台是系统中最“日用”的功能。护理人员每天要记录大量条目如果页面响应慢体验会很差。针对这个场景我做了两个设计。第一个设计是批量录入。页面支持按“老人 日期”快速录入多条护理记录一次提交到后端。后端在Service层批量循环插入使用MyBatis-Plus的saveBatch方法。这里需要注意事务注解Transactional必须加在方法上保证“要么全部成功、要么全部失败”不能出现录了三条之后第四条报错导致数据不完整的情况。第二个设计是时间范围查询。操作界面需要查“某位老人最近一个月的护理记录”对应的SQL用between即可。如果数据量大了建议给nursing_record表的old_man_id和nursing_time建联合索引否则全表扫描会越来越慢。Transactional public boolean batchAdd(ListNursingRecord records) { for (NursingRecord record : records) { record.setCreateTime(LocalDateTime.now()); } return nursingRecordMapper.insertBatchSomeColumn(records); }3.5 费用计算与月度账单生成费用模块是最容易写出bug的地方因为涉及金额和时间窗口的判断。我采用的方案是“每月1号定时生成上月账单”逻辑如下查询所有状态为在住的老人对每位老人结合其入住日期和退住日期如果有计算当月应计费天数按房间类型匹配床位费按护理等级匹配护理费再加上固定的餐饮费汇总生成一条账单记录状态为待缴费。这个功能里有个关键边界情况老人月中入住或退住。比如15号入住就不能收整月床位费。我用Java的时间计算实现了一个按天折算的逻辑public BigDecimal calcDailyFee(BigDecimal monthlyFee, int daysInMonth, int stayDays) { if (stayDays daysInMonth) { return monthlyFee; } return monthlyFee.multiply(BigDecimal.valueOf(stayDays)) .divide(BigDecimal.valueOf(daysInMonth), 2, RoundingMode.HALF_UP); }四舍五入模式用RoundingMode.HALF_UP这是财务系统的基本素养。不要用什么“四舍六入五成双”的银行家舍入法业务上说不清楚。4. 项目部署、常见问题与避坑技巧到这里系统的主要代码都讲完了最后聊聊我把这套系统从“跑起来”到“稳定运行”时遇到的一些坑以及排查方案。这部分对初学者尤其有价值很多问题不是代码逻辑的错误而是环境和配置层面的网上搜很难找到针对性答案。4.1 从源码到可运行的完整部署流程如果你拿到的源码结构完整部署流程非常标准我建议按这个顺序走一遍安装JDK 8或11配置JAVA_HOME和PATH环境变量。IDE里我推荐直接用IntelliJ IDEA社区版就够用打开工程后等待Maven自动下载依赖。安装MySQL本地开发用5.7或8.0均可。创建好数据库并导入SQL脚本。修改application.yml中的数据库用户名和密码本地账号密码不要用root建议创建一个专用账号降低误操作风险。在IDEA中运行NursingHomeApplication.java的main方法看到“Started NursingHomeApplication”则表示启动成功。浏览器访问 http://localhost:8080 用初始账号admin/admin登录。需要对外发布时执行mvn clean package -DskipTests在target目录下生成nursing-home.jar然后部署到服务器java -jar nursing-home.jar。4.2 快速定位启动失败的高频问题我整理了一个问题速查表都是实际运行中最常遇到的。现象根本原因解决方案启动报错Port 8080 was already in use端口被其他程序占用换端口或在启动命令加--server.port8081启动报错Access denied for user rootlocalhost数据库密码错误或用户权限不足核对application.yml密码用root账号授权远程访问页面中文乱码数据库字符集不是utf8mb4重建数据库并指定utf8mb4检查连接串加characterEncodingutf8启动报错Unknown database nursing_home还没执行数据库初始化脚本通过source命令导入SQL脚本登录后页面js和css加载失败静态资源被拦截器拦截在WebMvcConfig中放行/static/**和/login路径连不上本地MySQL8驱动版本与MySQL版本不匹配将pom.xml中mysql依赖版本升级为8.0.x注意Java 8和Java 11都能跑Spring Boot 2.7但不要用Java 17除非你同时把Spring Boot升级到3.x。Java 17配合Spring Boot 2.7会出现兼容性问题报错信息很像是框架本身的bug其实是版本匹配的问题。4.3 内存溢出与性能调优的实用经验“java: outofmemoryerror: insufficient memory”这个问题在idea里跑起来时偶尔会碰到尤其是一次性导入大量数据时。这通常不是代码问题而是JVM堆内存分配不足。解决方案在IDEA的Run Configuration中增加VM options-Xms256m -Xmx1024m让JVM初始分配256M、最大分配1G堆内存如果服务已经打包成jar启动命令改为java -Xms256m -Xmx1024m -jar nursing-home.jar同时检查一下是否有查询把一次性数据全拉到内存处理。比如导出报表时不要select *整表查出来再过滤应该把过滤条件下推到SQL里。再一个性能经验是千万不要在循环里查单条数据。比如在查询床位信息时很多新手会写for循环里再查一次房间表这会产生N1查询问题。我在代码里用了一对一、一对多的映射查询或者先批量查出来再在内存里组装接口响应时间从几百毫秒降到几十毫秒。学习这个思想比单纯跑通项目更有价值。4.4 二次开发的三个建议方向源码拿到手之后别满足于“能跑”下面这几个方向我认为是性价比最高的扩展点第一个方向是引入Spring Security或者Sa-Token来做权限系统把当前的拦截器鉴权升级为细腻度更高的RBAC模型。如果你准备面试大厂这个改造写进简历会是一个亮点因为大多数课程设计项目都没有完整的权限体系。第二个方向是把前端升级成Vue3 Element Plus做成真正的前后端分离。后端接口已经天然RESTful风格了改造前端比重写后端容易得多。前端工程化之后页面的交互体验会有质的提升。第三个方向是接入ECharts做数据大屏。养老院的管理者很喜欢看到这种直观的统计图表在住人数、床位利用率、本月费用收缴率、护理记录趋势等。只需要增加几个统计查询接口前端接图表组件项目整体档次瞬间不一样。5. 写在最后的个人体会我最初做第一版养老院管理系统时把所有查询逻辑都堆在Controller里一张页面两百行代码看得脑壳疼。后来跟着项目需求不断迭代才慢慢意识到分层设计、事务控制、并发处理这些概念的意义——不是书本上让你背的是每个真实的坑逼着你学。这套源码的价值不在于代码本身有多高级而在于它帮你把JavaWeb的知识点完整地串了一遍Maven工程管理、Spring Boot自动装配、MyBatis-Plus的CRUD和分页、Thymeleaf模板渲染、拦截器鉴权、事务管理、MD5加密甚至还有定时任务生成账单。这些知识点每一项单拎出来都有人讲但能组合成一个完整可运行系统的实战项目才是真正能帮你建立“工程感”的东西。如果你正在为课程设计或者毕业设计发愁建议你按我的步骤先把这个系统跑起来然后找一个小功能下手改造——比如加一个“老人生日提醒”模块把表结构、前后端代码、异常处理全部通一遍。这个过程走完你对JavaWeb项目的理解会上一个台阶后面再学Spring Cloud或者其他框架都会顺畅很多。最后提醒一句代码要自己敲一遍直接复制粘贴跑通和手敲一遍理解到的细节差的不是一星半点。本文还有配套的精品资源点击获取