news 2026/10/9 3:17:41

Servlet+JSP+MySQL房屋租赁系统:从零搭建与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Servlet+JSP+MySQL房屋租赁系统:从零搭建与避坑指南

简介:这是一套基于Servlet的房屋租赁管理信息系统完整源码,采用Java+Servlet+JSP+MySQL技术栈,运行于Tomcat7及以上环境,适合Java Web初学者、课程设计或毕业设计参考者学习使用。系统分为管理员与普通用户两种角色,前台涵盖会员注册登录、密码找回、新闻中心、出租求租、出售求购、信息检索、留言板等模块,后台则提供新闻资讯、会员、留言、滚动公告、出租出售求租信息、预定定金合同、收付款、房屋统计报表、销售租金报表及登录日志等管理功能,业务链条较为完整。压缩包共681个文件,约2.6MB,以gif图片、js脚本、jsp页面、html静态页、java源码与class文件、css样式为主,另含sql建库脚本与少量配置说明文件,便于直接导入运行与二次开发。目前已有262人学习下载,可作为理解Servlet分层开发、JSP页面交互与数据库操作的实战参考。

1. 从一份课程设计说起:Servlet 房屋租赁系统到底解决什么问题

很多 Java Web 初学者在学完 Servlet、JSP、MySQL 之后,最大的困惑不是“不会写”,而是“不知道把这些东西拼起来能做什么”。房屋租赁管理信息系统就是一个非常典型的练手场景:它同时涉及房源信息维护、租客管理、合同记录、前后台权限区分,几乎覆盖了 Java Web 开发中最核心的增删改查、会话管理和页面跳转逻辑。用 Servlet 做控制器、JSP 做视图、MySQL 做持久化,这套组合虽然看起来“老”,但恰恰是理解 MVC 分层最直接的方式。你不需要先学 Spring Boot 才能看懂请求怎么进来、数据怎么出去。对于要交课程设计、准备 Java 面试题、或者想从零搭一个能跑起来的管理系统的人来说,这个方向值得投入,因为它的每一层你都能亲手摸到,而不是被框架封装成黑匣子。

2. 技术选型与分层设计:为什么不用框架也能把系统跑通

2.1 Servlet + JSP + MySQL 的职责边界

在动手写代码之前,先把每一层该干什么定清楚,否则后面一定会出现“JSP 里写数据库连接”这种翻车现场。Servlet 负责接收 HTTP 请求、调用业务方法、控制跳转;JSP 只负责把后端传过来的数据渲染成 HTML;MySQL 负责存房源、租客、合同、用户这些表。业务逻辑如果简单,可以直接写在 Servlet 里;如果稍微复杂一点,建议抽一个 Service 层,哪怕只是几个静态方法,也能让代码可读性提升一个档次。

常见做法是:com.rental.servlet放所有 Servlet 类,com.rental.dao放数据库操作,com.rental.entity放实体类,com.rental.util放数据库连接工具。JSP 统一放在WEB-INF/jsp下面,前台和后台分目录,避免直接通过 URL 访问到未经过权限校验的页面。

提示:不要把 JSP 放在 WebContent 根目录下直接访问,否则用户可能绕过 Servlet 直接打开后台页面。

2.2 数据库表设计:五张核心表就能撑起前后台

房屋租赁系统的表不用一开始就设计得特别复杂,但下面这五张表基本是绕不开的:

表名用途关键字段
user登录用户(管理员/租客)id, username, password, role
house房源信息id, title, address, price, status
tenant租客信息id, name, phone, id_card
contract租赁合同id, house_id, tenant_id, start_date, end_date
notice公告信息id, title, content, create_time

建表时注意status字段用整数表示房源状态(比如 0 未租、1 已租),不要用中文直接存,否则后面查询和排序都会很别扭。contract表里的house_id和tenant_id要加外键约束,虽然有些课程设计会忽略外键,但加上之后能帮你避免脏数据。

CREATE TABLE house ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, address VARCHAR(200), price DECIMAL(10,2), status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段 SQL 里status用TINYINT而不是VARCHAR,是为了后面做筛选时可以直接用WHERE status = 0,不用做字符串比较。create_time默认当前时间,省去手动插入的麻烦。字符集用utf8mb4而不是utf8,避免中文乱码和特殊符号问题。

2.3 前后台权限控制:一个 Filter 解决大部分问题

前后台最大的区别不是页面长得不一样,而是权限。后台只有管理员能进,前台租客登录后只能看自己的合同。最省事的做法是写一个AuthFilter,在doFilter里判断session中是否有用户信息,以及当前请求路径是否属于后台。

public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String uri = request.getRequestURI(); HttpSession session = request.getSession(); Object user = session.getAttribute("user"); // 后台路径必须以 /admin/ 开头,且用户角色必须是 admin if (uri.contains("/admin/")) { if (user == null || !"admin".equals(((User) user).getRole())) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } } chain.doFilter(req, res); } }

这段过滤器的逻辑很直接:先拿到请求路径和 session 中的用户对象,如果访问的是/admin/下的资源,但用户没登录或者角色不是管理员,就直接重定向到登录页。注意return不能漏,否则请求会继续往下走。在web.xml里配置这个 Filter 时,url-pattern建议写/*,然后在代码里判断路径,而不是配多个url-pattern,这样后期加路径不用改配置。

3. 从零跑通一个房源列表:Servlet 传数据、JSP 渲染的最小闭环

3.1 写一个 HouseListServlet 把数据查出来

先不要想着一次性把整个系统写完,先跑通“查房源列表并显示在页面上”这个最小闭环。只要这个通了,后面的新增、修改、删除都是同样的套路。

@WebServlet("/house/list") public class HouseListServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<House> list = new ArrayList<>(); String sql = "SELECT id, title, address, price, status FROM house ORDER BY id DESC"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { House h = new House(); h.setId(rs.getInt("id")); h.setTitle(rs.getString("title")); h.setAddress(rs.getString("address")); h.setPrice(rs.getBigDecimal("price")); h.setStatus(rs.getInt("status")); list.add(h); } } catch (SQLException e) { e.printStackTrace(); } request.setAttribute("houseList", list); request.getRequestDispatcher("/WEB-INF/jsp/house/list.jsp").forward(request, response); } }

这里用try-with-resources自动关闭连接、语句和结果集,避免忘记关连接导致连接池耗尽。PreparedStatement即使没有参数也建议用,养成习惯,后面加条件查询时不用改结构。查询结果放到request域里,然后forward到 JSP,而不是redirect,因为redirect会丢失 request 域中的数据。

3.2 JSP 页面用 JSTL 遍历,别写 Java 脚本片段

JSP 里最忌讳的就是<% %>里写一堆 Java 代码,尤其是数据库操作。正确做法是用 JSTL 的<c:forEach>遍历 Servlet 传过来的houseList。

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table border="1"> <tr> <th>编号</th><th>标题</th><th>地址</th><th>价格</th><th>状态</th> </tr> <c:forEach items="${houseList}" var="h"> <tr> <td>${h.id}</td> <td>${h.title}</td> <td>${h.address}</td> <td>${h.price}</td> <td>${h.status == 0 ? '未租' : '已租'}</td> </tr> </c:forEach> </table>

<c:forEach>的items属性直接写 EL 表达式${houseList},var是每次循环的变量名。状态字段用三元表达式在页面上转成中文,而不是在数据库里存中文。如果houseList为空,页面会显示一个空表格,可以在表格外面加<c:if test="${empty houseList}">暂无房源</c:if>做友好提示。

3.3 数据库连接工具类:别每次都在 Servlet 里写 DriverManager

如果每个 Servlet 都写一遍DriverManager.getConnection,代码会重复到让你后悔。抽一个DBUtil类,用静态代码块加载驱动,提供一个getConnection方法。

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/rental?useSSL=false&serverTimezone=UTC"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL驱动加载失败", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }

URL里的useSSL=false和serverTimezone=UTC是 MySQL 8.0 以上版本必须加的,否则会报时区错误或者 SSL 警告。驱动类名是com.mysql.cj.jdbc.Driver,不是老版本的com.mysql.jdbc.Driver。如果用的是 MySQL 5.7,驱动类名可以兼容,但建议统一用新的。密码不要硬编码在代码里,课程设计可以接受,但实际项目应该放到配置文件里。

4. 避坑与排查:那些让系统跑不起来的常见问题

4.1 中文乱码:POST 和 GET 要分开处理

现象:表单提交后,数据库里存进去的中文变成???或者乱码。原因:POST 请求默认用 ISO-8859-1 解码,GET 请求在 Tomcat 8 之后默认用 UTF-8,但之前版本不是。解决:在 Filter 里统一设置request.setCharacterEncoding("UTF-8")和response.setContentType("text/html;charset=UTF-8")。注意setCharacterEncoding必须在读取任何参数之前调用,否则无效。

4.2 数据库连接报错:驱动版本和时区

现象:启动后第一次访问数据库就抛No suitable driver found或者The server time zone value is unrecognized。原因:要么没把 MySQL 驱动 jar 放到WEB-INF/lib下,要么 JDBC URL 没加时区参数。解决:确认mysql-connector-java的 jar 包在WEB-INF/lib目录里,并且 URL 写成jdbc:mysql://localhost:3306/rental?useSSL=false&serverTimezone=Asia/Shanghai。如果用的是 MySQL 8.0,驱动类名必须是com.mysql.cj.jdbc.Driver。

4.3 JSP 页面报 404:路径和 WEB-INF 的关系

现象:Servlet 写好了,浏览器访问却返回 404。原因:JSP 放在WEB-INF下,不能通过浏览器直接访问,必须经过 Servlet 转发。解决:所有 JSP 都通过 Servlet 的forward到达,不要试图在地址栏直接输入/WEB-INF/jsp/house/list.jsp。另外检查@WebServlet的路径是否和访问的 URL 一致,大小写敏感。

4.4 修改功能失效:表单里漏了 id 隐藏域

现象:点击“编辑”后,表单提交保存,数据库里数据没变或者新增了一条。原因:更新操作需要根据 id 来UPDATE,但表单里没有把 id 传回来。解决:在编辑页面的表单里加<input type="hidden" name="id" value="${house.id}">,Servlet 里用request.getParameter("id")获取。没有这个隐藏域,更新就会变成插入。

4.5 删除后列表不刷新:重定向和转发的区别

现象:删除成功后,页面还是显示旧数据。原因:删除后用了forward到列表页,但列表数据是之前 request 域里的,没有重新查询。解决:删除成功后用response.sendRedirect(request.getContextPath() + "/house/list"),让浏览器重新发起一次请求,这样列表 Servlet 会重新查数据库。

5. 进阶技巧:用分页和条件查询让系统更像一个真实项目

5.1 手写分页:LIMIT 加两个参数就够了

课程设计里如果房源列表一页显示所有数据,看起来会很假。加一个分页并不难,核心就是 SQL 的LIMIT和两个参数:当前页码和每页条数。

int page = request.getParameter("page") == null ? 1 : Integer.parseInt(request.getParameter("page")); int pageSize = 10; int offset = (page - 1) * pageSize; String sql = "SELECT * FROM house ORDER BY id DESC LIMIT ? OFFSET ?";

LIMIT ?是每页条数,OFFSET ?是跳过的行数。页码从 1 开始,offset就是(page - 1) * pageSize。还需要一个 count 查询来算总页数,用于在页面上显示“上一页/下一页”。注意page参数要做空值和数字校验,否则用户手动改 URL 传个abc进来会直接抛异常。

5.2 条件查询:用 StringBuilder 拼 SQL 但别拼参数

按标题、地址、状态筛选房源时,SQL 是动态的,但参数必须用PreparedStatement的?占位,不能直接拼字符串。

StringBuilder sql = new StringBuilder("SELECT * FROM house WHERE 1=1"); List<Object> params = new ArrayList<>(); if (title != null && !title.isEmpty()) { sql.append(" AND title LIKE ?"); params.add("%" + title + "%"); } if (status != null && !status.isEmpty()) { sql.append(" AND status = ?"); params.add(Integer.parseInt(status)); }

WHERE 1=1是一个常用技巧,方便后面统一用AND拼接。params列表按顺序存参数,最后遍历设置到PreparedStatement里。这样既避免了 SQL 注入,又不会因为条件缺失导致语法错误。

5.3 用 Session 存登录用户,但别存太多东西

登录成功后把User对象放到session里,方便后续页面判断权限和显示用户名。但不要把整个用户列表或者大量数据塞进 session,否则内存占用会上去,而且 session 序列化时容易出问题。一般只存id、username、role三个字段就够了。退出登录时调用session.invalidate()清空整个会话,而不是只移除一个属性。

5.4 合同到期提醒:用 SQL 日期函数做简单判断

在合同列表里,可以加一列显示“是否到期”。不需要在 Java 里算日期,直接用 MySQL 的DATEDIFF函数。

SELECT c.id, c.start_date, c.end_date, DATEDIFF(c.end_date, CURDATE()) AS days_left FROM contract c WHERE c.tenant_id = ?

days_left小于 0 表示已到期,小于 30 表示即将到期。页面上用 JSTL 判断${c.daysLeft < 0 ? '已到期' : (c.daysLeft < 30 ? '即将到期' : '正常')}。这样不用在 Java 里写日期计算,减少出错概率。

5.5 最后说一个我自己的习惯

每次写完一个 Servlet,我都会先在浏览器里直接访问它的 URL,看返回的是 500 还是 404,再去看 Tomcat 控制台的异常堆栈。很多问题其实堆栈第一行就写清楚了,比如ClassNotFoundException就是驱动没放对位置,NullPointerException就是某个参数没传。不要一上来就改代码,先看日志。这个习惯帮我省下了大量瞎猜的时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 3:17:37

Java实现数值分析、线性代数与机器学习算法:从公式到工程落地

简介&#xff1a;这是一套面向Java开发者、科研人员与学生的基础算法源码包&#xff0c;聚焦数值分析、线性代数与机器学习三大方向&#xff0c;帮助使用者在Java项目中快速嵌入数学计算与建模能力&#xff0c;降低从底层实现到应用验证的门槛。资源共45个文件&#xff0c;以36…

作者头像 李华
网站建设 2026/10/9 3:17:33

Git核心技能实操:从安装配置到疑难杂症的全套考题与参考答案

很多刚接触 Git 的同学容易陷入一个误区&#xff1a;命令背了一堆&#xff0c;碰到真实项目还是一头雾水。最近我在团队内部做了一次 Git 核心技能摸底&#xff0c;把这几年踩过的坑、带新人时常遇到的问题、以及日常评审时发现的高频失误&#xff0c;全部揉成了一套实操考试题…

作者头像 李华
网站建设 2026/10/9 3:17:24

测试文章发布指南:版本号、检查清单与避坑技巧

我们做内容创作这些年&#xff0c;最怕听到的一句话往往不是“标题不够吸引人”&#xff0c;而是“我刚才好像不小心把测试文章发出去了”。说真的&#xff0c;测试文章这种东西&#xff0c;平时它安安静静躺在草稿箱里&#xff0c;一旦在错误的时间被推上线&#xff0c;轻则排…

作者头像 李华
网站建设 2026/10/9 3:16:03

OpenClaw Skill机制详解与精选清单:从入门到全平台部署实操

最近一直在折腾OpenClaw&#xff0c;从一个只会拿来聊天的普通用户&#xff0c;到慢慢把各种skill玩出花来&#xff0c;这个过程踩了不少坑&#xff0c;也攒了不少心得。OpenClaw这套东西&#xff0c;说白了就是一个开源的AI助手平台&#xff0c;核心思路是把“大模型对话”变成…

作者头像 李华
网站建设 2026/10/9 3:16:01

校园二手交易平台Java开发实战:轻量级生产系统搭建指南

简介&#xff1a;本资源是一个基于Java技术栈开发的校园二手交易平台完整项目源码包&#xff0c;面向计算机专业本科生、Java初学者及Web应用开发学习者&#xff0c;旨在解决高校学生间教材、数码产品、生活用品等闲置物品高效流转的实际需求。压缩包共440个文件&#xff0c;体…

作者头像 李华