news 2026/9/12 22:45:45

SpringBoot+MyBatis开发老龄化社区服务平台实战教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+MyBatis开发老龄化社区服务平台实战教程

简介:基于Java与SpringBoot构建的人口老龄化社区服务与管理平台,是一套面向高校计算机专业毕业设计及课程设计的完整项目源码包。系统按管理员、员工、用户三类角色设计,用户端支持注册登录、修改密码、查看社区信息与文件、浏览活动并报名、留言反馈;管理员可维护用户、员工、老人信息,管理社区文件、走访任务与系统配置;员工则可负责活动管理、报名审核、老人信息维护与走访记录等。前后端分离,后端以SpringBoot提供接口,前端采用Vue与HTML/CSS实现页面,整体模块划分清晰,便于学习者理解业务流与代码结构。资源包共854个文件,大小约72MB,包含146个Java源码、52个Vue组件、153个JS脚本、HTML/CSS页面资源,以及SQL数据库脚本、Maven/yml配置、项目说明文档和MP4演示视频;同时附有SVG/GIF/PNG等前端素材、字体图标与备份文件。压缩包内还提供部分批处理脚本,配合演示视频与说明文档,可显著降低环境搭建与部署难度。目前已有88人浏览学习,适合具备Java基础、希望快速掌握社区服务类管理系统设计与实现的读者,也可作为毕业设计、课程设计的完整参考。

1. 人口老龄化社区服务平台到底是什么,该怎么拆

人口老龄化社区服务与管理平台,看着像一套民政系统,实际落到工程上就三件事:管老人档案、管服务工单、管健康数据。管老人是把六十岁以上居民的基本信息、住址、紧急联系人建电子档案;管服务是让老人或家属在线提交生活照料、医疗陪护、精神慰藉等申请,由后台派单给社区工作人员并跟踪状态;管健康数据则是把每次上门测的血压、血糖、用药记录按时间串起来,供社区医生查询。用 Java+SpringBoot 来做这套系统,是毕业设计和初级后端项目里最稳的组合:SpringBoot 的自动装配和内嵌 Tomcat 把环境成本压到最低,而你又能把 Java 基础、MySQL 建表、接口设计这些评审老师最看重的硬功夫完整展示出来。下文按工程骨架、数据库设计、业务接口、权限事务、部署答辩五段展开,代码基于 SpringBoot 2.7.x + JDK 8 + MySQL 8.0 编写。

2. SpringBoot 工程骨架搭建:从 pom.xml 到三层目录这样划分

单体管理后台加一组 REST 接口,不需要微服务,不需要多模块工程,建立一个单模块 Maven 项目就够。这个判断本身就是答辩时可以讲的架构决策:社区服务平台的并发量级不高,单体应用加 MySQL 完全能扛住,硬拆模块反而增加部署成本。下面把依赖选型、配置文件和目录结构一次说清。

2.1 pom.xml 依赖选型:版本对齐是关键

用 SpringBoot 2.7.18 不用 3.x,理由很现实:JDK 8 在高校机房和多数企业存量环境里依然是主力,SpringBoot 3 强制 JDK 17,答辩现场最容易因为版本不一致翻车。依赖版本这件事,SpringBoot 的父级 pom 已帮你锁死大部分,你只需要加 starter 依赖,不用手写版本号,只有 MyBatis starter 这种非官方原生的才需要显式指定。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>

这组依赖解决三件事:spring-boot-starter-web 负责路由、JSON 序列化和内嵌 Tomcat;mybatis-spring-boot-starter 自动装配 SqlSessionFactory 和 Mapper 扫描,你只管写接口;validation starter 配合 @Valid 注解做参数校验。版本匹配记住一个原则:mybatis-spring-boot-starter 的 2.x 系列配 SpringBoot 2.x,3.x 系列配 SpringBoot 3.x,混用启动时会报 NoSuchBeanDefinitionException。mysql-connector-java 没写版本是因为 parent 已把版本锁定在 8.0.33,不用自己管。

2.2 application.yml:数据源、驼峰映射、SQL 日志一次配好

