
简介面向Java初学与毕业设计人群的房屋租赁管理系统完整项目包基于SpringBoot框架开发覆盖管理员、房东、用户三类角色包含房源发布、信息审批、订单管理等核心业务可用于课程设计、毕业设计或JavaWeb开发练习。资源共913个文件以Java源码、Vue前端、JS脚本、CSS样式、SQL数据库脚本为主同时提供论文文档与答辩PPT压缩包大小仅24.22MB结构清晰便于按模块学习。目前已有63人学习下载适合需要完整可运行项目参考的开发者。资源内含安装运行批处理脚本、数据库初始化文件以及源码工程配置论文目录涵盖绪论、开发环境、系统分析、数据库设计、功能实现与系统测试等完整章节可快速理解房屋租赁系统从需求分析到上线测试的整个过程帮助读者掌握SpringBoot与JSP技术整合开发并为毕业设计答辩提供直接素材。1. 一个能跑、能写论文、能答辩的 Spring Boot 房屋租赁系统值不值得做“基于 Spring Boot 的房屋租赁系统”在 Java 毕设里的热度一直不低但“源码论文ppt答辩”这个标题的完整含义不是“写个网页”而是同时交付三件套一套能演示的 Spring Boot 工程、一篇和代码对得上号的毕业论文、一份答辩时能讲清逻辑的 PPT。对正在选毕设题目的学生来说这个方向的最大优点是业务边界清楚房屋、租客、合同、账单、到期提醒每个模块都能独立演示也能串成一条租赁业务闭环。对想快速上手 Spring Boot 的开发者它又是一套标准的控制器-服务-数据访问分层练习。这篇笔记不打算复刻某份现成源码而是按最常见可靠的实现路径把表结构、核心代码、论文组织和演示避坑一次讲透。2. 先立骨架再写代码Spring Boot 房屋租赁系统的表结构与接口规划2.1 技术选型为什么这套组合对毕设体量刚刚好Spring Boot 是当前 Java Web 毕设里最主流的框架选项原因是自动配置、起步依赖和内嵌 Tomcat 把项目从“要写一堆配置才能启动”变成“加了依赖就能跑”。房屋租赁系统按功能模块拆有房屋管理、租客管理、合同管理、账单管理、统计看板五块CRUD 占大多数真正的业务规则集中在签合同、算租金、查到期这三处。因此最常见的组合是 Spring Boot MyBatis-Plus MySQL 三件套前端用 Thymeleaf 服务端渲染或者用 Vue 做前后端分离。这里要提前做决定的是前端方向。Thymeleaf 能把控制器方法直接映射到页面省去跨域、Token 传输这些额外讲解写论文时“控制层-页面”的对应关系非常直白Vue 则要多写接口联调和前端路由工作量更大但更贴近企业项目。毕设阶段如果时间紧我一般建议用 Thymeleaf 起步把预算留给库存判断这类业务逻辑。更重的 Spring Cloud 和老旧的 SSH 框架都不是这个题目的最优解前者要解释“为什么小系统要做服务治理”后者在“框架应用”这一栏显得单薄且找不到新意。项目结构按标准三层来走controller 接收 HTTP 请求service 放业务规则mapper 管数据访问。实体类放在 entity请求和响应对象单独放 dto、vo。包名按 com.example.house 这种方式组织不要把所有类塞进一个包否则后面论文里的功能模块图会很难画。这个结构在答辩时基本不会被挑战因为它是 Spring Boot 社区最通用的分层方式。2.2 核心表结构房、客、合同、账单四张表的建表 SQL建表是动手第一件事也是最容易返工的一步。房屋租赁系统的核心不是“房子”而是“合同”因为租金、账单、到期提醒全部从合同派生。所以表结构至少要覆盖四张核心表房屋表记录房源基础信息和当前状态租客表记录承租方信息合同表把房屋、租客、起止时间和成交租金关联起来账单表按合同按月生成应缴和实缴记录。-- 房屋表核心字段与状态 CREATE TABLE house ( id BIGINT AUTO_INCREMENT PRIMARY KEY, community VARCHAR(50) NOT NULL COMMENT 小区名称, building_no VARCHAR(10) COMMENT 楼栋号, room_no VARCHAR(20) NOT NULL COMMENT 房号, area DECIMAL(6,2) COMMENT 建筑面积(㎡), layout VARCHAR(20) COMMENT 户型如两室一厅, monthly_rent DECIMAL(10,2) NOT NULL COMMENT 月租金(元), status TINYINT DEFAULT 0 COMMENT 0未出租 1已出租 2维修中, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_house_unit (community, building_no, room_no) ); -- 租客表字段按登记和签约需要设计 CREATE TABLE tenant ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL COMMENT 姓名, phone VARCHAR(20) NOT NULL COMMENT 手机号, id_card VARCHAR(18) COMMENT 身份证号演示系统建议脱敏, emergency_contact VARCHAR(30) COMMENT 紧急联系人, emergency_phone VARCHAR(20) COMMENT 紧急联系电话, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 合同表状态与租金快照是重点 CREATE TABLE lease_contract ( id BIGINT AUTO_INCREMENT PRIMARY KEY, contract_no VARCHAR(32) NOT NULL COMMENT 合同编号, house_id BIGINT NOT NULL COMMENT 房屋ID, tenant_id BIGINT NOT NULL COMMENT 租客ID, start_date DATE NOT NULL COMMENT 起租日期, end_date DATE NOT NULL COMMENT 退租日期, monthly_rent DECIMAL(10,2) NOT NULL COMMENT 成交月租租金快照, deposit DECIMAL(10,2) DEFAULT 0 COMMENT 押金金额, status TINYINT DEFAULT 1 COMMENT 1生效中 2已退租 3已到期, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_contract_no (contract_no), KEY idx_house_dates (house_id, start_date, end_date) ); -- 账单表唯一键防止同一合同同月重复生成 CREATE TABLE bill ( id BIGINT AUTO_INCREMENT PRIMARY KEY, contract_id BIGINT NOT NULL COMMENT 合同ID, bill_month CHAR(7) NOT NULL COMMENT 账单月份如2026-03, amount DECIMAL(10,2) NOT NULL COMMENT 应收金额, water_fee DECIMAL(8,2) DEFAULT 0 COMMENT 水费, power_fee DECIMAL(8,2) DEFAULT 0 COMMENT 电费, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付, paid_time DATETIME COMMENT 实付时间, UNIQUE KEY uk_contract_month (contract_id, bill_month) );这段 SQL 里有三个设计值得在论文里展开。房屋表的唯一键 uk_house_unit 把“同一小区同一楼栋同一房号不能重复录入”交给数据库兜底比在 service 里先 selectCount 再判空更可靠合同表的 monthly_rent 是租金快照合同签订那一刻把房屋表当前的月租拷贝过来之后房屋调价不影响已签合同的对账账单表的 uk_contract_month 唯一键保证定时任务重复跑、按钮被连点两次都不会生成两份同月账单。金额字段一律用 DECIMAL 而不是 DOUBLE这是新手项目里最常见的坑之一。DOUBLE 在浮点计算时会出现 0.10.2 不等于 0.3 的精度问题租金、押金一旦出现误差演示时填 3400 元房租实际存成 3399.9999 就很尴尬。日期字段用 DATE 记录起止日期DATETIME 记录创建和实付时间。另外补一张 admin 表做后台登录字段只有 id、username、password_hash密码用 BCrypt 哈希后入库用拦截器校验登录态即可不必上 Spring Security否则配置比业务还多。2.3 接口规划先列 API 再写代码论文的模块图直接照这个画建完表之后、动手写 Controller 之前我建议先花半小时把 API 清单列出来。这个习惯的好处是论文第 4 章“功能模块设计”的图就是这张清单的图形化答辩时评委问“系统有哪些功能”也可以直接对着接口回答。房屋租赁系统按模块拆最少需要这些接口功能模块接口方法说明房屋管理/api/house/pageGET分页查询支持状态和关键字过滤房屋管理/api/housePOST / PUT新增/编辑房屋房屋管理/api/house/{id}DELETE仅限未出租房屋逻辑删除租客管理/api/tenant/pageGET租客分页列表租客管理/api/tenantPOST / PUT新增/编辑租客合同管理/api/contractPOST签订合同带时间重叠校验合同管理/api/contract/{id}/terminatePUT退租释放房屋账单管理/api/bill/generatePOST手动触发当月账单生成账单管理/api/bill/pageGET账单分页查询看板统计/api/dashboard/summaryGET出租率、应收/实收汇总接口返回统一封一层 ResultT包含 code、message、data 三个字段。代码里正常路径写 return Result.ok(...)业务异常直接 throw new BizException(该时间段已有生效合同)再配一个全局异常处理器统一定制返回。这样的好处是前端不用针对每个接口做异常分支答辩时“统一返回结构 全局异常处理”是一个能单独拎出来讲的亮点。3. 从查询到合同生成租赁核心业务的实现要点与关键代码3.1 房屋分页查询MyBatis-Plus 分页插件与条件查询房屋列表是系统打开后的第一个页面功能很基础但“分页 关键字 状态过滤”组合在一起就足够演示 Spring Boot 里最常用的一套查询写法。用 MyBatis-Plus 的 Page 对象和 LambdaQueryWrapper 实现GetMapping(/api/house/page) public ResultIPageHouseVO page( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Integer status, RequestParam(required false) String keyword) { // 1. 创建分页对象页码从1开始 PageHouse page new Page(pageNum, pageSize); // 2. 构建条件状态精确过滤关键字模糊匹配 LambdaQueryWrapperHouse wrapper new LambdaQueryWrapper(); if (status ! null) { wrapper.eq(House::getStatus, status); } if (StringUtils.hasText(keyword)) { wrapper.and(w - w.like(House::getCommunity, keyword) .or().like(House::getRoomNo, keyword)); } wrapper.orderByAsc(House::getId); // 3. 执行分页查询Page对象会接收total等分页数据 IPageHouse housePage houseMapper.selectPage(page, wrapper); // 4. 转成VO后返回避免直接暴露实体字段 return Result.ok(housePage.convert(house - houseService.toVO(house))); }参数上有几个细节。pageNum 默认 1pageSize 默认 10前端不传也能跑通这是接口健壮性的基本要求。keyword 用 StringUtils.hasText 而不是字符串 equals 判断能同时排除 null 和空字符串。LambdaQueryWrapper 通过方法引用来指定字段字段名在编译期就能被发现写错这也是论文里“为什么选 MyBatis-Plus”时的加分回答。关键点在第 3 步MyBatis-Plus 的分页不是自动生效的必须在配置类里注册分页插件否则 selectPage 会退化成查全表再内存分页。配置代码如下Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 指定MySQL方言分页SQL才会带上LIMIT interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }注册之后观察控制台打印的 SQL能看到 LIMIT 关键字这才叫分页真正生效。这个配置要放在一个带 Configuration 的类里而不是写在 Controller 内部否则扫描不到。3.2 签订合同时间重叠校验与事务边界合同模块是整个系统的业务核心答辩时评委大概率会问“同一间房会不会被重复租出去”。这个问题在代码上分三层防护第一层校验房屋状态只有“未出租”才能签新合同第二层做时间区间重叠校验同一房屋不能有两份生效合同的时间段相交第三层靠数据库唯一索引兜底。签合同的核心方法Transactional(rollbackFor Exception.class) public Long createContract(ContractCreateDTO dto) { // 1. 房屋状态校验只有未出租才能签 House house houseMapper.selectById(dto.getHouseId()); if (house null || house.getStatus() ! 0) { throw new BizException(该房屋当前不可出租); } // 2. 时间重叠校验新合同起点早于已有合同终点且新合同终点晚于已有合同起点 LambdaQueryWrapperContract wrapper new LambdaQueryWrapper(); wrapper.eq(Contract::getHouseId, dto.getHouseId()); wrapper.eq(Contract::getStatus, 1); wrapper.le(Contract::getStartDate, dto.getEndDate()); wrapper.ge(Contract::getEndDate, dto.getStartDate()); Long count contractMapper.selectCount(wrapper); if (count ! null count 0) { throw new BizException(该时间段已有生效合同请勿重复出租); } // 3. 租金快照成交租金以签订时房屋租金为准 Contract contract new Contract(); contract.setContractNo(generateContractNo(house.getId())); contract.setHouseId(house.getId()); contract.setTenantId(dto.getTenantId()); contract.setStartDate(dto.getStartDate()); contract.setEndDate(dto.getEndDate()); contract.setMonthlyRent(house.getMonthlyRent()); contract.setStatus(1); contractMapper.insert(contract); // 4. 房屋状态置为已出租 houseMapper.updateById(House.builder().id(house.getId()).status(1).build()); return contract.getId(); }时间重叠那段 SQL 是整个方法里最需要理解的地方新合同的开始日期小于等于已有合同的结束日期且新合同的结束日期大于等于已有合同的开始日期只要查出来有记录就说明时间区间相交。这个条件初看别扭画一条时间轴就清楚了。A 合同从 2026-03-01 到 2026-08-31B 合同想签 2026-07-01 到 2026-12-31B 的 startDate 2026-07-01 小于 A 的 endDate 2026-08-31同时 B 的 endDate 2026-12-31 大于 A 的 startDate 2026-03-01于是被拦截。如果条件写错边界日期就会出现重复出租比如旧合同结束当天被新合同占用。事务注解 Transactional(rollbackFor Exception.class) 把“插合同”和“改房屋状态”绑成一个原子操作中间任何一步抛异常前面已经执行的 insert 或 update 全部回滚。这里特别要注意 rollbackForSpring 默认只在遇到 RuntimeException 时回滚如果方法里抛的是自定义受检异常不加 rollbackFor 就会事务不生效合同插进去了但房屋状态没有改成已出租。这个坑属于“看日志看不出问题看数据才知道坏了”的典型现场。3.3 按月账单与到期提醒定时任务怎么写不翻车房屋租赁系统做完 CRUD 之后让评委觉得“有业务深度”的是自动化和提醒。账单按月生成、合同到期提醒都可以用 Spring Boot 自带的 Scheduled 注解实现不需要引入 Quartz。一个典型的账单生成任务// 每月1号凌晨2点执行分0秒0 时2 日1 Scheduled(cron 0 0 2 1 * ?) public void generateMonthlyBill() { LocalDate today LocalDate.now(); // 只处理还未到期、且状态为生效中的合同 ListContract activeContracts contractMapper.selectList( new LambdaQueryWrapperContract() .eq(Contract::getStatus, 1) .ge(Contract::getEndDate, today)); for (Contract contract : activeContracts) { Bill bill new Bill(); bill.setContractId(contract.getId()); bill.setBillMonth(YearMonth.now().toString()); bill.setAmount(contract.getMonthlyRent()); bill.setStatus(0); // 待支付 try { billMapper.insert(bill); } catch (DuplicateKeyException e) { // 唯一键兜底重复执行任务时跳过已生成的账单 log.warn(账单已存在跳过 contractId{} month{}, contract.getId(), bill.getBillMonth()); } } }cron 表达式 “0 0 2 1 * ?” 拆开是秒 0、分 0、时 2、日 1、月 *、星期 ?也就是每个月 1 号凌晨 2 点执行。选凌晨 2 点而不是 1 点是因为很多 MySQL 实例把定时备份也放在凌晨避开高峰期可以减少锁表冲突这个细节写进论文“非功能需求”一节会显得考虑过生产环境。任务里先查“生效中且结束日期大于等于今天”的合同避免给已经退租的合同继续生成账单。账单为什么会重复生成定时任务跑第二次、人为手动触发、接口被重复请求都可能让同一个月同一份合同出现两条账单。前面表结构里的 uk_contract_month 唯一键在这里兜底第二次 insert 抛 DuplicateKeyException代码捕住异常打一条 warn 日志跳过。如果不在代码里捕异常定时任务会因为这条重复记录中断后面合同的账单全部生成不了。到期提醒的思路更简单每天上午 8 点扫一次合同把 30 天内到期的生效中合同筛出来逐个生成未读提醒Scheduled(cron 0 0 8 * * ?) public void scanExpiringContracts() { LocalDate deadline LocalDate.now().plusDays(30); ListContract expiring contractMapper.selectList( new LambdaQueryWrapperContract() .eq(Contract::getStatus, 1) .le(Contract::getEndDate, deadline)); expiring.forEach(c - reminderService.add( c.getContractNo(), 合同将于 c.getEndDate() 到期)); }数据量上去以后扫描全表会变慢可以给 lease_contract 的 (status, end_date) 加联合索引让查询走索引而不是全表扫描。毕设阶段在论文“数据库优化”一节提这个联合索引是很扎实的一笔。4. 论文和 PPT 答辩怎么跟代码对上把三件套做成一件事4.1 论文章节安排每个章节分别对应源码里的哪一部分很多项目代码写完论文才发现无从下笔本质原因是代码和文档没有在同一节奏上推进。其实毕业论文的结构高度固定房屋租赁系统这种业务型题目尤其好写按照下面这张表来填内容章节和源码的对应关系就很清楚论文章节写什么对应源码/材料摘要一句话概括系统目标、技术栈、功能模块和结论写完后对照代码核对每个功能点绪论背景与意义、国内外现状、研究内容房屋租赁市场规模、中介管理痛点、为什么选 Spring Boot需求分析功能性需求、非功能性需求、用例图接口清单拆成用例系统设计总体架构、功能模块图、数据库 ER 图、核心表设计建表 SQL 和模块划分系统实现每个功能模块一个页面 核心代码片段房屋管理页、合同签订流程、账单定时任务系统测试测试环境、测试用例表、测试结果JUnit 用例 手工演示记录总结与展望做了什么、达到什么效果、还能怎么改权限细化、报表导出、消息推送论文第 4 章“系统实现”的每个小节都配一张“运行截图 一段核心代码 两段解释”。截图像素要清晰代码不长于 15 行。截图右上角的浏览器标签页里最好露出系统标题避免被看出是网上下载的套图。代码从自己项目里复制答辩时老师问“这段代码在哪个类里”能打开 IDEA 现场指出来才算数。需求分析章节最容易写成“百度百科搬运”。更可靠的写法是把自己的接口清单翻译成用例把 POST /api/contract 展开成“租客选中房源、选择起止日期、系统校验时间冲突、生成合同、置房屋为已出租”这样带前置条件和后置条件的用例描述。这样写出来既是论文内容也是答辩时讲解脚本的一部分。4.2 PPT 的 12 页结构演示 Demo 配哪几页讲最顺答辩 PPT 控制在 12 页左右每页一个主题只放结论不放大段文字。房屋租赁系统的页面分布可以按下面这张表来组织核心思想是“演示和讲稿穿插”不要一口气讲完 10 页 PPT 再开 Demo。页码页面内容讲法/配合1封面题目、姓名、学号、导师2研究背景与目标房屋租赁管理痛点30 秒讲完3技术选型放项目结构截图讲 Spring Boot 和 MyBatis-Plus 的选型理由4系统功能架构五个功能模块图讲模块间关系5数据库设计ER 图讲租金快照和账单唯一键6核心业务合同签订流程讲时间重叠校验7系统演示一房屋管理、分页查询、关键字过滤8系统演示二合同签订、账单生成、到期提醒9核心代码时间重叠校验 事务注解对应第 6 页10系统测试测试用例表11总结与展望做完了什么还能做什么12致谢/QA快速收尾准备回答问题演示时自己手输 URL 容易紧张建议提前把核心页面加到浏览器书签。数据要“像真的”十套房源分布在 3 个小区租金在 800 到 6000 之间拉开梯度租客姓名用常见的真人感名字别用 test01、test02。演示房屋管理时切到第 4 页功能架构演示合同签订时切回第 5 页 ER 图观众看着界面上的状态变化再对照架构图听你讲信息才接得住。4.3 答辩高频问题为什么、怎么办、哪里体现答辩本质是验证“代码是不是你写的、系统有没有想清楚”。围绕 Spring Boot 房屋租赁系统被问频率最高的几个问题回答方向整理如下高频问题回答要点为什么选 Spring Boot自动配置 起步依赖减少样板配置内嵌 Tomcat 一条命令启动结合项目里用到的 starter为什么用 MyBatis-Plus单表 CRUD 不用手写 SQL分页插件统一处理分页LambdaQueryWrapper 防止字段名写错同一间房会不会重复出租三道防线房屋状态、时间重叠查询、唯一键兜底时间重叠条件画时间轴解释到期提醒怎么实现Scheduled 定时扫描加状态和截止日期联合索引演示定时任务日志金额为什么用 DECIMALDOUBLE 有浮点精度误差金额场景不接受 3399.9999为什么把成交租金存到合同表租金快照房屋调价不影响历史合同和账单对账事务加在哪里签订合同方法上Transactional(rollbackFor Exception.class)插合同和改状态必须同生共死用户量变大了怎么办先答优化点Redis 缓存热门房源、账单生成改队列异步、读写分离不要硬说自己做过测试是怎么做的讲测试用例表正常租房、时间重叠、非法房态、重复生成账单四个场景密码安全有没有考虑登录密码 BCrypt 加密不存明文管理员表单独做权限校验答不清的时候就用代码说话。比如时间重叠问题直接翻到 Service 类里那几行 selectCount比口述十句话都有说服力。5. 常见问题排查Spring Boot 房屋租赁系统的几个经典翻车现场5.1 Spring Boot 版本太高JDK 8 环境直接启动失败现象本地 JDK 8 启动 Spring Boot 项目Maven 构建阶段就报错或者启动过程中抛出 java.lang.UnsupportedClassVersionError以及大量 javax.servlet 相关类找不到的异常。原因Spring Boot 3.x 系列的最低要求是 JDK 17包名也从 javax 迁移到了 jakarta。很多毕设电脑默认装的是 JDK 8网上部分教程和源码包还在用 2.x两边一撞启动日志里全是类找不到。解决把项目固定在 Spring Boot 2.7 系列父 POM 不要引 3.x 的版本确认 IDE 的 Project SDK 选的是本地 JDK 8。2.7 系列对自动配置、starter 机制的支持很完整写论文完全够用。快速检查方式看项目里 import 的是 javax.servlet 还是 jakarta.servlet后者说明版本进入 3.x 时代了。5.2 分页接口查出全部数据Page 参数形同虚设现象调用 /api/house/page 传 pageNum2pageSize5返回结果却依然是前 10 条或全部数据total 字段也不对。原因MyBatis-Plus 的分页插件没有注册。selectPage 依赖配置类里的 PaginationInnerInterceptor 来改写 SQL没有插件时只能做“查全表 内存分页”的效果数据库层面没有 LIMIT数据一多就崩。解决在配置类里注册 MybatisPlusInterceptor并显式传入 DbType.MYSQL。注册后观察控制台打印的 SQL能看到 LIMIT 关键字这是分页真正生效的标志。另一个排查点是确认 Controller 方法返回的是 IPage 而不是 List也别在调用 selectList 之后手动 new Page 却不传参。5.3 合同起止日期和账单月份错位退租当月应收算不清现象合同从 2026-03-15 到 2027-03-14系统每月 1 号按整月生成 3400 元账单。退租结算时2027-03 只住了 14 天账单却是整月 3400 元于是出现“多收退租当月、少算首月实际天数”的对不上账。原因账单按自然月生成而合同按签约周期起租两者天然不对齐。如果首尾月不做按天折算就会出现多收少退。解决把业务规则在论文里写清楚首月按实际入住天数折算尾月按实际住满天数结算中间月份按整月生成。账单生成代码里让 contract 的 start_date 参与计算首月金额 月租金 / 当月总天数 × 实际入住天数而不是直接 setAmount(月租金)。退租模块也要配套一个“按本月已住天数生成退租账单”的方法否则演示到退租时会当场对不上数。5.4 接口直接返回实体类JSON 序列化循环引用导致数据丢失现象合同列表接口返回后前端渲染的房屋信息和租客信息要么是 null要么控制台直接报序列化错误日志里出现 infinite recursion 或 Could not write JSON。原因实体类上加关联字段比如 Contract 实体里放了一个 House 对象House 对象里又引用 ListContractJackson 序列化时陷入循环。解决所有返回给前端的数据走 VOController 的返回类型不要写实体类。ContractVO 里放 houseId、community、roomNo、tenantName 这类扁平字段在 Service 层组装数据。这个做法同时解决了“实体字段直接暴露给前端不安全”的问题答辩时提到 VO/DTO 分离又是一处加分点。6. 把系统做厚从“能跑”到“能答辩、能演示”的验证技巧写代码到一半的时候我习惯先把“演示剧本”列出来系统打开后先给评委看哪个页面、点哪个按钮、预期出现什么数据。没有剧本的演示最容易在答辩现场冷场——点开房屋管理发现只有两条测试数据或者定时任务还没到执行时间评委看不出系统在跑什么。一个实用的做法是准备一套“最小但像真”的演示数据3 个小区共 10 套房4 个租客2 份生效合同加 1 份历史到期合同账单覆盖最近 3 个月。数据量少不代表失真关键在数据之间有逻辑关系某套房状态是“已出租”对应的合同恰好处于生效期某间房在 5 天前刚到期提醒列表里就躺着一条到期告警。演示时先讲数据再讲操作界面上的每个状态都对应得上前面的表设计。验证工作不要只靠浏览器手点。我一般会在项目里补三个最小化的单元测试合同时间重叠校验构造两份时间交叉的合同断言抛异常、账单重复生成同一个 contract_id 和 bill_month 插两次断言第二次失败、到期提醒查询把合同结束日期设为 10 天后断言列表长度。这三个测试能在答辩前自动筛掉最可能翻车的三个点而且测试代码放进论文“系统测试”章节非常加分。测试类加 SpringBootTest 依赖 spring-boot-starter-test跑 mvn test 即可。演示前还有两个小习惯。第一把 Spring Boot 的日志级别调到 DEBUG或者打开 MyBatis 的 SQL 日志演示时让评委看到控制台打印的 SELECT 语句比任何架构图都有说服力。第二用 Apifox 或 Postman 把核心接口存成一个请求集合CRUD、合同签订、账单生成、到期扫描各留一条现场如果页面操作失灵切到接口工具里一条一条讲逻辑也能把场面救回来。我做这类项目最大的教训是别把代码全部写完再开始写论文。论文的模块图和数据库 ER 图在表结构和接口清单定稿的那天就可以画出来后面每完成一个功能模块就往论文“系统实现”章节里填一段代码和截图。等系统跑通时论文初稿已经成型答辩 PPT 只是把论文里的图抽出来排一遍版而不是从零开始赶工。希望这个工作顺序和上面的避坑清单能帮到你让你的 Spring Boot 房屋租赁系统真的做到“能跑、能写、能讲”三件事一起交付。本文还有配套的精品资源点击获取