最近在辅导学生做毕业设计时,发现很多同学对如何将Spring Boot技术栈落地到一个完整的、有业务逻辑的Web项目感到迷茫。网上虽然有很多零散的教程,但往往只讲某个技术点,缺乏一个贯穿前后端、数据库、页面交互的完整案例。本文将以一个基于Spring Boot的租房平台为例,带你从零开始,手把手搭建一个具备房源发布、浏览、搜索、用户管理等核心功能的Web应用。
通过这个项目,你将系统性地掌握如何使用Spring Boot整合Maven、Thymeleaf、MyBatis-Plus、MySQL以及Ajax等技术,并理解它们在一个真实业务场景中是如何协同工作的。无论你是正在寻找毕设项目的学生,还是希望巩固Spring Boot实战技能的开发者,这篇文章都能为你提供一份清晰、可复现的“保姆级”指南。
1. 项目背景与核心需求分析
在开始编码之前,明确我们要做什么以及为什么选择这些技术至关重要。一个租房平台的核心目标是连接房东(房源提供方)与租客(需求方),实现房源信息的有效管理和高效匹配。
1.1 业务场景与功能模块
典型的租房平台包含以下核心功能模块:
- 用户模块:用户注册、登录、个人信息管理。区分普通用户(租客)和管理员。
- 房源模块:这是核心。包括房源的发布、编辑、删除、上架/下架。房源信息应包含标题、描述、地址、租金、户型、面积、配套设施、图片等。
- 信息展示与搜索模块:首页展示房源列表,支持按价格、户型、区域等多条件筛选和关键词搜索。
- 交互模块:租客可以收藏感兴趣的房源,或通过站内信、联系方式(需授权)与房东沟通。
1.2 技术选型与架构说明
为什么选择这套技术栈?
- Spring Boot:作为项目基石,它极大地简化了Spring应用的初始搭建和开发过程,通过自动配置和起步依赖,让我们能快速构建独立运行、生产级别的应用。
- Maven:项目构建和依赖管理工具。通过
pom.xml文件统一管理项目所需的所有库(Jar包),解决依赖冲突,规范项目结构。 - Thymeleaf:服务端模板引擎。它允许我们在HTML中直接使用Spring表达式语言来动态渲染数据(如显示房源列表),并且与Spring Boot集成度极高,语法自然。
- MyBatis-Plus:强大的MyBatis增强工具。它内置了通用Mapper和Service,只需简单继承,即可实现单表的大部分CRUD操作,极大减少SQL编写量,提高开发效率。
- MySQL:成熟稳定的关系型数据库,用于持久化存储用户、房源、收藏等所有业务数据。
- Ajax:异步JavaScript和XML技术。用于实现页面的局部刷新,提升用户体验。例如,在搜索房源时,无需刷新整个页面即可更新列表;收藏房源时,按钮状态可以即时改变。
这套组合是当前Java Web开发中非常经典和流行的选择,兼顾了开发效率、性能和维护性。
2. 开发环境与项目初始化
工欲善其事,必先利其器。确保你的开发环境已就绪。
2.1 环境与工具准备
- 操作系统:Windows 10/11, macOS 或 Linux 均可。
- JDK:版本 8 或 11(推荐11,长期支持版本)。安装后配置好
JAVA_HOME环境变量。 - IDE:IntelliJ IDEA(社区版或旗舰版)。它对Spring Boot的支持非常友好。
- 数据库:MySQL 5.7 或 8.0。建议使用图形化管理工具如Navicat、MySQL Workbench或IDEA自带的数据库工具。
- 构建工具:Maven 3.6+。IDEA通常自带,也可独立安装。
- 浏览器:Chrome 或 Firefox,用于调试前端和Ajax请求。
2.2 使用Spring Initializr创建项目
这是最快捷的方式。你可以通过 start.spring.io 在线生成,也可以在IDEA中直接创建。
- 在IDEA中选择
File -> New -> Project。 - 选择
Spring Initializr,选择合适的JDK版本。 - 填写项目元数据:
- Group:
com.rental(示例,可按需修改) - Artifact:
rental-platform - Type: Maven
- Java Version: 8 或 11
- Packaging: Jar
- Group:
- 在Dependencies中添加初始依赖:搜索并添加
Spring Web,Thymeleaf,MyBatis Framework,MySQL Driver。Lombok(用于简化实体类代码)也强烈建议添加。 - 点击完成,IDEA会自动创建项目并下载依赖。
2.3 初始项目结构与pom.xml调整
项目创建后,结构如下:
rental-platform ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── rental │ │ │ └── RentalPlatformApplication.java (启动类) │ │ └── resources │ │ ├── static (存放静态资源:css, js, images) │ │ ├── templates (存放Thymeleaf模板HTML文件) │ │ └── application.properties (配置文件) │ └── test (测试目录) └── pom.xml (Maven依赖配置文件)我们需要手动将MyBatis Framework依赖替换为功能更强大的MyBatis-Plus。打开pom.xml,找到mybatis-spring-boot-starter依赖项,将其替换为:
<!-- MyBatis-Plus 起步依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> <!-- 请查看官网使用最新稳定版 --> </dependency> <!-- Lombok (已在Initializr添加,确认存在) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Spring Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>修改后,点击IDEA右侧Maven面板的刷新按钮,下载新的依赖。
3. 数据库设计与核心配置
在编写代码前,先设计好数据库表结构。
3.1 数据库表设计
我们创建数据库rental_db,并设计三张核心表:
-- 1. 用户表 CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '密码(加密存储)', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `role` varchar(20) DEFAULT 'USER' COMMENT '角色:USER, ADMIN', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 2. 房源信息表 CREATE TABLE `house` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '房源ID', `title` varchar(200) NOT NULL COMMENT '房源标题', `description` text COMMENT '详细描述', `price` decimal(10,2) NOT NULL COMMENT '月租金', `area` decimal(6,2) DEFAULT NULL COMMENT '面积(平方米)', `room` int(11) DEFAULT '1' COMMENT '室', `hall` int(11) DEFAULT '0' COMMENT '厅', `bathroom` int(11) DEFAULT '1' COMMENT '卫', `address` varchar(500) NOT NULL COMMENT '详细地址', `city` varchar(50) DEFAULT NULL COMMENT '城市', `district` varchar(50) DEFAULT NULL COMMENT '区县', `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图URL', `status` int(11) DEFAULT '1' COMMENT '状态:0-下架,1-上架', `landlord_id` bigint(20) NOT NULL COMMENT '房东用户ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_landlord` (`landlord_id`), KEY `idx_city_district` (`city`,`district`), KEY `idx_price` (`price`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源信息表'; -- 3. 收藏表(用户与房源的多对多关系) CREATE TABLE `favorite` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `house_id` bigint(20) NOT NULL COMMENT '房源ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_user_house` (`user_id`,`house_id`), -- 防止重复收藏 KEY `idx_user` (`user_id`), KEY `idx_house` (`house_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收藏表';3.2 应用配置文件
接下来,配置Spring Boot连接数据库和MyBatis-Plus。编辑src/main/resources/application.properties文件:
# 服务器端口 server.port=8080 # 数据库连接配置 spring.datasource.url=jdbc:mysql://localhost:3306/rental_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=your_password # 替换为你的数据库密码 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # MyBatis-Plus 配置 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,开发环境使用 mybatis-plus.global-config.db-config.id-type=auto # 主键策略(数据库自增) mybatis-plus.global-config.db-config.logic-delete-field=deleted # 逻辑删除字段名(如果启用) mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑已删除值 mybatis-plus.global-config.db-config.logic-not-delete-value=0 # 逻辑未删除值 mybatis-plus.configuration.map-underscore-to-camel-case=true # 自动驼峰命名转换 # Thymeleaf 配置 spring.thymeleaf.cache=false # 开发时关闭缓存,修改HTML即时生效 spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.thymeleaf.mode=HTML spring.thymeleaf.encoding=UTF-84. 核心功能模块实现
我们将按照MVC(Model-View-Controller)分层架构来组织代码。
4.1 实体类(Model)与数据访问层(Mapper)
首先创建与数据库表对应的实体类。使用Lombok的@Data注解可以自动生成getter、setter等方法。
// 文件路径:src/main/java/com/rental/entity/User.java package com.rental.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("user") // 指定对应表名 public class User { @TableId(type = IdType.AUTO) // 主键自增 private Long id; private String username; private String password; private String email; private String phone; private String avatar; private String role; @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; }// 文件路径:src/main/java/com/rental/entity/House.java package com.rental.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; @Data @TableName("house") public class House { @TableId(type = IdType.AUTO) private Long id; private String title; private String description; private BigDecimal price; private BigDecimal area; private Integer room; private Integer hall; private Integer bathroom; private String address; private String city; private String district; private String coverImage; private Integer status; // 0-下架,1-上架 private Long landlordId; // 关联用户ID @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }接着,创建对应的Mapper接口。MyBatis-Plus的强大之处在于,对于单表的基础CRUD,我们只需要创建一个空接口继承BaseMapper即可。
// 文件路径:src/main/java/com/rental/mapper/UserMapper.java package com.rental.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.rental.entity.User; import org.apache.ibatis.annotations.Mapper; @Mapper // 标识为MyBatis的Mapper,Spring Boot会自动扫描 public interface UserMapper extends BaseMapper<User> { // 无需编写任何方法,即可拥有增删改查等基础方法 }// 文件路径:src/main/java/com/rental/mapper/HouseMapper.java package com.rental.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.rental.entity.House; import org.apache.ibatis.annotations.Mapper; @Mapper public interface HouseMapper extends BaseMapper<House> { // 如果需要复杂的多表查询,可以在这里定义方法,并在对应的XML文件中编写SQL }为了让createTime和updateTime自动填充生效,我们需要配置一个元对象处理器。
// 文件路径:src/main/java/com/rental/config/MyMetaObjectHandler.java package com.rental.config; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }4.2 业务逻辑层(Service)
Service层负责处理业务逻辑。我们可以使用MyBatis-Plus提供的IService和ServiceImpl来快速构建。
// 文件路径:src/main/java/com/rental/service/IHouseService.java package com.rental.service; import com.baomidou.mybatisplus.extension.service.IService; import com.rental.entity.House; import java.util.List; public interface IHouseService extends IService<House> { // 定义业务相关的方法,例如条件查询房源列表 List<House> searchHouses(String keyword, String city, String district, BigDecimal minPrice, BigDecimal maxPrice); }// 文件路径:src/main/java/com/rental/service/impl/HouseServiceImpl.java package com.rental.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.rental.entity.House; import com.rental.mapper.HouseMapper; import com.rental.service.IHouseService; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; @Service public class HouseServiceImpl extends ServiceImpl<HouseMapper, House> implements IHouseService { @Override public List<House> searchHouses(String keyword, String city, String district, BigDecimal minPrice, BigDecimal maxPrice) { LambdaQueryWrapper<House> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(House::getStatus, 1); // 只查询上架的房源 if (keyword != null && !keyword.trim().isEmpty()) { queryWrapper.and(wrapper -> wrapper .like(House::getTitle, keyword) .or() .like(House::getDescription, keyword) .or() .like(House::getAddress, keyword) ); } if (city != null && !city.isEmpty()) { queryWrapper.eq(House::getCity, city); } if (district != null && !district.isEmpty()) { queryWrapper.eq(House::getDistrict, district); } if (minPrice != null) { queryWrapper.ge(House::getPrice, minPrice); // ge: greater than or equal to } if (maxPrice != null) { queryWrapper.le(House::getPrice, maxPrice); // le: less than or equal to } queryWrapper.orderByDesc(House::getCreateTime); // 按发布时间倒序 return this.list(queryWrapper); } }4.3 控制层(Controller)与页面交互
Controller负责接收HTTP请求,调用Service处理,并返回视图(HTML页面)或数据(JSON)。
4.3.1 首页与房源列表展示
首先创建一个简单的首页控制器,并展示房源列表。
// 文件路径:src/main/java/com/rental/controller/IndexController.java package com.rental.controller; import com.rental.entity.House; import com.rental.service.IHouseService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal; import java.util.List; @Controller @RequiredArgsConstructor // Lombok注解,自动注入final字段 public class IndexController { private final IHouseService houseService; @GetMapping({"/", "/index"}) public String index(@RequestParam(required = false) String keyword, @RequestParam(required = false) String city, @RequestParam(required = false) BigDecimal minPrice, @RequestParam(required = false) BigDecimal maxPrice, Model model) { // 调用Service进行条件查询 List<House> houseList = houseService.searchHouses(keyword, city, null, minPrice, maxPrice); model.addAttribute("houseList", houseList); model.addAttribute("keyword", keyword); model.addAttribute("city", city); model.addAttribute("minPrice", minPrice); model.addAttribute("maxPrice", maxPrice); return "index"; // 对应 templates/index.html } }接下来创建Thymeleaf模板页面index.html。
<!-- 文件路径:src/main/resources/templates/index.html --> <!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>租房平台 - 首页</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"> <style> .house-card { margin-bottom: 20px; transition: transform .2s; } .house-card:hover { transform: translateY(-5px); box-shadow: 0 4px 15px rgba(0,0,0,.1); } .price { color: #ff6b6b; font-weight: bold; font-size: 1.2em; } </style> </head> <body> <!-- 导航栏 --> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container"> <a class="navbar-brand" href="/">租房平台</a> <!-- 搜索框 --> <form class="d-flex ms-auto" action="/" method="get"> <input class="form-control me-2" type="search" name="keyword" placeholder="搜索房源..." th:value="${keyword}"> <input class="form-control me-2" type="text" name="city" placeholder="城市" th:value="${city}"> <input class="form-control me-2" style="width: 100px;" type="number" name="minPrice" placeholder="最低价" th:value="${minPrice}"> <input class="form-control me-2" style="width: 100px;" type="number" name="maxPrice" placeholder="最高价" th:value="${maxPrice}"> <button class="btn btn-outline-success" type="submit">搜索</button> </form> <!-- 用户相关链接 --> <div class="navbar-nav"> <a class="nav-link" href="/login">登录</a> <a class="nav-link" href="/register">注册</a> <a class="nav-link" href="/publish">发布房源</a> </div> </div> </nav> <!-- 房源列表 --> <div class="container mt-4"> <h2 class="mb-4">房源列表</h2> <div class="row" th:if="${#lists.isEmpty(houseList)}"> <div class="col-12"> <div class="alert alert-info">暂无房源信息,快去发布第一个房源吧!</div> </div> </div> <div class="row"> <!-- 使用Thymeleaf循环渲染房源卡片 --> <div class="col-md-4 col-lg-3" th:each="house : ${houseList}"> <div class="card house-card"> <img th:src="${house.coverImage != null} ? ${house.coverImage} : '/images/default-house.jpg'" class="card-img-top" alt="房源封面" style="height: 180px; object-fit: cover;"> <div class="card-body"> <h5 class="card-title" th:text="${house.title}">房源标题</h5> <p class="card-text text-muted" th:text="${house.address}">房源地址</p> <p class="card-text"> <span th:text="${house.room} + '室' + ${house.hall} + '厅' + ${house.bathroom} + '卫'"></span> | <span th:text="${house.area} + '㎡'"></span> </p> <p class="card-text price" th:text="'¥' + ${#numbers.formatDecimal(house.price, 1, 2)}">价格</p> <a th:href="@{/house/detail/{id}(id=${house.id})}" class="btn btn-primary btn-sm">查看详情</a> <button class="btn btn-outline-danger btn-sm float-end" onclick="addFavorite([[${house.id}]])">收藏</button> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script> <script> // 使用Ajax实现收藏功能 function addFavorite(houseId) { // 这里需要先判断用户是否登录,为简化示例,假设已登录 $.ajax({ url: '/api/favorite/add', type: 'POST', contentType: 'application/json', data: JSON.stringify({ houseId: houseId }), success: function(response) { if (response.success) { alert('收藏成功!'); } else { alert('收藏失败:' + response.message); } }, error: function() { alert('网络请求失败,请检查登录状态。'); } }); } </script> </body> </html>4.3.2 实现Ajax交互的收藏功能
上面页面中调用了/api/favorite/add接口,我们需要实现它。这通常是一个返回JSON的RESTful接口。
首先,创建一个数据传输对象(DTO)来接收前端JSON数据。
// 文件路径:src/main/java/com/rental/dto/FavoriteDTO.java package com.rental.dto; import lombok.Data; import javax.validation.constraints.NotNull; @Data public class FavoriteDTO { @NotNull(message = "房源ID不能为空") private Long houseId; // 用户ID可以从会话(Session)或安全上下文中获取,无需前端传递 }然后,创建对应的Controller。为了简化,这里假设用户已登录,并从Session中获取用户ID。
// 文件路径:src/main/java/com/rental/controller/api/FavoriteApiController.java package com.rental.controller.api; import com.rental.dto.FavoriteDTO; import com.rental.service.IFavoriteService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession; import javax.validation.Valid; import java.util.HashMap; import java.util.Map; @RestController // @RestController = @Controller + @ResponseBody,直接返回JSON @RequestMapping("/api/favorite") @RequiredArgsConstructor public class FavoriteApiController { private final IFavoriteService favoriteService; @PostMapping("/add") public ResponseEntity<Map<String, Object>> addFavorite(@Valid @RequestBody FavoriteDTO favoriteDTO, HttpSession session) { Map<String, Object> result = new HashMap<>(); // 模拟从Session获取当前用户ID,实际项目中应使用Spring Security等安全框架 Long userId = (Long) session.getAttribute("userId"); if (userId == null) { result.put("success", false); result.put("message", "用户未登录"); return ResponseEntity.status(401).body(result); // 401 Unauthorized } try { boolean success = favoriteService.addFavorite(userId, favoriteDTO.getHouseId()); if (success) { result.put("success", true); result.put("message", "收藏成功"); } else { result.put("success", false); result.put("message", "已收藏过该房源"); } return ResponseEntity.ok(result); } catch (Exception e) { result.put("success", false); result.put("message", "服务器内部错误"); return ResponseEntity.status(500).body(result); } } }对应的Service实现:
// 文件路径:src/main/java/com/rental/service/impl/FavoriteServiceImpl.java package com.rental.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.rental.entity.Favorite; import com.rental.mapper.FavoriteMapper; import com.rental.service.IFavoriteService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class FavoriteServiceImpl extends ServiceImpl<FavoriteMapper, Favorite> implements IFavoriteService { @Override @Transactional(rollbackFor = Exception.class) public boolean addFavorite(Long userId, Long houseId) { // 检查是否已收藏 LambdaQueryWrapper<Favorite> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Favorite::getUserId, userId).eq(Favorite::getHouseId, houseId); long count = this.count(queryWrapper); if (count > 0) { return false; // 已收藏 } // 新增收藏记录 Favorite favorite = new Favorite(); favorite.setUserId(userId); favorite.setHouseId(houseId); return this.save(favorite); } }5. 项目运行与测试
5.1 启动应用
确保MySQL服务已启动,并且rental_db数据库及表已创建。
在IDEA中找到主启动类RentalPlatformApplication,右键点击Run。
观察控制台日志,如果没有报错,看到类似Tomcat started on port(s): 8080的信息,说明启动成功。
5.2 功能测试
- 访问首页:打开浏览器,访问
http://localhost:8080。你应该能看到导航栏和空的房源列表(因为数据库还没有数据)。 - 测试数据插入:可以通过编写一个简单的单元测试,或者直接使用数据库工具向
house表插入几条测试数据。再次刷新首页,房源卡片应该被渲染出来。 - 测试搜索功能:在搜索框输入关键词、城市或价格范围,点击搜索,页面会刷新并展示筛选后的结果。
- 测试Ajax收藏:点击房源卡片的“收藏”按钮,浏览器会发起一个Ajax POST请求。你需要先实现一个简单的登录功能,并在登录后将用户ID存入Session (
session.setAttribute("userId", user.getId())),收藏功能才能正常工作。你可以先注释掉Controller中的登录检查逻辑进行测试。
6. 常见问题与排查思路
在开发过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
启动报错:Failed to configure a DataSource | 数据库连接配置错误或数据库服务未启动。 | 1. 检查application.properties中的url,username,password。2. 确认MySQL服务是否运行 ( net start mysql)。3. 确认数据库 rental_db是否存在。 |
访问页面报错:Whitelabel Error Page或404 | 1. Controller的请求路径映射错误。 2. Thymeleaf模板文件位置或名称错误。 3. 静态资源路径错误。 | 1. 检查Controller中@GetMapping或@PostMapping的路径。2. 确认HTML文件是否在 resources/templates/目录下,且返回的视图名称与之匹配(不含后缀)。3. 检查静态资源(CSS, JS, images)是否在 resources/static/下。 |
页面能访问,但Thymeleaf表达式不生效,显示为[[${...}]] | 1. HTML文件没有引入Thymeleaf命名空间。 2. 模型(Model)中没有添加对应的属性。 | 1. 确保HTML的<html>标签有xmlns:th="http://www.thymeleaf.org"。2. 在Controller方法中,使用 model.addAttribute("key", value)传递数据。 |
| MyBatis-Plus 查询不到数据或报错 | 1. 实体类字段名与数据库列名映射失败。 2. Mapper接口未被Spring扫描到。 3. 查询条件构造错误。 | 1. 检查实体类@TableName、@TableField注解,或开启驼峰命名转换。2. 确认Mapper接口上有 @Mapper注解或在启动类加了@MapperScan。3. 在 application.properties中开启SQL日志 (mybatis-plus.configuration.log-impl),查看生成的SQL是否正确。 |
| Ajax请求返回404或405 | 1. 请求的URL路径错误。 2. 请求方法(GET/POST)与Controller定义不匹配。 3. 未添加 @ResponseBody或未使用@RestController。 | 1. 检查浏览器开发者工具(F12)的Network面板,确认请求URL和Method。 2. 确保Controller方法使用了正确的 @GetMapping、@PostMapping等注解。3. 确保返回JSON的Controller类有 @RestController注解。 |
7. 项目扩展与最佳实践建议
至此,一个具备基础功能的租房平台骨架已经完成。要将其完善为一个真正的项目,还需要考虑以下方面:
7.1 功能扩展方向
- 用户认证与授权:集成Spring Security或Sa-Token,实现完整的注册、登录、登出、密码加密、角色权限控制(如只有管理员能审核房源,只有房东能发布房源)。
- 图片上传:实现房源封面图和多图上传功能。可以使用本地存储,或集成阿里云OSS、腾讯云COS等对象存储服务。
- 分页查询:房源列表数据量大时,必须使用分页。MyBatis-Plus提供了强大的分页插件,只需简单配置即可。
// 在配置类中配置分页插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } // 在Service中使用Page对象 Page<House> page = new Page<>(current, size); IPage<House> housePage = houseService.page(page, queryWrapper); - 缓存优化:对于首页房源列表等不常变化的热点数据,可以集成Redis进行缓存,减轻数据库压力。
- 站内信/聊天:实现租客与房东的沟通功能。可以使用WebSocket实现简单的实时聊天,或使用消息队列处理通知。
7.2 工程化与最佳实践
- 项目结构分层清晰:严格遵循
controller->service->mapper/dao->entity的分层。DTO用于前后端数据传输,VO用于页面渲染。 - 统一响应封装:为所有API接口设计统一的JSON响应格式(如包含
code,message,data字段),便于前端处理。 - 全局异常处理:使用
@ControllerAdvice和@ExceptionHandler捕获并处理各类异常,返回友好的错误信息,而不是暴露堆栈信息。 - 参数校验:在DTO中使用
javax.validation注解(如@NotNull,@Size)进行参数校验,并在Controller方法参数前添加@Valid注解。 - 日志记录:使用SLF4J + Logback记录详细的业务日志和错误日志,便于线上排查问题。
- 配置文件分离:将
application.properties拆分为application-dev.properties(开发环境)、application-prod.properties(生产环境),通过Spring Profile激活不同配置。 - API文档:集成Swagger或Knife4j,自动生成和测试API接口文档。
- 单元测试:为Service层和Controller层编写单元测试(使用JUnit和Mockito),保证核心逻辑的正确性。
这个基于Spring Boot的租房平台项目,就像一辆组装好的汽车框架,你已经拥有了发动机(Spring Boot)、车轮(MySQL、Thymeleaf)和方向盘(Controller)。要让它跑得更稳、更快、更安全,就需要不断地为它添加“功能配件”和“安全措施”。建议你以当前代码为基础,选择一两个扩展方向深入实践,例如先把Spring Security集成进去实现登录,再实现图片上传。每一步的实践都会让你对Spring Boot生态的理解更加深刻。