2026/10/27 16:42:37

Spring Boot房屋租赁系统实战:从搭建到答辩

Spring Boot房屋租赁系统实战:从搭建到答辩 简介本资源是一套完整的基于SpringBoot的房屋租赁系统毕业设计项目面向Java初学者与高校计算机专业学生解决课程设计、毕业设计中从需求分析到系统部署的全流程实践问题。资源包含可运行源码、32页规范论文含绪论、技术选型、系统分析与设计、功能实现、测试等完整章节及答辩PPT覆盖管理员、房东、租客三类角色的核心业务场景。压缩包共913个文件以162个Java后端逻辑文件、152个JavaScript前端交互脚本、56个Vue组件、44个CSS样式文件及SQL数据库脚本为主辅以SVG图标、JPG/PNG素材和配置类XML/YML文件整体24.22MB结构清晰、模块解耦明确便于分层学习与二次开发。目前已有63人学习下载读者可直接导入IDE运行调试快速掌握SpringBootMySQLVue前后端协同开发模式并复用论文框架与答辩材料。1. 为什么一个“房屋租赁系统”要死磕 Spring Boot——不是为了炫技而是让房东少改三次数据库字段、让租客多等两秒就刷新出真实房源你见过凌晨三点还在改 MyBatis XML 的后端也见过前端同学把 Vue 打包产物塞进src/main/resources/static后发现index.html里href/css/app.xxx.css加载 404查了六小时才发现spring.web.resources.static-locations默认没包含static目录层级这不是段子——这是我在三个校企合作毕设项目里亲手踩过的坑。这个标题里的「基于 Spring Boot 房屋租赁系统」本质不是写个 CRUD 网站交差而是用 Spring Boot 的自动装配、约定优于配置、嵌入式容器、模块化分层能力把一套真实业务中高频出现的痛点房源状态实时同步下架/已租/待看、租约周期与押金自动计算、多角色权限隔离房东/租客/管理员、文件上传与合同生成、前后端分离部署兼容性——全部压进一个可复现、可调试、可交接的工程骨架里。它适合两类人一是计算机专业大三学生需要在两周内跑通完整流程并答辩二是刚转 Java 的初级工程师想用一个有真实字段如lease_start_date,deposit_amount,house_status和真实约束如“同一房源不可同时被两个租客锁定”的项目反向吃透 Spring Boot 的Transactional隔离级别、Valid分组校验、ConfigurationProperties绑定 YAML、以及spring-boot-starter-web底层到底怎么接管 Tomcat 请求链路。别信“Spring Boot 就是简化配置”这种话——真正卡住你的永远是application.yml里一个缩进错误导致Value(${file.upload.path})注入为空或是MapperScan没扫到 mapper 接口连启动日志都不报错只在调用时抛NullPointerException。2. 从零搭起可运行骨架用 Spring Boot 3.2 JDK 17 Maven 构建最小可行工程Spring Boot 版本太高比如 3.x确实会翻车——不是功能不行而是默认移除了javax.*包、强制要求 Jakarta EE 9、且spring-boot-starter-web内置 Tomcat 10.1 后所有 Servlet API 调用必须从javax.servlet改成jakarta.servlet。很多老教程抄的WebServlet注解直接编译不过。我们选Spring Boot 3.2.5 JDK 17 Maven 3.8.6这是当前2024 年中在校企项目中最稳的组合JDK 17 是 LTSSpring Boot 3.2 对 Lombok、MyBatis Plus、Vue 打包产物静态资源托管支持最成熟且能避开 Spring Boot 3.3 刚发布的spring-native兼容性雷区。2.1 初始化工程用 start.spring.io 还是手动建我选后者别用官网生成器一键下载 ZIP——它默认勾选Spring Web但漏掉关键依赖spring-boot-starter-validation做表单校验、spring-boot-starter-data-jdbc轻量级数据库操作比 JPA 更可控更致命的是不生成application.yml的基础结构。我习惯手建mkdir house-rental cd house-rental mvn archetype:generate -DgroupIdcom.example.rental -DartifactIdrental-server -DarchetypeArtifactIdmaven-archetype-webapp -DinteractiveModefalse然后手动补pom.xml核心依赖注意版本对齐parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jdbc/artifactId /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies提示H2 数据库仅用于本地开发和演示它内存模式启动快、无需安装且支持 SQL 初始化脚本。上线必须换 MySQL 或 PostgreSQL但开发阶段用 H2 能绕过数据库连接池配置、字符集乱码等早期阻塞点。2.2 定义核心实体从“房子”开始拒绝贫血模型很多毕设代码里House类只有id,name,price三个字段结果答辩时老师问“租期怎么算押金怎么退维修记录存哪”——立刻哑火。我们按真实业务拆Data Builder NoArgsConstructor AllArgsConstructor public class House { private Long id; private String title; // 房源标题如“朝阳区精装一居室” private BigDecimal price; // 月租金单位元 private Integer depositMonths; // 押金月数常见 1/2/3 private String address; // 详细地址 private String status; // ONLINE, OFFLINE, RENTED, MAINTAINING private LocalDateTime createdAt; private LocalDateTime updatedAt; }对应HouseStatus枚举public enum HouseStatus { ONLINE(上架), OFFLINE(下架), RENTED(已出租), MAINTAINING(维修中); private final String desc; HouseStatus(String desc) { this.desc desc; } }注意BigDecimal代替Double存价格避免浮点误差LocalDateTime代替Date时区安全Builder让测试构造对象一行搞定status字段用枚举而非字符串防止数据库存入online和ONLINE混用。2.3 配置 application.yml三个必调参数少一个都启动失败Spring Boot 3.2 的application.yml必须显式声明spring.config.import才能加载application-dev.yml且server.port默认 8080但若你电脑装了 Docker 或其他服务占了该端口得立刻改。以下是开发环境最小可用配置# application.yml spring: config: import: optional:classpath:/application-dev.yml profiles: active: dev jackson: date-format: yyyy-MM-dd HH:mm:ss serialization: write-dates-as-timestamps: false servlet: context-path: /api server: port: 8081 # 避开 8080防冲突 # application-dev.yml spring: datasource: url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSE driver-class-name: org.h2.Driver username: sa password: h2: console: enabled: true path: /h2-console sql: init: mode: always schema-locations: classpath:schema-h2.sql >dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version4.1.0/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version6.2.0/version /dependency注意必须用mybatis-plus-spring-boot3-starter非mybatis-plus-boot-starter后者不兼容 Jakarta EE 9pagehelper版本 6.2.0 才支持 Spring Boot 3.x 的Interceptor新接口。3.2 编写建表 SQLschema-h2.sql 与>CREATE TABLE IF NOT EXISTS house ( id BIGINT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, deposit_months INT NOT NULL DEFAULT 1, address VARCHAR(200) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT ONLINE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );src/main/resources/data-h2.sqlINSERT INTO house (title, price, deposit_months, address, status) VALUES (国贸精装一居室, 6500.00, 2, 北京市朝阳区建国门外大街1号, ONLINE), (西二旗地铁合租单间, 2800.00, 1, 北京市海淀区西二旗地铁站旁, ONLINE), (望京SOHO创意公寓, 8200.00, 3, 北京市朝阳区望京街10号, RENTED);3.3 定义 Mapper 接口与 Service 层Mapper public interface HouseMapper extends BaseMapperHouse { // MyBatis Plus 自带通用 CRUD无需 XML } Service public class HouseService implements IServiceHouse { Autowired private HouseMapper houseMapper; Override public boolean save(House entity) { entity.setCreatedAt(LocalDateTime.now()); entity.setUpdatedAt(LocalDateTime.now()); return houseMapper.insert(entity) 0; } Override public boolean updateById(House entity) { entity.setUpdatedAt(LocalDateTime.now()); return houseMapper.updateById(entity) 0; } public PageHouse listByStatus(String status, int current, int size) { PageHouse page new Page(current, size); LambdaQueryWrapperHouse wrapper Wrappers.lambdaQuery(); wrapper.eq(House::getStatus, status); return houseMapper.selectPage(page, wrapper); } }关键细节save()和updateById()重写是为了自动填充时间戳listByStatus()用LambdaQueryWrapper而非字符串拼接类型安全IDE 能跳转字段PageHouse返回值自带总条数、当前页、分页链接前端分页组件直接消费。4. 前后端联调与 Vue 打包集成把 dist 文件夹塞进 Spring Boot 的正确姿势“Vue 打包放进 Spring Boot 中” 是高频热词但 90% 的人卡在vue.config.js的outputDir和 Spring Boot 的static-locations不匹配。Vue CLI 默认打包到dist/而 Spring Boot 默认只认src/main/resources/static/**和src/main/resources/public/**。硬拷贝dist/*到static目录错——这会导致npm run build后还要手动复制且index.html里的href/css/app.xxx.css在 Spring Boot 下变成http://localhost:8081/api/css/app.xxx.css404。正解是让 Vue 打包路径对齐 Spring Boot 的静态资源规则并用spring.web.resources.static-locations显式声明。4.1 配置 Vue CLI修改 vue.config.js// vue.config.js const path require(path); module.exports { outputDir: dist-springboot, // 不用默认 dist避免混淆 publicPath: ./, // 关键相对路径让 CSS/JS 引用不带 /api 前缀 configureWebpack: { resolve: { alias: { : path.resolve(__dirname, src) } } }, devServer: { proxy: { /api: { target: http://localhost:8081, // Spring Boot 后端地址 changeOrigin: true, pathRewrite: { ^/api: // 前端发 /api/houses代理后变成 /houses } } } } };注意publicPath: ./是核心——它让index.html生成link href./css/app.xxx.css而不是link href/css/app.xxx.css。这样当 Spring Boot 把dist-springboot当作静态目录时浏览器请求http://localhost:8081/css/app.xxx.css就能命中。4.2 Spring Boot 静态资源配置覆盖默认路径在application-dev.yml中追加spring: web: resources: static-locations: classpath:/static/,classpath:/public/,file:./dist-springboot/解释file:./dist-springboot/表示从项目根目录读取dist-springboot文件夹classpath:/static/保留原有资源路径顺序很重要——Spring Boot 按列表顺序查找file:放最后避免覆盖 classpath 下的favicon.ico。4.3 启动验证三步确认是否成功运行npm run build生成dist-springboot/index.html启动 Spring Bootmvn spring-boot:run访问http://localhost:8081—— 应显示 Vue 页面打开浏览器开发者工具 → Network → 刷新检查index.html、css/app.xxx.css、js/app.xxx.js是否全部 200打开http://localhost:8081/h2-console登录后执行SELECT * FROM house确认数据存在。如果index.html加载但 CSS/JS 404一定是publicPath没设成./如果页面空白且控制台报Failed to load resource: the server responded with a status of 404 ()检查static-locations是否写了file:./dist-springboot/且路径名与outputDir一致。5. 避坑指南Spring Boot 房屋租赁系统开发中 5 个血泪经验这些坑不是来自文档而是我在帮 12 个学生 debug 时记下的真实日志片段。每个都附带现象、原因、解决照着改省下至少 8 小时。5.1 现象启动时报Caused by: java.lang.ClassNotFoundException: javax.servlet.http.HttpServletRequest原因Spring Boot 3.x 迁移到 Jakarta EE 9所有javax.*包名改为jakarta.*但你代码里或某个依赖如老版本 Shiro还引用javax.servlet。解决全局搜索import javax.servlet替换为import jakarta.servlet检查pom.xml中是否有shiro-web等老框架换成shiro-spring-boot-starter3.x 版本用mvn dependency:tree | grep servlet查未升级依赖。5.2 现象Valid校验不生效NotBlank字段为空仍能保存原因没在 Controller 方法参数前加Valid或没在类上加Validated或spring-boot-starter-validation依赖漏了。解决确认pom.xml有 validation 依赖Controller 方法签名必须是public Result add(Valid RequestBody House house)House类字段加NotBlank(message 标题不能为空)启动日志搜Validator应看到Using LocaleMessageSource。5.3 现象H2 控制台能访问但SELECT * FROM house返回空schema-h2.sql没执行原因spring.sql.init.mode默认是embedded只对嵌入式 DB 生效而 H2 是嵌入式但spring.sql.init.schema-locations路径写错或schema-h2.sql文件编码不是 UTF-8含中文注释时尤其易错。解决确认application-dev.yml中spring.sql.init.mode: always用 VS Code 打开schema-h2.sql右下角检查编码是否为 UTF-8不是则「Save with Encoding」→ UTF-8路径用classpath:schema-h2.sql不要写classpath:/schema-h2.sql斜杠多余。5.4 现象Vue 页面能打开但调用/api/houses返回 404后端 Controller 没日志原因RestController类没加RequestMapping(/api)或方法上GetMapping(/houses)的路径与前端axios.get(/api/houses)不匹配或server.servlet.context-path和RequestMapping前缀重复。解决Controller 类加RequestMapping(/api)方法用GetMapping(/houses)确保application.yml中spring.servlet.context-path: /api启动后访问http://localhost:8081/api/houses测试排除前端代理问题。5.5 现象Transactional方法内调用另一个Transactional方法事务不生效原因Spring AOP 代理默认只对外部调用生效类内方法自调用如save()调sendNotification()绕过代理事务失效。解决拆成两个 Service 类用Autowired注入调用或用AopContext.currentProxy()强制走代理不推荐最稳妥是业务逻辑抽到独立 Service保证调用链经过 Spring 容器。6. 答辩与交付如何把源码、论文、PPT 串成一条说服力链毕设答辩不是考你能不能写代码而是考你能不能说清楚为什么选这个技术哪里体现了工程能力数据怎么来的边界在哪我带过的项目凡能过答辩的都把这三件套做成闭环源码里埋线索论文里写依据PPT 里展证据。6.1 源码即证据在关键位置加注释锚点别让老师翻你几千行代码找亮点。在HouseService.java的save()方法开头加/** * 【答辩重点】事务一致性保障 * 1. 使用 Transactional(isolation Isolation.REPEATABLE_READ) 防止脏读 * 2. 自动填充 created_at/updated_at避免手动 set * 3. status 默认 ONLINE符合业务规则 */ Transactional(isolation Isolation.REPEATABLE_READ) Override public boolean save(House entity) { ... }在application-dev.yml的spring.sql.init段落加# 【答辩重点】数据库初始化策略 # mode: always → 开发阶段确保表结构最新避免手动删库 # schema-locations → DDL 与 DML 分离便于后期迁移 # style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />