简介:一套基于Servlet、JSP与MySQL的JavaWeb房屋租赁管理系统源码,面向JavaWeb初学者、课程设计与毕业设计人群,也适用于物业、中介公司或个人房东管理房源,核心解决房屋租赁信息化管理问题。压缩包共含1796个文件,大小约10.06MB,主要包括Java源文件、JSP页面、CSS样式、PNG图片、编译后的class文件以及SQL脚本等,其中源码与页面文件可支撑系统运行,资源素材用于界面展示,SQL脚本用于初始化数据库。系统涵盖房源信息管理(位置、面积、租金等)、租客信息管理、租赁合同管理等模块,经过多次测试,运行稳定,便于对照学习。项目内包含Eclipse配置、web.xml部署描述符与标准WebContent目录,学习者可以借此掌握Servlet处理HTTP请求、JSP通过request和session与Servlet交互、MySQL数据增删改查以及整体工程组织方式,配合SQL脚本可快速初始化数据库,便于本地部署复现。已有3128人学习下载,适合作为JavaWeb综合实战参考。
1. 这个JavaWeb房屋租赁管理系统到底值不值得用:从课设到可运行后台
我搜了一圈“JavaWeb房屋租赁管理系统源码(servlet+jsp+mysql)”,发现大多数结果在卖打包好的项目文件,真正把架构、建表和部署流程讲透的很少。这套系统的价值在于用最朴素的servlet+jsp+mysql三件套,覆盖了登录、房源管理、租赁合同、账单统计这条完整业务链。对正在做JavaWeb课程设计的人,或者想给单位快速搭一个内部小后台的人,它比Spring Boot那套更轻,也更容易在答辩时讲清楚每个类的职责。这篇文章不替任何源码背书,只把架构、建表、核心代码和踩坑点拆开,让你拿到手能改、能跑、能说清原理。
2. 系统架构拆解:servlet+jsp+mysql三件套如何撑起一个租赁业务
2.1 分层思路:servlet当控制器,jsp当视图,mysql当存储
接手这类源码时最难受的不是看不懂代码,而是不知道请求是怎么串起来的。这套系统的请求链路是经典MVC:浏览器发起请求,web.xml把URL映射到某个servlet,servlet在doGet或doPost里调用业务对象,业务对象通过JDBC访问mysql,最后把结果塞进request或session,再forward到jsp页面渲染。jsp负责展示数据,也负责提交表单,servlet负责接收参数和跳转。
很多免费源码在jsp里直接写Java代码,这不是错,但对后续维护是灾难。我会要求自己至少把数据库查询拆到DAO类里,servlet只保留参数解析、调用和转发。下面这段登录成功后的转发逻辑,是这类项目里最常见的写法:
// LoginServlet.java @WebServlet("/login") public class LoginServlet extends HttpServlet { private UserDao userDao = new UserDao(); @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { HttpSession session = request.getSession(); session.setAttribute("loginUser", user); response.sendRedirect(request.getContextPath() + "/house/list"); } else { request.setAttribute("error", "账号或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } } }这里userDao.findByUsernameAndPassword是典型的查询操作,内部用PreparedStatement传参,避免SQL注入。注意setCharacterEncoding("UTF-8")必须在读取任何参数之前调用,否则中文用户名会乱码。response.sendRedirect和forward的区别是:重定向改变URL,转发不改变URL。登录成功后要跳转到/house/list,这是另一个servlet负责查询房源列表。表单的action写${pageContext.request.contextPath}/login,不要写死/login,否则部署到带上下文路径的tomcat下会404。
2.2 核心表结构:租客、房源、合同、账单、押金怎么建模
房屋租赁管理系统不只是一张房子表。如果只有房源表,后续的合同、账单、租客关系会越写越乱。我按业务模型拆成四张基础表和两张业务表:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| user | 系统用户(管理员/租客/房东) | id, username, password, role |
| house | 房源信息 | id, address, area, rent, status |
| tenant | 租客信息(也可并入user) | id, name, phone, idcard |
| contract | 租赁合同 | id, house_id, tenant_name, start_date, end_date, rent, deposit |
| bill | 水电/月度账单 | id, contract_id, amount, status, pay_time |
| payment | 押金与交租记录 | id, contract_id, type, amount, create_time |
建表时有两个坑一定要避开。第一个坑:合同表里必须冗余房源地址和租金快照,不冗余就等着跨表关联查得头疼。第二个坑:状态字段不要用无意义的数字,我用0/1/2表示“未出租/已出租/已下架”,但必须在代码里用常量定义,别在jsp里直接写house.status == 1,否则后来的人完全看不懂。下面给一个简化的建表SQL示例:
CREATE TABLE house ( id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(255) NOT NULL, area DECIMAL(10,2) DEFAULT 0, rent DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0空闲 1已租 2下架', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE contract ( id INT AUTO_INCREMENT PRIMARY KEY, house_id INT NOT NULL, tenant_name VARCHAR(50) NOT NULL, tenant_phone VARCHAR(20), start_date DATE, end_date DATE, rent DECIMAL(10,2) NOT NULL, deposit DECIMAL(10,2) DEFAULT 0, status TINYINT DEFAULT 1 COMMENT '1生效 0到期 2违约', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_house_id (house_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里我把tenant_name和tenant_phone直接用冗余字段放进合同表,而不是硬关联到tenant表。原因是租赁合同是凭证,签约那一刻的租客信息必须被冻结保存,后头租客换手机号,合同里保留的仍是签约时的号码,这对应数据库设计里的“快照冗余”。房源表也不做物理删除,只用status下架,避免contract表里的house_id变成悬空引用。created_at字段用DEFAULT CURRENT_TIMESTAMP,省去Java端手动设置时间。
2.3 为什么选servlet+jsp而不是spring boot:学习成本与部署轻量
经常有同学问为什么不用Spring Boot。课题点名“JavaWeb房屋租赁管理系统”用servlet+jsp+mysql,目标就不是微服务架构,而是让你吃透JavaEE最底层的请求流程。servlet+jsp是JavaWeb的地基,市面上几乎所有JavaWeb课程,包括不少黑马JavaWeb笔记数据,也都是从servlet讲起再过渡到框架。用这个组合做课设,部署只需要一个Tomcat,不需要Maven拉依赖,不需要理解自动配置,学习曲线明显平缓。
缺点也很明显:jsp页面和Java逻辑容易耦合,前端模板能力弱,做复杂交互很吃力。但对租赁管理这类后台系统,表单、列表、统计页面已经够用。我建议的取舍是:如果只做课设或内部工具,坚持servlet+jsp,把时间花在业务完整性和数据库设计上;如果要做商业软件,再考虑Spring Boot重构,那是后话。
另外,这套源码一般会包含web.xml,有些用@WebServlet注解,两种方式都可以。如果你拿到的是web.xml版本,注意看<servlet-mapping>里的url-pattern,不要和静态资源冲突。比如/house/list映射到HouseServlet,而/css/style.css不应该经过servlet链,web.xml里通常用default servlet兜底。
3. 环境准备与数据库初始化:mysql安装、建库、初始化脚本
3.1 Windows下mysql 5.7.44安装配置要点
拿到源码先别急着跑项目,先确认mysql环境。搜“mysql安装教程”会得到一堆8.0版本,但许多老源码用的是5.7。mysql 8.0的认证插件是caching_sha2_password,老JDBC驱动连接时直接报Public Key Retrieval is not allowed。所以我建议装mysql 5.7.44,这也是目前较稳定的5.7最终版本之一。安装过程不复杂,但很多人栽在Windows10上怎么安装的图形化和免安装版选择上。
常见做法是下载zip免安装包,解压到D:\mysql-5.7.44-winx64,然后新建一个my.ini,内容如下:
[mysqld] basedir=D:/mysql-5.7.44-winx64 datadir=D:/mysql-5.7.44-winx64/data port=3306 character-set-server=utf8mb4 default-storage-engine=InnoDB skip-name-resolve注意datadir指定的目录不能事先手动建好,要由mysqld自己初始化,否则后面会报错。接着用管理员身份打开cmd,依次执行:
cd /d D:\mysql-5.7.44-winx64\bin mysqld --initialize-insecure net start mysql--initialize-insecure会生成root用户且密码为空。初始化完成后用mysql -uroot -p进入,密码直接回车,立刻改密码:
mysql -uroot -p ALTER USER 'root'@'localhost' IDENTIFIED BY '123456'; FLUSH PRIVILEGES;skip-name-resolve的目的是让连接不再做反向DNS解析,速度更快,代价是权限表里的host必须写IP或localhost。如果连接时提示Access denied for user 'root'@'localhost',多半是host写错了。现在很多人喜欢用docker安装mysql,失败率不低,常见失败点是端口映射和字符集;单机学习不建议折腾docker,直接Windows本机装更省事。mysql 8.4.11 LTS也发布了,但为了兼容这套servlet+jsp源码里的老JDBC驱动,我仍建议先跑5.7。
3.2 建库建表SQL:把房源、合同、账单落到实际字段
环境就绪后建库。以house_rental库为例,把所有建表语句放在一个init.sql中一次导入,避免每张表手打。除了前面提到的house和contract,还需要user、bill表。完整初始脚本如下:
CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rental; CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT 'admin', real_name VARCHAR(50) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE house ( id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(255) NOT NULL, area DECIMAL(10,2), rent DECIMAL(10,2), status TINYINT DEFAULT 0, remark VARCHAR(500) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE contract ( id INT AUTO_INCREMENT PRIMARY KEY, house_id INT NOT NULL, tenant_name VARCHAR(50) NOT NULL, tenant_phone VARCHAR(20), start_date DATE, end_date DATE, rent DECIMAL(10,2), deposit DECIMAL(10,2) DEFAULT 0, status TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_house_id (house_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE bill ( id INT AUTO_INCREMENT PRIMARY KEY, contract_id INT NOT NULL, amount DECIMAL(10,2), status TINYINT DEFAULT 0 COMMENT '0待缴 1已缴', due_date DATE, pay_time DATETIME, KEY idx_contract_id (contract_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;执行导入命令:mysql -uroot -p house_rental < init.sql,或用图形工具直接跑。表设计说明:user表没有外键,登录和业务模块解耦;contract冗余tenant_name和tenant_phone,是为了后期打印合同不再关联租客表;bill里的due_date用来判断逾期待缴。源码中的DAO类通常对应每张表,例如HouseDao.findById(int id)返回一个House对象。建表后顺手过一遍mysql常用命令,比如SHOW CREATE TABLE house;能快速看表结构是否按预期生成。后面做分页查询时注意mysql排序相关的语法,比如按创建时间倒序用ORDER BY created_at DESC,以及LIMIT ? , ?分页时offset从0开始,Service层要把页码转成offset = (page - 1) * size。
3.3 连接池与JDBC配置:避免每次请求都new Connection
很多免费源码在每个DAO方法里直接写DriverManager.getConnection(url, user, password),这能跑,但并发一大就卡。我至少会用连接池。考虑到课设不需要太多依赖,可以用dbcp或c3p0,但很多servlet+jsp源码用了Druid,带监控更好用。配置druid.properties:
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/house_rental?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username=root password=123456 initialSize=5 maxActive=20 maxWait=60000然后在项目里用一个JdbcUtils类加载配置。注意JDBC驱动:mysql 5.7用com.mysql.jdbc.Driver,mysql 8.0改成com.mysql.cj.jdbc.Driver,混用会报ClassNotFoundException。更关键的是连接池的管理方式:用ThreadLocal保存当前线程的Connection,让Service层在同一个事务里使用同一个连接,避免各DAO拿到不同连接导致事务失控。具体写法在第4章新增合同时展开。
4. 用servlet+jsp实现核心业务:从登录到租房订单的完整链路
4.1 登录与权限:HttpSession和Filter拦截器的写法
登录模块不能只做一个表单比对,权限拦截必须做。否则用户直接敲/house/list就能绕过登录页,这类课设源码里太多了。下面这段Filter是最常见的做法:
// AuthFilter.java @WebFilter("/*") public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String path = request.getRequestURI(); if (path.endsWith("/login") || path.endsWith("/login.jsp") || path.contains("/css/") || path.contains("/js/")) { chain.doFilter(req, resp); return; } HttpSession session = request.getSession(false); if (session != null && session.getAttribute("loginUser") != null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() + "/login.jsp"); } } }这里用request.getSession(false)避免未登录用户各自创建空session。Filter放行静态资源和登录接口,否则css加载不出来,页面会很难看。如果项目用了@WebFilter注解,要注意tomcat版本,老版本不支持,需要改到web.xml里配置。
登录成功后,session里存的是User对象。在jsp中可以直接用${sessionScope.loginUser.realName}显示姓名,这对应很多“jsp个人信息展示页面”的写法。注意默认jsp启用了EL表达式,不需要额外引包;但如果是老项目,可能在page指令里禁用了isELIgnored="true",那就要去掉这一项。
4.2 房源列表与分页:jsp+servlet怎么传参不乱码
租赁管理里房源列表是高频页面,分页不能只把全部数据塞进List,房源多了jsp渲染会变慢。把分页参数封装成一个Page对象,包含currentPage、pageSize、totalCount、list。HouseServlet分页核心片段:
// HouseServlet.java int page = 1; int size = 5; String pageStr = request.getParameter("page"); if (pageStr != null && !pageStr.trim().isEmpty()) { page = Integer.parseInt(pageStr); } String address = request.getParameter("address"); if (address == null) address = ""; HouseDao dao = new HouseDao(); List<House> list = dao.findByPageAndAddress(page, size, address); int total = dao.countByAddress(address); int totalPages = (total + size - 1) / size; request.setAttribute("list", list); request.setAttribute("totalPages", totalPages); request.setAttribute("currentPage", page); request.setAttribute("address", address); request.getRequestDispatcher("/house_list.jsp").forward(request, response);这里Integer.parseInt(pageStr)没有做try-catch,如果用户传page=abc会抛NumberFormatException。实战中我会加兜底,解析失败就重置为1。请求参数的中文乱码有三个地方要同时做:jsp页面用<%@ page contentType="text/html;charset=UTF-8" %>,servlet开头request.setCharacterEncoding("UTF-8"),以及tomcat的server.xml里给Connector加URIEncoding="UTF-8"。少任何一个,地址栏输入中文搜房源都可能变成问号。
分页链接不要用out.println直接拼,写在jsp的HTML片段里更好维护。如果不想引入JSTL,可以用三元表达式,但可读性差。下面是最简单的分页条:
<div class="pagination"> 共<span>${totalPages}</span>页 <c:if test="${currentPage > 1}"> <a href="${pageContext.request.contextPath}/house/list?page=${currentPage-1}&address=${address}">上一页</a> </c:if> <span>第${currentPage}页</span> <c:if test="${currentPage < totalPages}"> <a href="${pageContext.request.contextPath}/house/list?page=${currentPage+1}&address=${address}">下一页</a> </c:if> </div>使用c:if需要引入JSTL库,课设阶段可以加,不算重。注意address原样拼在URL里有编码隐患,正规做法是URLEncoder编码。这里踩过坑的人都知道,翻车现场通常是点下一页后地址栏出现乱码,搜索条件全部丢失。
4.3 新增租赁合同:事务处理与参数校验的坑
新增合同不是只往contract表插一行,还要同步把house表的status改为1,并且生成押金账单。如果两次操作各自提交,第二次失败就会合同在但房源仍空闲,数据不一致。我的习惯是用同一个Connection,开启手动事务:
// ContractService.java public void createContract(Contract contract) throws SQLException { Connection conn = JdbcUtils.getConnection(); conn.setAutoCommit(false); try { contractDao.insert(conn, contract); houseDao.updateStatus(conn, contract.getHouseId(), 1); billDao.insertDeposit(conn, contract.getId(), contract.getDeposit()); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); JdbcUtils.release(conn); } }JdbcUtils.getConnection()是从连接池拿的,关闭时会归还连接池而不是真正断开。事务开启后,所有DAO方法必须接收这个conn参数,不能再自己从池里拿新连接,否则回滚无效。这是新手最容易忽略的:以为调了conn.commit()就万事大吉,其实DAO里早就偷偷开了另一个连接。
参数校验方面,日期格式、租金正数、房源状态要在servlet层先查一次,Service层再查一次。双重校验的原因是servlet层拦截明显恶意输入,Service层保护核心业务不被其他地方误调用。日期解析用java.time.LocalDate.parse配合DateTimeFormatter,不要用new Date(String),不同服务器时区会导致日期偏移。如果提示Text '2025-05-01' could not be parsed,多半是字符串里带空格,用replace(" ", "")清理即可。
有人会把“插入合同+更新房源+生成账单”写成一个mysql存储过程,用CALL create_contract(...)调用,这种写法能减少Java端事务控制,但存储过程调试困难、可移植性差。我的项目除非性能瓶颈明显,否则更愿意把业务写在Service层。如果你只想快速通过答辩,存储过程是加分项,但要在mysql命令行里先测通。
5. 部署运行中的避坑与常见问题排查
5.1 现象:idea运行javaweb项目配置后一直404
IDEA里启动tomcat后页面404,原因通常是两个。一是Artifact没部署到tomcat的Deployment里,项目的war exploded没有添加。二是在IDEA的Run Configuration的Deployment标签页里添加Artifact时,Application context没填对。比如你填了/house_rental_war_exploded,访问路径就必须带这个前缀。
解决方法是先确认Deployment里有Artifact,Application context填/或项目名;然后在浏览器按实际上下文访问。如果能看到tomcat欢迎页但项目资源没加载,检查Facet里web.xml位置和web资源目录是否配置正确。直接把war拷进tomcat的webapps目录时,要注意目录名是否符合预期,比如house_rental.war解压后目录名是house_rental,URL就要带/house_rental。
还有一个常见问题是源码里用了@WebServlet("/house/list"),但IDEA的Artifact没有自动把编译后的class和jsp打包进去,导致资源被IDE偷偷过滤。排查时看IDEA控制台有没有类加载异常,或者target目录里缺了哪些文件。
5.2 现象:mysql连接失败,Communications link failure
这个报错五花八门,常见的是Access denied和Communications link failure。Access denied原因是用户名密码错误或host不对,确认连接串的username和password与mysql里的root一致。Communications link failure一般是端口不通或驱动版本不对。
用netstat -ano | findstr 3306看看端口是否被监听,确认mysql服务已启动。连接串里useSSL=false不能少,JDBC 8.0以上驱动如果没加serverTimezone=Asia/Shanghai会直接报时区错误。5.7数据库用com.mysql.jdbc.Driver,8.0数据库用com.mysql.cj.jdbc.Driver,两者不能混用。另外mysql 8.0默认认证插件导致老驱动连不上,解决办法是装5.7或升级驱动依赖。
5.3 现象:jsp页面中文乱码
中文字符显示成???或好,原因有三个:页面编码不是UTF-8、servlet没设置request编码、数据库连接串字符集不对。解决路径依次检查:
jsp第一行必须有<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>。servlet的doGet/doPost第一行加request.setCharacterEncoding("UTF-8")。mysql的my.ini里character-set-server=utf8mb4。表结构也要用utf8mb4。执行SHOW VARIABLES LIKE 'character%'确认客户端、连接、服务器三个都是utf8。
如果从数据库读出来的中文变成问号,大概率是表字段字符集不对,用ALTER TABLE house CONVERT TO CHARACTER SET utf8mb4修复。get请求参数乱码,需要在tomcat的server.xml里给Connector设置URIEncoding="UTF-8"。POST请求乱码,重点查servlet里的setCharacterEncoding是否写在第一行。
5.4 现象:tomcat启动后端口被占用
启动报Port 8080 was already in use,原因很简单:之前启动的tomcat没关闭,或者别的程序占用了8080。解决:
netstat -ano | findstr 8080 taskkill /PID <pid> /F或者干脆在server.xml里改端口为8081。注意改端口后,访问路径也要跟着改。如果是IDEA启动的tomcat实例,在控制台点红色停止按钮,不要直接关窗口。IDEA里经常出现“Tomcat没有真正停止”的问题,结束进程后需要到任务管理器里手动结束Java进程。
5.5 现象:合同提交后房源状态没变成已租
这通常是事务没生效,要么DAO内部自己拿了新连接,要么conn.setAutoCommit(false)被覆盖。解决:在DAO方法里把当前连接hashCode打出来,确认是否同一个。如果发现是不同连接,修改DAO方法签名,强制要求传Connection参数。
另一个常见原因是提交表单时houseId被篡改,攻击者手动改POST参数就能把别人已住的房源重新出租。解决:Service层先查house表状态,只有status为0才允许创建合同。并发提交时还要注意mysql锁的分类里InnoDB的行锁和间隙锁,典型报错是Deadlock found when trying to get lock; try restarting transaction。解决方式是缩小事务范围,先更新house状态再插入合同,避免两个事务互相等锁。如果项目里同时跑了多个tomcat实例,这个死锁更容易触发,必要时要对house_id加唯一索引约束。
还有一个反直觉的坑:合同表里插入成功,但house表update影响了0行,代码却仍然commit。原因是update语句里漏了where id = ?,或者传了null。调试时记得打印受影响行数,int rows = update.executeUpdate(),如果rows不是1就要抛异常回滚。
6. 把系统做厚:从能跑到能用的三个验证与优化技巧
6.1 用EXPLAIN验证分页查询是否走索引
房源列表页在数据量小的时候看不出问题,数据过千后,findByPageAndAddress会明显变慢。用mysql的EXPLAIN命令看执行计划:
EXPLAIN SELECT * FROM house WHERE address LIKE '%花园%' ORDER BY created_at DESC LIMIT 5, 5;如果type列是ALL或Extra列出现Using filesort,说明没走索引或排序没用上索引。解决:给address加普通索引,给created_at加索引。但注意LIKE '%xxx%'本身会让索引失效,换成前缀查询LIKE 'xxx%'才行。分页深翻页时LIMIT 10000, 5会很慢,可以用WHERE id > ? ORDER BY id LIMIT 5的键集分页替代。
6.2 用状态机约束代替分散的if判断
合同状态和房源状态在多个servlet里会被重复判断,比如“只有生效中的合同才能生成账单”“只有已出租的房源才能退租”。我习惯用枚举类把这些状态机收口,而不是在jsp或servlet里写散落的status == 1。这样后续加“退租”操作时,只需要在状态机里定义从“已租”到“空闲”的转换,不让别人绕过。
6.3 验证清单:拿一条真实业务串一遍
在交付源码前,我会按“新增房源→提交租房合同→生成账单→缴纳押金→合同到期退租”这条链路完整走一遍,每一步都看数据库对应表数据变化。重点检查中间状态是否残留,比如退租后house.status是否回到0,bill表是否生成退款记录。我曾经因为漏掉“合同到期自动改状态”被质疑过,后来在定时任务里补了一个UPDATE contract SET status=0 WHERE end_date < CURDATE() AND status=1,这才把完整闭环补上。
这套系统做好后,最好再补一个简单的操作日志表,记录谁在什么时候改了什么房源,不然有人误操作后查不到责任。我的习惯是在Service层统一写日志,而不是在各个servlet里加,这样能防止日志漏记。希望这个方向能帮到你,也祝你的JavaWeb项目一次跑通。
本文还有配套的精品资源,点击获取