SpringBoot 配置文件的细节决定你联调时省不省心。数据库 URL 里 serverTimezone 和 useSSL 两个参数最容易漏,漏了要么报时区错误,要么报 SSL 连接警告。MyBatis 的驼峰映射开关写错位置则会导致查询结果全是 null 且不报错,这是新手排查时间最长的一个坑。

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/eldercare?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: root hikari: maximum-pool-size: 10 minimum-idle: 2 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.eldercare.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

map-underscore-to-camel-case 这行是核心,它让数据库的 elder_name 字段自动映射到实体的 elderName 属性,省掉 XML 里手写 resultMap。log-impl 配成 StdOutImpl 后,每条 SQL 的完整语句和绑定参数都会打到控制台,排查数据库增删改查比看堆栈还快。allowPublicKeyRetrieval=true 是针对 MySQL 8.x caching_sha2_password 插件的兼容参数,不加有的驱动版本启动就报 Public Key Retrieval is not allowed。HikariCP 的 maximum-pool-size 设 10 对这个量级足够,社区平台这类系统峰值连接数很少超过 5。

2.3 Controller-Service-Mapper 三层目录这样建

包结构按业务域建,controller、service、mapper、entity、dto、config 六个顶级包,entity 下再按业务模块分包。分层职责我用一张表固定下来,也方便答辩时直接对着讲:

分层包名职责典型注解
表现层controller接收参数、统一返回、参数校验触发@RestController
业务层service业务规则、事务边界、流程编排@Service
数据访问层mapperSQL 绑定、动态 SQL、分页查询@Mapper
实体层entity与表字段一一对应的 POJO@Data
模型层dto/vo入参校验对象、出参视图对象无注解

一个请求的标准流向是:Controller 接收前端 JSON,转成 DTO 做校验,调 Service 方法,Service 内部调 Mapper,SQL 执行完结果装成 VO 返回前端。SQL 全部收敛到 resources/mapper 目录下的 XML 文件里,Java 代码里不出现任何数据库方言,这是 MyBatis 对比 JDBC 原生代码最大的优势,也是靠这套配置可以把团队协作边界的价值说明白的点。

3. 数据库表结构设计与 MyBatis 映射:把表建扎实,后面少改代码

很多项目挂掉不是因为 Java 代码写得烂,而是表设计经不起问。评审一问“为什么这个字段用 varchar 不用 datetime”,答不上来就露馅。这一章把核心表的设计依据、建表 SQL 和 Mapper 参数传递全给到位。

3.1 核心业务表划分:四张表撑起整个平台

管理后台视角下,至少需要四类数据:老人档案、社区工作人员、服务工单、健康记录。elder_user 和 community_staff 是基础表,相互独立;service_order 通过 elder_id 和 staff_id 与它们关联;health_record 通过 elder_id 与 elder_user 关联。一个老人对应多条健康记录,这是一对多;一个工单关联一个老人和一个处理人员,这是多对一。

表名含义关键字段数据量预估
elder_user老年人口档案name, age, id_card, address, emergency_name, emergency_phone, status千级
community_staff社区工作人员name, job_no, phone, role, passwd百级以下
service_order服务工单order_no, elder_id, staff_id, service_type, service_time, status万级
health_record健康档案elder_id, systolic, diastolic, blood_sugar, record_date十万级

建表顺序有讲究:先建 elder_user 和 community_staff 两张基础表,再建依赖外键的 service_order 和 health_record,避免 DDL 引用不存在的表导致报错。库名直接叫 eldercare,字符集用 utf8mb4 而不用 utf8,utf8mb4 才完整支持四字节字符——老人姓名里出现生僻字的概率比你想的高很多。

3.2 建表 SQL:字段类型选择和外键处理

下面这段 SQL 是本系统的核心,可直接放进 Navicat 或命令行执行。所有表都带 create_time 和 update_time 两个审计字段,这是证明你有生产意识的细节,答辩时值得主动提一句。

