简介:这是一份面向高校计算机相关专业毕业设计场景的SSM小区人口管理系统程序包,以Java技术栈实现人口信息、费用、疫情黑名单、出入登记等核心业务模块,适合需要完整参考实现或二次开发起步的开发者。整套资源共305个文件,包含70个Java源码及对应class文件、67个Vue前端页面、29个XML配置、数据库脚本等,压缩包仅1.04MB,结构紧凑、便于本地部署调试。已有153人学习下载。包内除可运行的程序源代码和数据库外,还配有运行环境说明,可帮助解决环境配置、数据库导入等常见问题;内容预览显示系统对用户、费用、疫情黑名单、出入登记等模块均有实体与控制器实现,整体覆盖需求分析、设计、编码和测试环节,适合作为毕业设计参考或功能扩展基底。
1. SSM小区人口管理系统毕业设计程序:一道经典的 Java Web 必修课
如果你是准备交计算机相关毕业设计、又不想把时间耗在前端框架上的学生,SSM 小区人口管理系统是一个绕不开的经典题目。它把 Spring、SpringMVC、MyBatis 三个框架揉进一个真实的业务场景里:给小区做人口底账,管住户的基础信息、房屋归属、入住迁出记录。系统不大,但“查询一个小区有多少常住人口、谁住哪栋楼、什么时候搬进来的”这些需求,恰好把你学过的 Java EE 知识串成一条完整链路。这套题的行业价值在于:它不要求高并发,不要求微服务,却要求你把三层架构、ORM、事务控制、页面传参全走一遍,答辩时每一层都能说出名堂。下文从原理到代码再到踩坑,直接按做毕设的顺序讲。这也是为什么我建议你把这个题目真正跑通一遍,而不是只留下一个打不开的记录。
2. SSM 框架的原理与选型:三层分工和值不值得为毕设坚持
SSM 是 Spring + SpringMVC + MyBatis 的缩写,本质是一个三层架构的落地组合。毕业设计里最常见的定位是:Spring 管对象和事务,SpringMVC 管 HTTP 请求的路由,MyBatis 管 SQL 和实体类的映射。很多同学一开始分不清“这三个框架分别做了什么”,往往把配置文件抄来抄去,结果项目能启动但说不清路径,答辩一追问就卡住。这一章先讲清每层的职责,再给你一个为什么毕设不当然选 SpringBoot 的判断依据。
2.1 Spring:对象装配与事务边界
Spring 在 SSM 里承担最基础也是最少被直观感受到的职责——容器。你在 service 层写的UserService,在 controller 里写的UserController,不再由new来创建,而是交给 Spring 容器实例化并注入依赖。配合事务管理器,当 service 方法里同时做“插入住户 + 更新楼栋房间状态”两件事时,任何一个失败都能整体回滚,这是小区人口管理系统里很关键的能力,因为人口数据一旦写坏很难回溯。
具体到代码,Spring 通常通过applicationContext.xml声明<context:component-scan>开启包扫描,再配置一个DataSourceTransactionManager。学生党最容易犯的错是只配了数据源而忘了配事务管理器,结果系统看起来能跑,但数据半写半不写的情况出现了,只说一句“没回滚”。建议你把事务配置作为验收第一天就要确认的点,因为它直接影响后续所有写操作的正确性。
从选型角度看,Spring 容器本身是这三个框架里最没有存在感但也最不能替换的部分。它不产生你肉眼可见的页面效果,可是所有 controller、service、mapper 的装配机制都依赖它。如果你在答辩时能说清楚“控制反转、依赖注入、事务传播机制”这三个词,一道高频问题就能扛住一半。
2.2 SpringMVC 与 MyBatis:请求路由和数据访问
SpringMVC 解决的是“浏览器发来/list?page=1,应该交给哪个方法处理”的问题。它在 web.xml 中配置DispatcherServlet做统一入口,再通过@RequestMapping把 URL 映射到 Controller 方法。和传统的 Servlet 相比,它把参数绑定、数据校验、视图解析都做了封装。比如前端表单提交一个name=张三,SpringMVC 可以直接把它绑定到Student对象的name属性上,不需要你自己request.getParameter()再 set。
MyBatis 则是把 Java 方法与 SQL 做了一个映射层,让你把 SQL 写在一个 mapper XML 文件里,而不是散落在 Java 字符串中。它不是一个完整的 ORM 框架,不像 Hibernate 那样自动生成 SQL,而是允许你手写 SQL 并指定参数和结果映射。对于小区人口管理系统这种查询场景复杂、字段多、连表频繁的项目,手写 SQL 比自动生成更可控。比如“按楼栋号统计人口”“查近三个月迁入人员”,这种统计型 SQL 在 MyBatis 里写几个<select>标签就能搞定,换成 Hibernate 的 HQL 反而不直观。
2.3 对比 SpringBoot:毕设选 SSM 的加分项与代价
很多 2024、2025 级的同学会来问我:既然 SpringBoot 更省事,为什么毕业设计还要用 SSM?我的看法是:如果你的选题是“小区人口管理”这种业务逻辑相对直白的管理系统,SSM 是加分的,不是落后的。这背后的原因有几个层面的考量。
第一是考核逻辑的需求。毕业设计评审看重“过程的完整性”而不是“技术的新鲜度”。SSM 要求你手动写 Spring 配置、手动处理事务和 mapper 扫描,这意味着你能讲出配置背后的原理;而 SpringBoot 的自动配置像黑匣子,答辩时如果只答“加个依赖就能跑”,就很难体现自己的工作量。第二是课时和资料环境的约束。很多学校的课程设计、毕业设计题目限定在 SSM 框架里,老师已经把这套流程沉淀下来,代码审查、论文撰写、运行环境都按这套来。你突然换成 SpringBoot,反而容易在环境问题上纠缠。
第三是性价比的判断。SSM 的配置确实比 SpringBoot 繁琐,但人口管理系统的功能量并不大。一个中等体量项目约 20 到 25 张表以内的复杂度,手动配置 XML 的时间成本完全可以接受。如果题目换成高并发商城或秒杀系统,我会劝你直接走 SpringBoot + 微服务,但人口管理不在此列。所以,除非学校明确允许 SpringBoot,否则按照 SSM 路线坚持下去,投入产出比更高,答辩亮点也更自然。
3. 系统设计与数据库建模:把小区人口这个题目拆成功能与技术方案
在写任何代码前,先把系统需求和数据库表设计定下来。很多同学一拿到题目就急着配框架、写登录,结果到中间发现“房屋和住户关系理不清”“统计字段缺失”,只能推翻重来。小区人口管理系统的核心业务并不复杂,但要表结构设计得能支撑“人口台账”和“房屋台账”两套数据,必须先把模块和字段说清楚。
3.1 模块划分:从人口底账到楼栋房屋,一个闭环怎么拆
按常见毕设文档的划分,我会把整个系统分成六个功能模块:用户登录与权限管理、小区楼栋房屋信息管理、住户信息管理、人口变动登记(迁入/迁出)、住户查询与统计、系统参数配置。
这六块的逻辑顺序是:先有楼栋和房屋,才能往里面安排住户;住户进来之后要登记迁入信息,离开时要登记迁出记录;在此基础上做按楼栋的人口统计,给物业或社区提供一个底账视图。登录和权限模块往往是页面最容易被拿来演示的部分,但数据库里不宜做太复杂,角色控制在“管理员、工作人员”两到三种就够了,表结构也不必设计成 RBAC 标准五表。
3.2 核心表结构:住户、楼栋、人口变动三张表的设计要点
拿住户表做例子,我最常给学生建议的字段设计是:主键id、姓名name、身份证号id_card、性别gender、联系电话phone、户籍所在地domicile、与户主关系relation_to_householder、房屋 IDhouse_id、入住时间check_in_time、状态status以及创建时间create_time。
这几处细节是论文答辩容易被问到的:身份证号是不是用 varchar,而不是 bigint,因为身份证长度超过 int,且会有前导零或是末位 X;与户主关系为什么不单独建表,因为人口管理里户主关系通常只是一个枚举或短文本,不必抬高到字典表层级;入住时间用 date 还是 datetime,如果业务上只需要精确到日期,用 date 更直白。
楼栋表建议保留楼层数floor_count、单元数unit_count、户数household_count这三个冗余字段。为什么要冗余?因为每次查询“这栋楼住满没有”时,多了一个文本字段可以从列表上直接展示,而不必二次 count 房屋表。代价是房屋数据变动时多两步代码,但对于毕设系统,性价比更高。房屋表则要存楼栋 ID、单元号、楼层、门牌号,以及当前入住人 IDoccupant_id,它指向住户表主键,形成闭环。
人口变动表是这个小系统里最能体现设计能力的一张表。字段包含变动类型(迁入、迁出、死亡、变更)、变动时间、涉及住户 ID、关联房屋 ID、变动说明。这张表保留了变化历史,统计“最近三个月迁入多少人”就不用去对比当前住户表的check_in_time,而是直接过滤变动表的记录。
3.3 表关系与字典设计:外键、状态字段与冗余的取舍
既然做关系型数据库,表之间的关联是绕不开的。设计这批表时,外键要不要物理存在,我的建议是“代码层保证,不强制加物理外键”。原因不复杂:物理外键让插入变更时必须维护约束一致,新增房屋、替换入住人时经常要按顺序操作;而实际开发中很多 MySQL 分库分表架构都不推荐物理外键,因为会影响在线 DDL 和写入并发。毕设用物理外键不是不可以,但你需要能解释表关系时为什么会设计house_id指向房屋表主键、occupant_id指向住户表主键。
状态字段这里给出三个建议值:住户状态可设为0-居住中、1-已迁出、2-暂离。房屋状态则用0-空闲、1-有人居住、2-维修中。注意这套状态值要在前后端一致,前端页面下拉框显示中文,后端代码里存数字,这是最常见的表结构落地方式。状态值本身属于字典数据,你可以建一张dict表存键值对,也可以直接在 JSON 或 Java 枚举里固定。对于毕设,用枚举类@Getter public enum HouseStatus维护即可,少一张表、少一层查询。
4. SSM 整合落地:从配置文件到一条住户记录的写入流程
前面章节搭好了理论框架,从这一章开始进入可复现代码。SSM 项目拿下来之后,我一般按四步走:先搭工程结构和依赖,再写 Spring 与 MyBatis 配置,然后按照“实体类—Mapper—Service—Controller”顺序写一条业务链路,最后把页面和参数连上。下面每一步都给代码和说明,你照着运行环境:JDK 8 / Tomcat 8.5 / MySQL 5.7(或 MySQL 8.0 注意驱动差异)即可正常跑通。
4.1 工程结构与依赖配置:SSM 整合的最小依赖清单
如果你用 Eclipse 或 IDEA,建议创建 Maven 的war打包工程,而不是普通 Java 工程。一个典型的 SSM 工程目录结构如下:
src/main/java ├── com.example.population │ ├── controller # SpringMVC控制层 │ ├── service # 业务层接口与实现 │ ├── mapper # MyBatis mapper接口 │ ├── entity # 实体类 │ └── common # 公共常量/工具类 src/main/resources ├── mapper # MyBatis XML ├── spring # Spring配置 └── mybatis-config.xml src/main/webapp ├── WEB-INF │ ├── web.xml │ ├── jsp # 页面 │ └── lib └── static # css/js/图片为什么把 mapper 接口和 XML 分开放?因为 MyBatis 的包扫描有两种常见做法:只扫接口、再把 XML 放在同包的 resources 目录下,或者用mapper-locations指定 XML 路径。分开放在/resources/mapper下更容易排查“接口存在但 SQL 找不到”的问题。pom.xml 的最小依赖清单如下:
<properties> <spring.version>5.3.3</spring.version> </properties> <dependencies> <!-- Spring核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- MyBatis与Spring整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <!-- Servlet与JSP --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>这段依赖里值得重点说的是mybatis-spring,它负责把 MyBatis 的SqlSessionFactory交给 Spring 容器管理,使 Mapper 接口能被自动代理注册成 Bean。如果缺失这一项,你在 Service 里注入UserMapper时会直接报找不到 Bean。spring-jdbc也是容易漏的,它提供了DataSourceTransactionManager事务支持,就算是纯 MyBatis 项目也建议保留。版本方面,Spring 5.3、MyBatis 3.5 是稳定配合。
4.2 Spring 与 MyBatis 配置:dataSource、mapper 扫描与事务
配置阶段是一堆小细节堆出来的,我把最常用的写法放在下面,你可以直接改库名账号密码来用。首先是 Spring 的applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 扫描service和mapper接口 --> <context:component-scan base-package="com.example.population.service"/> <context:component-scan base-package="com.example.population.mapper"/> <!-- 数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/population_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!-- SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.example.population.entity"/> </bean> <!-- Mapper接口扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.population.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="txManager"/> </beans>这段配置有三个高频雷点,值得提前说明。第一个是 URL 里的serverTimezone:如果你用 MySQL 8.0 且不设置时区,驱动会以 UTC 作为默认值,导致时间字段差 8 小时。加上serverTimezone=Asia/Shanghai是常规操作。第二个是useSSL=false,本地开发环境一般没有配置 SSL 证书,如果不加,在很多 MySQL 版本上面连接会直接提示SSL connection error。第三个是MapperScannerConfigurer的扫描包路径必须和 Mapper 接口所在包一致,否则接口注册不到容器里,Service 注入时会一片红。
4.3 一条链路的写码顺序:实体类、Mapper 接口、Service、Controller
以住户管理为例,按顺序建立四个文件。先写实体类Householder.java:
package com.example.population.entity; import java.util.Date; public class Householder { private Integer id; private String name; private String idCard; private String gender; private String phone; private String domicile; private String relationToHouseholder; private Integer houseId; private Date checkInTime; private Integer status; // getter/setter 省略 // 建议用 Lombok @Data,但纯手写也有好处:答辩可说清字段与数据库列的映射 }这里要注意,Java 的idCard是驼峰命名,数据库列名id_card是下划线命名。MyBatis 默认开启驼峰映射,但必须在mybatis-config.xml里配置<setting name="mapUnderscoreToCamelCase" value="true"/>,否则查询结果里这个字段会是 null。这是新手最常见的“数据对不上”来源,且不会报任何异常。
然后是 Mapper 接口HouseholderMapper.java:
package com.example.population.mapper; import com.example.population.entity.Householder; import java.util.List; public interface HouseholderMapper { int insert(Householder householder); int update(Householder householder); int deleteById(Integer id); Householder selectById(Integer id); List<Householder> selectList(Householder query); }对应的 XMLHouseholderMapper.xml里的插入语句长这样:
<mapper namespace="com.example.population.mapper.HouseholderMapper"> <insert id="insert" parameterType="Householder" useGeneratedKeys="true" keyProperty="id"> INSERT INTO householder ( name, id_card, gender, phone, domicile, relation_to_householder, house_id, check_in_time, status ) VALUES ( #{name}, #{idCard}, #{gender}, #{phone}, #{domicile}, #{relationToHouseholder}, #{houseId}, #{checkInTime}, #{status} ) </insert> </mapper>写这段 XML 有三个习惯你可以直接保留。第一,useGeneratedKeys="true"加keyProperty="id",这样插入后实体类马上获得自增主键,后面做人口变动登记时能直接取householder.getId()。第二,parameterType写的是Householder,靠的是typeAliasesPackage配置,就不用写全限定名。第三,所有占位符用#{},不要用${}。#{}会走预编译,能防 SQL 注入;而${}是字符串拼接,虽然在排序字段上方便,但一旦业务里有用户可控输入,这就是一个灾难。毕设答辩老师问到“你如何防 SQL 注入”时,这条回答会非常加分。
Service 层在接口外再包一层实现,是为了体现业务边界和事务粒度,也是毕设论文里“业务设计”部分的重要素材。比如登记入住不能只往住户表插入,还要同步更新房屋状态,这就是一个事务方法:
package com.example.population.service.impl; import com.example.population.entity.Householder; import com.example.population.mapper.HouseholderMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class HouseholderServiceImpl { @Autowired private HouseholderMapper householderMapper; @Transactional public void checkIn(Householder householder, Integer houseId) { householder.setHouseId(houseId); householder.setStatus(0); // 0-居住中 householderMapper.insert(householder); // 这里可额外调用房屋Mapper更新房屋状态 } }Controller 层负责接住页面参数并返回视图:
package com.example.population.controller; import com.example.population.entity.Householder; import com.example.population.service.impl.HouseholderServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/householder") public class HouseholderController { @Autowired private HouseholderServiceImpl householderService; @RequestMapping("/list") public ModelAndView list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer rows) { ModelAndView mv = new ModelAndView("householder/list"); // 调用分页查询,这里省略具体实现 return mv; } }Controller 层需要注意一个点:@RequestParam(defaultValue = "1")解决了前端没带参数时后端报 400 的问题。实际开发中几乎每次翻页请求都会带 page,但用户首次访问、从导航菜单点进来时不会有 page,所以 defaultValue 是保底。另外也可以用@Autowired注入,虽然 IDEA 会提示用构造器注入更佳,但毕设项目里@Autowired简洁直观,答辩提问时也更常见。
4.4 页面与参数绑定:JSP 的表单提交与日期格式处理
人口管理系统的前端多数是 JSP + JSTL,重点不在美观而在功能演示。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <form action="${pageContext.request.contextPath}/householder/save" method="post"> <input type="text" name="name" placeholder="姓名"/> <input type="text" name="idCard" placeholder="身份证号"/> <input type="text" name="gender" placeholder="性别"/> <input type="text" name="phone" placeholder="联系电话"/> <input type="text" name="checkInTime" placeholder="入住时间 yyyy-MM-dd"/> <button type="submit">保存</button> </form>当 Controller 方法参数直接写Householder householder时,SpringMVC 会自动按同名属性绑定。但有一个日期类型需要处理:如果页面传的是2025-04-01,而实体类是Date,SpringMVC 会因为类型转换失败报 400。解决方式是在 Controller 里加一个@InitBinder:
@InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true)); }这段代码的作用是在请求到达 Controller 前,把字符串按指定格式解析成 Date。CustomDateEditor的第二个参数true表示允许空值,这样前端不填入住时间时不会直接抛异常。这个小细节在答辩演示时很加分,因为很多同学在日期上翻车后会直接放弃这个字段,而你没有。
5. 毕设调试常见问题与排查:四件让人翻车的事
SSM 毕设从 0 到 1 的过程中,新手遇到的上错问题其实很集中。我把它按“现象—原因—解决”整理出来,每一条都是反复出现的高频坑。这一章的用途是:当你照着上面代码敲完却跑不起来,按下列症状逐条对,可以省下大量瞎试的时间。
5.1 现象:什么都不报错,页面就是白屏或 404
只用地址访问/householder/list,但页面始终 404 或白屏的,要先分两类:白屏,说明请求进到了 Controller 或视图解析失败;404,说明请求根本没找到映射方法。最常见的原因是 SpringMVC 的DispatcherServlet没拦截到.do或/,或者在 web.xml 中配置的<url-pattern>路径和访问路径不一致。
另一个隐蔽原因是 JSP 文件放错位置。JSP 应该放在/WEB-INF/jsp/下,视图解析器配置了前缀为/WEB-INF/jsp/、后缀为.jsp,而你把页面放在了/webapp根目录。放在根目录也能被访问,但会被 Tomcat 当作静态资源直接返回源码或下架。
排查时先看 IDEA 的 Console 有没有 Spring 容器初始化日志,再看 Tomcat 部署后target/目录里有没有对应的 class 和 JSP。这两步能筛掉八成白屏问题。
5.2 现象:SQL 执行成功但参数全是 null
页面提交数据后,数据库里确实插入了一条记录,但 name、phone 等字段全是 null。这个坑十有八九是 MyBatis 的驼峰映射没开。你数据库列是id_card,实体类是idCard,MyBatis 默认不转换,结果赋值失败。解决是在mybatis-config.xml里开一个设置:
<configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>注意这个配置文件要在SqlSessionFactoryBean中通过configLocation引入,否则写了也不生效。如果不想开全局映射,也可以在每个 XML 的<resultMap>里手动写column="id_card" property="idCard",麻烦一点但更精确。
5.3 现象:日期字段格式化不对,页面上显示 2025-04-01 08:00:00
JSP 列表页显示入住时间时,Date类型默认渲染成Tue Feb 04 00:00:00 CST 2025,非常难看。这是 Java 的Date.toString()默认行为。最常见做法是在 JSP 上用 JSTL 的<fmt:formatDate>标签控制格式,而不是改后端代码:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <fmt:formatDate value="${item.checkInTime}" pattern="yyyy-MM-dd"/>另一种情况是你后台查出的时间整体差了 8 小时,那问题就在数据库连接 URL 的时区设置。MySQL 8.0 默认驱动连接时使用服务区时区,加serverTimezone=Asia/Shanghai可以解决。你自己库里的时间字段如果是datetime类型,在 JDBC 驱动的useTimezone配置不当时也会出现偏差,但毕设一般用不到这么深,多数是输出格式问题。
5.4 现象:列表数据对不上或重复
住户列表和房屋列表数据对不上,最常见的源头是连表查询时没有用LEFT JOIN而用了JOIN。比如查入住信息时,如果某条住户记录没有配套房屋数据,内连接会直接把整行过滤掉。改为LEFT JOIN后,住户记录保留,房屋字段为空,页面再处理空值展示。
数据重复则和deleteById有关。如果只在住户表删除,没有清理人口变动表里的关联记录,再查统计时旧数据又冒出来。所以我建议你在删除住户时连着把population_change表里对应记录一并逻辑删除,或是在变动表里把状态置成“已失效”。另外前端分页时,如果用了distinct但 select 的字段不入 group by,MySQL 5.7 以下对ONLY_FULL_GROUP_BY模式会直接报错,如果没报错则可能查出重复,需要谨慎。
5.5 现象:登录后一刷新就退出,Session 失效
小区人口管理系统一般有登录功能,学生经常遇到登录后一切正常,但刷新页面就退出登录的问题。常见原因有两个。一个是 Tomcat 的 session 默认超时时间被设置得太短,检查web.xml里的<session-config><session-timeout>30</session-timeout></session-config>,若是0或未配置,回话在浏览器关闭时直接丢失。另一个原因是 controller 里用了重定向,比如登录成功return "redirect:/index"后把 session 里存的用户信息丢了。排查时写一个专门打印 session 的接口来验证,或者直接在 JSP 上 Display sessionId,刷新看有没有变化。
6. 从能跑到答辩:验证方法、加分改动与收尾习惯
项目跑通只是第一步,毕业设计真正拉开差距的地方在验证与收尾。这一章不讲新功能,只讲如何在交之前确认系统没留下明显破绽,以及哪些小改动最有答辩性价比。
先把验证清单拉出来:不能只演示新增和列表,要按用户主流程走一遍。我的习惯是准备一份测试数据脚本,包含 3 栋楼、20 间房和 15 位住户,其中覆盖一个“户主”、一个“配偶”、一个“租户”。这样做的好处是演示时有真实感,统计结果不会都是 0,页面也不至于空荡。验证顺序按“登录—新增楼栋—新增房屋—迁入住户—查询列表—编辑信息—迁出住户—查看统计”这条线走,每一步都同时记录编号、时间和前后状态。
加分改动我首推在首页加一个人口统计卡片。用 ECharts 或 Chart.js 展示“各楼栋常住人口柱状图”和“月度迁入迁出折线图”,这部分的接口是统计型 SQL,比如按楼栋 group by 住户的house_id,按月份 group by 变动表的change_time。技术上并不难,但视觉和答辩冲击力远高于普通 CRUD,而且能自然引出 MyBatis 动态 SQL 中的<foreach>和日期函数用法。
另一个高性价比改动是扩展导出功能。在列表页加一个“导出 CSV”按钮,后端生成 CSV 文件返回浏览器即可,不需要引入复杂 POI。严格来说这不属于核心旧需求,但很多评审老师会现场点导出验证文件是否真的能打开。只需一行response.setHeader("Content-Disposition", "attachment;filename=population.csv")加一段流式输出就足够。
收尾习惯方面,记得在论文和现场演示中固定一套操作路线,不要现场现想。我个人的习惯是把测试数据和演示路径打印出来贴在电脑边上,确保哪怕紧张也能按步骤走完;同时把数据库备份脚本和一个README.md放进项目根目录,内容写清建库语句、初始账号和运行环境。这几样不花多少时间,却能直接证明项目的完整性。
希望你做这个项目时能比我当年更早会做数据库备份。我第一次交毕设就是因为数据库里测试数据太乱,演示现场查不出可展示的记录,当场有些狼狈。后来养成每天备份一次的习惯,再没在演示时翻过车。按这条链路走下来,愿你把这个做过的基础项目磨得更熟,希望帮到你。
本文还有配套的精品资源,点击获取