简介:本资源是一套完整的基于SpringBoot的房屋租赁系统毕业设计项目,面向Java初学者与高校计算机专业学生,解决课程设计、毕业设计中从需求分析到系统部署的全流程实践问题。资源包含可运行源码、32页规范论文(含绪论、技术选型、系统分析与设计、功能实现、测试等完整章节)及答辩PPT,覆盖管理员、房东、租客三类角色的核心业务场景。压缩包共913个文件,以162个Java后端逻辑文件、152个JavaScript前端交互脚本、56个Vue组件、44个CSS样式文件及SQL数据库脚本为主,辅以SVG图标、JPG/PNG素材和配置类XML/YML文件,整体24.22MB,结构清晰、模块解耦明确,便于分层学习与二次开发。目前已有63人学习下载,读者可直接导入IDE运行调试,快速掌握SpringBoot+MySQL+Vue前后端协同开发模式,并复用论文框架与答辩材料。
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 是 LTS,Spring 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 -DgroupId=com.example.rental -DartifactId=rental-server -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false然后手动补pom.xml核心依赖(注意版本对齐):
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</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_EXIT=FALSE 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> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>4.1.0</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>6.2.0</version> </dependency>注意:必须用
mybatis-plus-spring-boot3-starter(非mybatis-plus-boot-starter),后者不兼容 Jakarta EE 9+;pagehelper版本 6.2.0 才支持 Spring Boot 3.x 的Interceptor新接口。
3.2 编写建表 SQL:schema-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.sql:
INSERT 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 BaseMapper<House> { // MyBatis Plus 自带通用 CRUD,无需 XML } @Service public class HouseService implements IService<House> { @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 Page<House> listByStatus(String status, int current, int size) { Page<House> page = new Page<>(current, size); LambdaQueryWrapper<House> wrapper = Wrappers.lambdaQuery(); wrapper.eq(House::getStatus, status); return houseMapper.selectPage(page, wrapper); } }关键细节:
save()和updateById()重写是为了自动填充时间戳;listByStatus()用LambdaQueryWrapper而非字符串拼接,类型安全,IDE 能跳转字段;Page<House>返回值自带总条数、当前页、分页链接,前端分页组件直接消费。
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.css,404。正解是:让 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 Boot:
mvn 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;" />