CREATE DATABASE IF NOT EXISTS eldercare DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE eldercare; CREATE TABLE elder_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', name VARCHAR(50) NOT NULL COMMENT '老人姓名', gender TINYINT NOT NULL DEFAULT 1 COMMENT '性别 1男 2女 0未知', age TINYINT NOT NULL COMMENT '年龄', id_card VARCHAR(18) COMMENT '身份证号', address VARCHAR(200) COMMENT '居住地址', phone VARCHAR(20) COMMENT '联系电话', emergency_name VARCHAR(50) COMMENT '紧急联系人', emergency_phone VARCHAR(20) COMMENT '紧急联系人电话', status TINYINT NOT NULL DEFAULT 1 COMMENT '状态 1正常 0停用', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_elder_name_age (name, age), KEY idx_elder_status (status) ) ENGINE=InnoDB COMMENT='老年人口档案表'; CREATE TABLE community_staff ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '姓名', job_no VARCHAR(20) NOT NULL UNIQUE COMMENT '工号', phone VARCHAR(20), role TINYINT NOT NULL DEFAULT 2 COMMENT '角色 1管理员 2服务人员', passwd VARCHAR(100) NOT NULL COMMENT '登录密码(BCrypt密文)', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='社区工作人员表'; CREATE TABLE service_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '工单编号', elder_id BIGINT NOT NULL COMMENT '老人ID', staff_id BIGINT COMMENT '处理人员ID', service_type TINYINT NOT NULL COMMENT '1生活照料 2医疗陪护 3精神慰藉 4家政服务', service_time DATETIME COMMENT '服务时间', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待派单 1进行中 2已完成 3已取消', remark VARCHAR(500) COMMENT '备注', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_order_elder FOREIGN KEY (elder_id) REFERENCES elder_user(id), CONSTRAINT fk_order_staff FOREIGN KEY (staff_id) REFERENCES community_staff(id), KEY idx_order_status (status), KEY idx_order_elder_time (elder_id, create_time) ) ENGINE=InnoDB COMMENT='服务工单表'; CREATE TABLE health_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, elder_id BIGINT NOT NULL COMMENT '老人ID', systolic INT COMMENT '收缩压mmHg', diastolic INT COMMENT '舒张压mmHg', blood_sugar DECIMAL(4,1) COMMENT '血糖mmol/L', weight DECIMAL(5,2) COMMENT '体重kg', heart_rate INT COMMENT '心率次/分', record_date DATE NOT NULL COMMENT '测量日期', doctor_remark VARCHAR(500) COMMENT '医生备注', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_health_elder FOREIGN KEY (elder_id) REFERENCES elder_user(id), KEY idx_health_date (elder_id, record_date) ) ENGINE=InnoDB COMMENT='老年人健康记录表';

几个决策点说明一下。order_no 工单编号用 UNIQUE 约束保证不重复,生成逻辑放在 Java 侧用日期前缀加雪花 ID 或 UUID 截断,不要用自增 ID 裸奔。外键约束在互联网生产环境常被禁用,但毕设项目里保留外键是加分项,能展示你对引用完整性的理解,量级在这里也根本不会成为性能瓶颈。age 字段用 TINYINT 足够,127 岁上限在实际场景中不会触发;身份证号用 VARCHAR(18) 而不是 BIGINT,因为末位可能包含 X。health_record 表刻意不做唯一约束,因为同一个老人一天可能测多次血压,record_date 和 elder_id 联合索引只用于提速查询,不限制重复。

3.3 MyBatis Mapper 接口与 XML 的参数传递

表建好后,对应的 Mapper 接口长这样,注意 @Param 注解在多参数场景下的必要性:

@Mapper public interface ElderUserMapper { int insert(ElderUser elderUser); ElderUser selectById(Long id); List<ElderUser> selectPage(@Param("offset") int offset, @Param("limit") int limit, @Param("keyword") String keyword); int updateById(ElderUser elderUser); int deleteById(Long id); }

XML 文件放在 resources/mapper 目录下,命名与接口保持一致,selectPage 对应的动态 SQL 如下:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.eldercare.mapper.ElderUserMapper"> <select id="selectPage" resultType="ElderUser"> SELECT id, name, gender, age, id_card, address, phone, emergency_name, emergency_phone, status, create_time FROM elder_user <where> <if test="keyword != null and keyword != ''"> AND (name LIKE CONCAT('%', #{keyword}, '%') OR phone LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY create_time DESC LIMIT #{offset}, #{limit} </select> </mapper>

#{} 是预编译参数占位符,MyBatis 会把值绑定成 PreparedStatement 的 ?,不会拼接进 SQL 字符串,从源头防 SQL 注入。${} 才是字符串拼接,只用于动态表名和排序列,且必须经过白名单校验。MySQL 分页直接用 LIMIT offset, limit 最清晰,如果要兼容 Oracle 就得换 ROWNUM 或 FETCH FIRST 方言,这就是为什么大型项目要上 PageHelper 或 MyBatis-Plus——但毕设里手写分页反而更能证明你懂 SQL 本身。

4. 核心业务接口实现:老年人档案、服务预约与分页查询

代码写得规范,答辩时根本不用背稿,打开代码讲结构就能撑住时间。这一章把新增老人档案、派单流程和分页查询三个接口从 Controller 到 Mapper 完整走一遍。

4.1 统一返回体和参数校验:先给整体立规矩

前后端分离的项目,后端每个接口的返回值最好统一成一种结构,不要一个接口返回 Map、一个返回 List,前端会很崩溃。定义一个 Result 类,包含 code、message、data 三个字段,成功 code=200,业务异常 4001,系统异常 5000,前端拿到非 200 直接弹错误提示。

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "success"; r.data = data; return r; } public static <T> Result<T> error(Integer code, String message) { Result<T> r = new Result<>(); r.code = code; r.message = message; return r; } }

统一返回体的一个隐性好处是前端可以做全局拦截:axios 响应拦截器里判断 code 字段,不是 200 统一 toast 错误消息。这样后端的错误信息能直接传达到用户,而不用每个页面单独写 try-catch。配合 @RestControllerAdvice 全局异常处理器,Controller 可以做到零 try-catch,代码整洁度直接上一个档次。

4.2 新增老年人档案接口:从 Controller 到 Mapper 的完整链路

新增老人档案是系统的入口功能,DTO 与 Entity 必须分开,DTO 只管前端传参和校验,Entity 和表结构一一对应。校验注解写在 DTO 上,Controller 加 @Valid 触发,业务里不需要手写 if 判断空值。

@Data public class ElderUserDTO { @NotBlank(message = "姓名不能为空") private String name; @NotNull(message = "年龄不能为空") @Min(value = 60, message = "服务对象需60岁以上") @Max(value = 150, message = "年龄不合法") private Integer age; @NotBlank(message = "身份证号不能为空") @Pattern(regexp = "^[0-9X]{18}$", message = "身份证格式错误") private String idCard; @NotBlank(message = "地址不能为空") private String address; private String phone; private String emergencyName; private String emergencyPhone; }

Controller 保持薄,真正的业务规则放在 Service 类里:

@Service @Slf4j @RequiredArgsConstructor public class ElderUserServiceImpl implements ElderUserService { private final ElderUserMapper elderUserMapper; @Override @Transactional(rollbackFor = Exception.class) public Long createElder(ElderUserDTO dto) { // 同一身份证只能建档一次,防止重复录入 long count = elderUserMapper.countByIdCard(dto.getIdCard()); if (count > 0) { throw new BizException("该身份证已存在档案"); } ElderUser elder = new ElderUser(); BeanUtils.copyProperties(dto, elder); elder.setStatus(1); elderUserMapper.insert(elder); log.info("新增老人档案, id={}", elder.getId()); return elder.getId(); } }

@Transactional(rollbackFor = Exception.class) 这句值得重点说明:Spring 默认只对 RuntimeException 回滚,遇到 Checked Exception 不会回滚,很多新人把业务异常定义为 Exception 子类之后发现事务不回滚,就是这里的原因。显式声明 rollbackFor = Exception.class 后所有异常都回滚。BeanUtils.copyProperties 把 DTO 同名字段拷贝到 Entity,省去手写 setter,但前提是两个类字段名完全一致,字段名对不上会静默丢失,这种 bug 排查起来很费劲。

4.3 服务预约工单:状态流转必须收敛到 Service

服务工单是平台的核心闭环。老人或家属提交预约后,状态从 0(待派单)进入流转;管理员派单给工作人员后变 1(进行中);服务人员上传结果后变 2(已完成);只有待派单状态可以取消,即变 3(已取消)。状态流转逻辑集中在 Service 方法里,不允许在 Controller 里直接改 status 字段:

public void dispatchOrder(Long orderId, Long staffId) { ServiceOrder order = serviceOrderMapper.selectById(orderId); if (order == null) { throw new BizException("工单不存在"); } // 状态机守卫:只有待派单才能派单 if (!order.getStatus().equals(0)) { throw new BizException("只有待派单状态才能派单"); } order.setStaffId(staffId); order.setStatus(1); serviceOrderMapper.updateById(order); }

这个 if 判断就是最朴素的状态机守卫,它保证工单不能从 2 跳回 1、也不能从 0 跳到 3,业务规则统一收口。答辩时这一条状态流转链路是最值得展开讲的素材,配合前端的下拉状态展示,能把完整的业务闭环讲清楚。工单编号 order_no 在 Service 里生成,用 SimpleDateFormat 拼日期再加雪花 ID 的截断字符串,确保多线程并发下不重复。

4.4 分页查询:手写 LIMIT 还是上 PageHelper

列表页是所有管理系统都绕不开的组件。手写分页的话要传 offset 和 limit,MyBatis 里手动算偏移量;想省事就用 PageHelper,一行 startPage 自动拦截下一条 SQL 追加 limit。

参数含义合法范围
pageNum当前页码,从 1 开始大于等于 1
pageSize每页条数1 到 100
keyword模糊搜索关键字null 或非空字符串
total总记录数,返回给前端做分页组件只读
public PageResult<ElderUserVO> listElder(int pageNum, int pageSize, String keyword) { PageHelper.startPage(pageNum, pageSize); Page<ElderUser> page = (Page<ElderUser>) elderUserMapper.selectPage(keyword); PageResult<ElderUserVO> result = new PageResult<>(); result.setTotal(page.getTotal()); result.setList(page.getResult().stream().map(elder -> { ElderUserVO vo = new ElderUserVO(); BeanUtils.copyProperties(elder, vo); return vo; }).collect(Collectors.toList())); return result; }

PageHelper 有一个经典坑:startPage 只对紧接着的下一条查询生效,如果中间穿插了其他 Mapper 查询,分页参数会串到那条 SQL 上。所以 startPage 必须和 select 方法紧挨着写,中间不隔任何其他数据库操作。另外 PageHelper 返回的 List 实际是 Page 子类,直接转型就能拿到 total,配合切面或者查询包装一下返回格式会更规范。

5. 权限鉴权、事务边界与统计报表:面向答辩的进阶配置

系统上线前的最后两层是安全和分析能力。在电商高并发场景这些是标配,在这个社区平台里做完就是超出评审预期。

5.1 登录鉴权:JWT 加拦截器比 Spring Security 更适合毕设

Spring Security 的过滤器链复杂度高,很多答辩人自己都讲不清 UsernamePasswordAuthenticationFilter 和 OncePerRequestFilter 的调用次序,老师追问细节容易露怯。常见做法是拦截器加 JWT,轻量且代码完全可控。JWT 本质是带签名的 JSON 字符串,分 Header、Payload、Signature 三段,服务端不保存会话状态,登录后返回 token,前端每次请求在 Authorization 头携带。

方案服务端状态存储跨域支持实现成本适用场景
Session + CookieRedis 或内存需额外配 CORS 和 SameSite同域名单体
JWT + 拦截器无状态天然支持前后端分离

拦截器注册的方式如下,把不需要登录的路径用 excludePathPatterns 放行:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/auth/login", "/api/auth/register"); } @Bean public JwtInterceptor jwtInterceptor() { return new JwtInterceptor(); } }

JWT 拦截器里每次从 request 的 Authorization 头取 token,用配置的密钥验签,解析出用户 ID 和角色放到 ThreadLocal 里供业务代码读取。密钥不要硬编码,放 application.yml 并在 .gitignore 里排除配置文件。token 有效期设 2 小时,前端捕获 401 状态码后跳转登录页。role 字段决定了哪些接口可访问,管理员才能处理派单操作,普通服务人员只能查看自己的工单列表。

5.2 事务边界怎么划:不是所有方法都要加 @Transactional

@Transactional 不是越多越好。Spring 事务基于 AOP 动态代理,只有代理对象的方法调用才会被拦截,同类中方法 A 调方法 B,B 上的事务注解不生效,这是面试题“Spring 事务失效场景”的经典答案来源之一。

操作是否加事务原因
单个 insert/update可选单条 SQL 自带原子性
新增档案后同时写操作日志必须两步骤统一提交或回滚
派单更新工单状态必须涉及状态和人员两个字段一致性
纯 select 查询不需要无状态变更

拿“派单”动作分析:更新工单状态和写一条操作日志是两次数据库操作,如果第二步失败而第一步成功,数据就不一致了,所以整个方法需要事务保护。而一个单纯的列表查询没有任何写操作,加事务只会增加内存和锁开销,属于负优化。事务粒度以方法为单位,方法内部的循环操作如果很长,就要考虑拆小,缩短事务持有数据库连接的时间。

5.3 统计报表:聚合查询 SQL 一次到位

前端首页需要一个“老年人口年龄结构”图表,后端给一个聚合接口就行,不需要在 Java 里做循环分组。这一条 SQL 在同一查询里完成分组、计数和平均值计算:

SELECT CASE WHEN age < 70 THEN '60-69' WHEN age < 80 THEN '70-79' ELSE '80+' END AS age_group, COUNT(*) AS cnt, ROUND(AVG(age), 1) AS avg_age FROM elder_user GROUP BY age_group ORDER BY age_group;

CASE WHEN 表达式在 SELECT 里生成分组键,GROUP BY 按这个新列聚合。ROUND 保留一位小数展示平均年龄。这条 SQL 的处理逻辑是数据库完全在内存中完成分组聚合,Java 侧只负责把结果集封装成接口返回。同理,服务类型分布统计把 GROUP BY 换成 service_type 字段即可,是同一个套路。

6. 打包部署与答辩演示:把代码变成完整闭环

6.1 Maven 打包与启动自检

毕设答辩最怕现场启动失败。我习惯用一个保守的打包和自检流程来保证现场稳定。项目根目录执行mvn clean package -DskipTests跳过测试快速打包,这一步确保单元测试的依赖不会影响产出物。得到 target 目录下的 jar 包后,用java -jar target/eldercare-0.0.1-SNAPSHOT.jar启动。启动成功的标志不是控制台出现 Spring Boot 的 ASCII banner 就完事,而是要看到Started EldercareApplication这行日志,它代表内嵌 Tomcat 已经完成端口绑定。

检查项执行命令预期结果
进程存活jps -l出现包含 eldercare 的进程号
端口监听netstat -anogrep 8080
接口连通curl http://localhost:8080/api/auth/login返回 JSON 而不是连接拒绝
SQL 日志看控制台出现 MyBatis 打印的预编译语句

如果上述自检中端口被占用,换端口要在 application.yml 里改 server.port 而不是硬编码在代码里。数据库连接失败时先看 MySQL 服务是否启动,再看账号密码和 URL 里的 database 名是否匹配,SQL 日志里往往已经给出了具体原因。

6.2 把项目讲出亮点的三个落点

答辩和面试中介绍这个项目时,用“业务背景→技术选型→我负责的模块→我踩过的坑”这条线来讲,比背 PPT 有效得多。提及技术栈时把这三件事绑定:MyBatis plus 或 MyBatis 的手写 SQL 让你能精确控制分页和关联查询,SpringBoot 内嵌容器让部署变成一条命令,JWT 方案让你能说清楚无状态鉴权的原理。数据库设计讲清四张表的外键关系和索引用途,比罗列页面数量更能拉开差距。最后一定要准备一个真实的问题复盘,比如字段映射失败导致查询为空、事务不生效、PageHelper 串页这三个坑里挑一个讲,会显得项目是真跑的而不是抄的。

这三个坑的排查过程本身就是最好的技术展示:查询为空去检查 map-underscore-to-camel-case,事务回滚失败去检查 rollbackFor 和同类调用,分页串页去查 startPage 与其他查询的先后顺序。把这些细节都串起来,项目的技术深度和工程意识就都有了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 22:42:35

无人机航电系统技术演进与DHCAA架构解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 22:40:33

CSS媒体类型与媒体查询:从基础到响应式设计实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 22:40:27

Go语言实现微服务金丝雀发布全链路实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华