简介:一套基于Servlet、JSP与MySQL构建的房屋租赁管理系统源码,面向高校JavaWeb课程设计、毕业设计及自学开发者,覆盖房源信息、租客资料、租赁合同等典型业务模块,能帮助读者快速搭建一个可运行的Web管理项目。压缩包共1796个文件,大小约10.06MB,核心包括java业务源码、jsp动态页面、编译后class文件及sql数据库脚本,同时附带png界面图片、css样式和Eclipse工程配置(.classpath、.project),结构划分清晰,便于按类型检索。系统经过多轮严谨测试,运行稳定,目前已有3127人学习下载。项目中包含登录、房源发布、租约记录等功能模块,可结合web.xml部署描述符与过滤器配置理解请求路由过程;研读完整源码还能掌握sql脚本导入、Servlet与JSP数据交互、MySQL持久化实现的完整链路,为独立开发同类管理系统提供扎实参考。
1. JavaWeb房屋租赁管理系统源码:为什么它值得当作 JavaWeb 的练手样本
以 Servlet + JSP + MySQL 为技术栈的房屋租赁管理系统,是 JavaWeb 课程设计和毕业设计里出现频率最高的项目类型之一。它不新、不花哨,但把 JavaWeb 最核心的动作都覆盖到了:Servlet 接收请求、JDBC 操作 MySQL、JSP 渲染页面、Session 管登录权限、连接池扛并发。对正在选题或要交课设的同学来说,它比图书管理系统业务更完整,比 SSM/SpringBoot 项目更容易讲清楚原理,跑通后还扛得住答辩追问。对想补 JavaWeb 底子的开发者也一样,顺着房屋租赁的业务流去读源码,一个请求从浏览器到数据库再回到页面,整条链路比看十集教程都直观。下面按选型、设计、运行、排错、改造的顺序,把这一套完整拆开。
2. 选型拆解:Servlet + JSP + MySQL 这套组合为什么到今天还在被用
2.1 Servlet 的控制器角色:先看生命周期,再看单例陷阱
拿到一套 Servlet+JSP 源码,第一步要看包结构。规整的源码通常是 controller、dao、entity、util、filter 五个包,实体类对应表,DAO 负责 SQL,Servlet 做参数接收和页面跳转,Filter 统一处理编码和登录状态。如果打开压缩包发现所有类平铺在一个包下面,Servlet 和工具类混在一起,那这个项目跑通没问题,但讲分层的时候会很吃力。
Servlet 是这套系统的请求入口。浏览器敲下 URL 之后,请求先打到 web.xml 里的 servlet-mapping,或者类上的 @WebServlet 注解映射路径,再进入 doGet 或 doPost 处理。房屋租赁系统里的常见动作——查房源列表、新增租客、签订合同、登记缴费,最后都会落到某个 Servlet 的这两个方法里。读源码时先别急着看方法体,先把类的继承关系和映射路径捋出来,能少走很多弯路。
Servlet 生命周期回答三个问题:实例什么时候创建、service 方法什么时候被调用、对象什么时候销毁。容器启动或首次请求时完成类加载和实例化,init 只执行一次;之后每次请求由容器从线程池里分配线程调用 service,service 再根据请求方法转发到 doGet/doPost;应用卸载时执行 destroy 释放资源。规整的源码里能看到 init 中初始化数据源、destroy 中关闭连接池,这就是标准写法,面试被问 Servlet 生命周期时可以直接拿自己的代码举例。
这里有一个特别容易被忽略的细节:Servlet 是单实例多线程。成员变量一旦被改成可变状态,两个并发请求就会互相踩。我评审类似的课设源码时,经常看到 Servlet 里放一个 SimpleDateFormat 或者一个可写的 List 成员,这都属于隐患。SimpleDateFormat 不是线程安全的,多个线程同时 format 同一个实例,轻则结果错乱,重则抛 NumberFormatException。正确做法是把这些对象改成局部变量,或者用 ThreadLocal 包一层。另外,request.getParameter 拿到的一定是 String,转数字时如果前端传了空字符串,Integer.parseInt 会直接抛异常,防御写法是抽一个参数工具类,先判空再转换。
2.2 JSP 是披着网页外衣的 Servlet:渲染和业务得分家
JSP 在源码里的角色是视图层。它的本质还是被容器编译成一个 Servlet class:容器把 JSP 里的静态 HTML 原样写进 out.write,把脚本片段编译成 Java 代码塞进 _jspService 方法。这就是为什么 JSP 能直接用 request、response、session、application 这组内置对象。刚接触 JSP 的人可以记住一句话:JSP 能用的,Servlet 都能用,JSP 只是把输出 HTML 的活儿变得更顺手。
页面上的房源列表、合同详情、账单表格,在源码里通常会混合三种写法:纯 HTML 标签、Java 脚本片段(scriptlet)和 EL/JSTL 表达式。老一点的项目大量使用<% %>写循环和判断,读起来像把 Java 代码揉进了 HTML,改一个样式要在一堆尖括号里找半天。稍规整一点的源码会刻意限制 scriptlet,用<c:forEach>、${}来渲染数据。拿到源码建议先看 JSP 顶部和列表页,如果 scriptlet 占了一大半,说明视图层没有经过重构,后面改造工程量会比较大。
JSP 偏重的部分是前端交互——菜单、分页、弹窗,基本靠 jQuery 加一套后台模板撑着。房屋租赁管理系统通常不需要复杂交互,表单提交后刷新列表页这种传统模式完全够用。这也是这套选型到今天还没过时的原因:业务简单、交互轻,服务端渲染反而比前后端分离少写一层接口。判断源码质量的一个小技巧是看页面里有没有把 JDBC 查询直接写进 JSP。如果看到<% Connection conn = ... %>这种开头,说明职责完全没有分离,这种代码跑起来能用,但改起来会痛。
2.3 MySQL 侧:JDBC 连接池和预编译才是这套源码的灵魂
数据库访问层是这类源码里水分最大的部分。规整的做法是 DAO 模式:每个实体对应一个 DAO 类,DAO 里用 JDBC 执行 SQL,把 ResultSet 里的列封装成实体对象。不规整的做法是把查询逻辑直接写在 Servlet 里,每访问一次数据库就 DriverManager.getConnection 一次。后者在课设源码里非常常见,跑起来没问题,但并发一高就会暴露问题,因为 Connection 的创建和销毁是 JDBC 里最贵的一步。
正确做法是用连接池,常见的是 DBCP 或 C3P0,配置一个 initialSize、maxActive、maxIdle,让连接复用起来。数据库连接池在源码里通常表现为一个 DbUtil 工具类,静态代码块里加载配置,getConnection 从池里取连接,close 把连接还回池子而不是真正关闭。你可以去找这个类,数一下池参数有没有生效,如果只是 new 了一个 BasicDataSource 但没用它的 getConnection,等于白配。
SQL 预编译是另一个必看的地方。用 PreparedStatement 的 ? 占位符传参,而不是 String 拼接 SQL。拼接写法一旦遇到用户输入的内容里带引号,轻则 SQL 语法错误,重则被注入。房屋租赁系统里的租客姓名、身份证号、地址都是用户可控输入,登录页更是注入重灾区。拿到源码可以先全局搜一下 statement.,再搜 preparedStatement.,两类出现的比例基本能反映这套代码的成熟度。建库时字符集选 utf8mb4 而不是 utf8,因为 utf8 在 MySQL 里最多存 3 字节,遇到生僻字或 emoji 会失败,utf8mb4 是它的超集,排序规则用 utf8mb4_general_ci 就够。
3. 数据库设计与业务流转:房源、合同、账单怎么用五张表串起来
3.1 五张核心表:从管理员到账单的建表脚本
房屋租赁管理系统不管界面怎么变,核心表跑不出这五张:管理员表(t_user)、房源表(t_house)、租客表(t_tenant)、合同表(t_contract)、账单表(t_bill)。业务关系是一对多的:一套房源可以有多个历史合同,一个租客也可以有多个历史合同,一份合同对应多条账单。这套设计可以直接拿来做建库脚本。
CREATE DATABASE IF NOT EXISTS house_rent DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rent; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(100) NOT NULL COMMENT '登录密码', real_name VARCHAR(50) COMMENT '真实姓名', role TINYINT DEFAULT 1 COMMENT '1-管理员 2-普通员工', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; CREATE TABLE t_house ( id INT PRIMARY KEY AUTO_INCREMENT, house_no VARCHAR(50) NOT NULL UNIQUE COMMENT '房源编号', address VARCHAR(200) NOT NULL COMMENT '详细地址', area DECIMAL(10,2) COMMENT '面积(平米)', house_type VARCHAR(20) COMMENT '户型,如两室一厅', rent_price DECIMAL(10,2) NOT NULL COMMENT '月租金', deposit DECIMAL(10,2) DEFAULT 0 COMMENT '押金', status TINYINT DEFAULT 0 COMMENT '0-空置 1-已出租 2-维修锁定', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源表'; CREATE TABLE t_tenant ( id INT PRIMARY KEY AUTO_INCREMENT, tenant_name VARCHAR(50) NOT NULL COMMENT '租客姓名', id_card VARCHAR(18) COMMENT '身份证号', phone VARCHAR(20) NOT NULL COMMENT '联系电话', remark VARCHAR(200) COMMENT '备注', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租客表'; CREATE TABLE t_contract ( id INT PRIMARY KEY AUTO_INCREMENT, house_id INT NOT NULL, tenant_id INT NOT NULL, start_date DATE NOT NULL COMMENT '合同开始日期', end_date DATE NOT NULL COMMENT '合同结束日期', monthly_rent DECIMAL(10,2) NOT NULL COMMENT '合同月租', deposit DECIMAL(10,2) DEFAULT 0 COMMENT '实收押金', status TINYINT DEFAULT 1 COMMENT '1-履约中 2-已到期 3-已退租', sign_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_house_id (house_id), KEY idx_tenant_id (tenant_id), CONSTRAINT fk_contract_house FOREIGN KEY (house_id) REFERENCES t_house(id), CONSTRAINT fk_contract_tenant FOREIGN KEY (tenant_id) REFERENCES t_tenant(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房屋合同表'; CREATE TABLE t_bill ( id INT PRIMARY KEY AUTO_INCREMENT, contract_id INT NOT NULL, item_name VARCHAR(50) NOT NULL COMMENT '租金/水费/电费/物业费', amount DECIMAL(10,2) NOT NULL COMMENT '金额', status TINYINT DEFAULT 0 COMMENT '0-未缴 1-已缴', due_date DATE COMMENT '缴费截止日期', pay_time DATETIME COMMENT '实缴时间', remark VARCHAR(200), KEY idx_contract_id (contract_id), CONSTRAINT fk_bill_contract FOREIGN KEY (contract_id) REFERENCES t_contract(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='缴费账单表';建表脚本里有几个设计决策值得在答辩或复盘时讲清楚。状态字段全部用 TINYINT,默认值为 0 表示空置或未缴,这样插入数据时可以少写一个字段,也让查询空置房源直接WHERE status=0,统计口径统一。金额字段全部用 DECIMAL(10,2) 而不是 float/double,因为二进制浮点数算钱会有精度问题,押金结算、月度统计都依赖金额精度。合同表和账单表都建了外键约束,因为业务上合同只改状态不删记录,这种场景加外键是安全的,还能防止程序里误删合同后账单变成孤儿数据。
3.2 业务流转:一套房源从空置到退租的状态变化
系统的业务流可以拆成五步:房源录入、租客登记、签订合同、账单缴费、退租结算。每一步对应一组页面和几个 Servlet,但核心是状态的变化。房源从空置到已出租、合同从履约中到已退租、账单从未缴到已缴,三张表的状态字段互相牵制。
-- 空置房源查询,用于新租客看房 SELECT * FROM t_house WHERE status = 0; -- 履约中的合同,带房源地址和租客姓名 SELECT c.*, h.address, t.tenant_name FROM t_contract c JOIN t_house h ON c.house_id = h.id JOIN t_tenant t ON c.tenant_id = t.id WHERE c.status = 1; -- 已过缴费截止日期的未缴账单,用于催缴列表 SELECT b.*, h.address, t.tenant_name FROM t_bill b JOIN t_contract c ON b.contract_id = c.id JOIN t_house h ON c.house_id = h.id JOIN t_tenant t ON c.tenant_id = t.id WHERE b.status = 0 AND b.due_date < CURDATE();这三条查询基本覆盖了出租管理后台最常用的三个页面:找空房、看履约合同、出催缴单。注意这里全部用 JOIN 而不是子查询,因为最终要在列表页展示房源地址和租客姓名,一次 JOIN 把数据带全。如果源码里写的是先查合同列表,再在 Java 里循环查房源、查租客,那性能就是个定时炸弹,数据量上百条后页面会明显变慢。
签订合同那一步比较特殊:插入一条合同记录之后,还要把房源状态从 0 改成 1。这两个动作必须放在同一个数据库事务里,否则合同签完了房源还是空置状态,另一个销售就能把同一套房再租出去。很多课设源码在这里是分开写的,没有事务保护,功能演示时看不出来,但属于明显的设计漏洞,在第 6 章会给出补法。
3.3 退租结算:押金怎么退、账单怎么平
退租是整个系统的业务难点。一笔押金在合同里记录了金额,退租时要先看该合同有没有未缴账单:有欠费就从押金里扣,剩余退还;没欠费就全额退。这个流程在课设里通常简化为统计未缴账单总额,再用合同押金去减。
SELECT IFNULL(SUM(amount), 0) AS unpaid_amount FROM t_bill WHERE contract_id = ? AND status = 0;这段 SQL 里的 IFNULL 一定不能省。没有未缴账单时 SUM 返回的是 NULL,直接用押金去减会把退租金额算成 NULL,页面上就显示一个空值,看起来像是系统出 bug。退租结算的业务动作不止这一个查询,后面还要把合同状态改成已退租、把房源状态改回空置、生成一条押金结算记录,这三个写操作连在一起,是事务改造的天然练习点。
4. 把源码跑起来:从版本搭配到 WAR 包部署的完整步骤
4.1 环境搭配:JDK、Tomcat、MySQL 各用什么版本
如果这套源码是标准的 javax.servlet 体系,版本搭配如下表。MySQL 安装配置教程网上已经很多,这里只说最容易翻车的版本关系。
| 组件 | 推荐版本 | 说明 | | JDK | 1.8 | 兼容性最好,不要为了尝鲜上 JDK 17 | | Tomcat | 8.5 或 9.0 | 不要用 Tomcat 10,原因见第 5 章 | | MySQL | 5.7 或 8.0 | 5.7 默认字符集更省心 | | mysql-connector-java | 5.1.49 或 8.0.x | 与 MySQL 版本对应 |
Tomcat 版本是这个项目最容易踩的坑。Tomcat 10 开始,Servlet 包名从 javax.servlet 改名成 jakarta.servlet,老项目直接导入会全线标红。驱动版本也要对齐:MySQL 5.7 用 5.1.49 驱动最稳,MySQL 8.0 用 8.0.x 驱动,反过来用的话,5.x 驱动连 MySQL 8 会报 SSL 错误或连接直接失败。
4.2 建库、导入表和初始化数据
把第 3 章的建表脚本保存成 init.sql,在命令行用 source 执行,或者打开 Navicat、MySQL Workbench 直接运行整个脚本。如果本地还没有这个库,先登录 MySQL 再导入:
mysql -uroot -p < init.sql执行完顺手验证一下:
SHOW TABLES; SELECT COUNT(*) FROM t_house;没有报错且能看到五张表,说明脚本没问题。如果 init.sql 里出现 SET FOREIGN_KEY_CHECKS=0,说明脚本的建表顺序可能有问题,正常按 t_user、t_house、t_tenant、t_contract、t_bill 的顺序建,不需要关闭外键检查。
4.3 改数据库连接配置
源码里数据库连接信息一般在 src 目录下的 db.properties,或者直接写在 DbUtil.java 里。先检查这几行:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/house_rent?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=把这里改成你的mysql密码参数说明。jdbc.driver 是第一个坑:MySQL 5.x 驱动用 com.mysql.jdbc.Driver,MySQL 8.x 驱动用 com.mysql.cj.jdbc.Driver,写错直接 ClassNotFoundException。serverTimezone 必带,MySQL 8 的驱动要求指定时区,缺了会报 The server time zone value 的异常。useSSL=false 是本地开发环境最省事的配置,MySQL 8 默认开 SSL 校验,不关有时会告警。characterEncoding=utf8 和 JSP 页面编码配套,乱码问题一半出在这里。
如果源码里不是 properties 文件,而是直接在 DbUtil.java 里写 URL,同样改这三行就行。改完后可以先用一个最简单的 Servlet 映射测一次登录,确认配置生效再继续。
4.4 在 IDEA 里配置 Tomcat 启动
打开 IDEA 后先把项目导入,等待 Maven 或 Gradle 同步完成。有 pom.xml 的项目会自动下载依赖;纯手工导 jar 的传统项目,检查 WEB-INF/lib 目录下的 Jar 是否完整。也有人习惯用 VSCode 写 Servlet,配置好 JDK 和扩展后也能编译,但调试、断点、Tomcat 集成都不如 IDEA 顺手,传统 JSP 项目直接用 IDEA 社区版最省事。
然后配置 Tomcat:Run → Edit Configurations → 左上角加号 → Tomcat Server → Local。切到 Deployment 页签,点加号 → Artifact,选择 war exploded 类型的 Artifact,Application context 填 /rent。启动后访问 http://localhost:8080/rent/。
一个好用的检查方式:先把 Tomcat 启动控制台日志打开,看到 “Deployment of web application archive has finished” 再刷新页面,不要凭感觉盲等。不少人不看日志直接开浏览器,页面报 404 就开始怀疑代码,其实只是应用还没部署完。
提示:IDEA 里配置 Tomcat 后如果事件日志报红色错误,先看是哪一层报错,再回到代码里排查,大部分都是 Artifact 没指对。
4.5 传统 JSP 项目打包 WAR:命令行和 IDEA 两种方式
只在自己电脑上跑,IDEA 内置 Tomcat 够用。但课程设计要部署到别的机器或交演示环境,就得打 WAR 包。传统 JSP 项目打包 WAR 有两种方式。
IDEA 方式:File → Project Structure → Artifacts → 点加号 → Web Application: Archive → 选 From module,然后 Build → Build Artifacts → 选 xxx:war → Build,产物在 out/artifacts/xxx_war/xxx.war。命令行方式更直接,在项目编译输出目录上执行:
jar cvf rent.war -C out/artifacts/xxx_war_exploded/ .把生成的 rent.war 放到 Tomcat 的 webapps 目录,启动 Tomcat:
$CATALINA_HOME/bin/startup.sh # Linux $CATALINA_HOME/bin/startup.bat # WindowsTomcat 会自动解压 war 并部署。访问路径是 http://localhost:8080/rent/,路径名就是 war 包名,想换上下文路径直接重命名 war 文件。启动之后先测登录页,再跑一个读操作和一个写操作,确认数据库配置没问题,再开始改代码。
5. 避坑指南:这套 JavaWeb 房屋租赁系统跑不起来时,先查这四个地方
5.1 数据库连不上:驱动类、时区、服务状态三件事一起查
现象:启动 Tomcat 时看到 ClassNotFoundException: com.mysql.jdbc.Driver,或者访问页面时抛 Communications link failure,控制台报 Can't connect to MySQL server on 'localhost' (10061) 这类嵌套异常。
原因按顺序排查:MySQL 服务没启动;连接串里账号密码不对;驱动 jar 没进 WEB-INF/lib;驱动类和 MySQL 版本不匹配;MySQL 8 缺 serverTimezone 配置。Linux 下本地连不上时常见报错是 error 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock',这是客户端找 socket 文件失败,先确认 mysqld 进程在跑,而不是急着改密码。
解决步骤:先确认 MySQL 进程,Windows 下sc query mysql或任务管理器看 mysqld,Linux 下systemctl status mysql;再用命令行客户端试一次mysql -uroot -p,能连上说明服务没问题。然后检查驱动 jar 是否在 WEB-INF/lib 目录下,最后把 4.3 的配置逐项对一遍。注意,如果机器上同时装过 MySQL 5.7 和 8.0,先用mysql --version确认 3306 端口跑的是哪一版,再对驱动和 URL 参数。
5.2 JSP 中文乱码:浏览器、JSP、JDBC、MySQL 四层编码必须一致
现象:页面上的中文全是乱码,或者往数据库插入租客姓名后查出来是 ????。
原因:这套系统全是中文输入,编码链路长:JSP 页面文件本身的编码、请求参数的编码、JDBC URL 里的 characterEncoding、MySQL 库和表的字符集,任何一环不是 UTF-8 就会出问题。最常见的是只改了 JSP 顶部,没改 JDBC URL 和数据库字符集。
解决:JSP 页面顶部统一声明,数据库 URL 补 characterEncoding,建库建表统一 utf8mb4。更正规的做法是给整个项目加一个编码过滤器:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 请求参数统一按 UTF-8 解码 request.setCharacterEncoding("UTF-8"); // 响应统一输出 UTF-8 response.setContentType("text/html;charset=UTF-8"); chain.doFilter(request, response); }这个过滤器配置在 web.xml 里,映射 /*,就能覆盖所有页面和 Servlet,避免每个 Servlet 单独写编码设置。Tomcat 8 之后的 GET 请求一般不会乱码,如果出现,去 server.xml 的 Connector 配置里加 URIEncoding="UTF-8"。
5.3 导入项目一堆红叉:八成是 Tomcat 10 的锅
现象:IDEA 里导入项目后,Servlet、HttpServletRequest、HttpSession 全线标红,或者编译时大量报 Error: package javax.servlet does not exist。
原因:Tomcat 10 开始换成 Jakarta EE 9+,包名从 javax.* 改名 jakarta.*,老 Servlet+JSP 项目基于 javax 命名空间写的,自然找不到类。这个坑特别隐蔽,因为配 Tomcat 时新手通常默认下载新版。
解决:把本地 Tomcat 换成 8.5 或 9.0,IDEA 里重新在 Project Structure → Libraries 指向新 Tomcat 的 servlet-api.jar 和 jsp-api.jar,再重新编译。如果已经为迁到 Tomcat 10 把 import 改成 jakarta 开头,再换回 Tomcat 9 时记得改回来。
提示:判断项目是不是 javax 系,打开任意一个 Servlet,看 import 开头是 javax.servlet 还是 jakarta.servlet。
5.4 系统放着放着就报连接失效:连接池里的连接被 MySQL 掐断了
现象:系统刚启动一切正常,第二天早上第一次访问某个页面时抛 Communications link failure 或 Connection is not available, request timed out。
原因:MySQL 的 wait_timeout 默认 8 小时,空闲超过 8 小时的连接会被服务端关闭,而连接池不知道,继续把死连接发给业务代码。课设环境晚上没人访问,第二天必现,属于典型的演示现场翻车场景。
解决:给连接池加两块配置,以 DBCP 为例:
dataSource.setTestOnBorrow(true); dataSource.setValidationQuery("SELECT 1");testOnBorrow 设为 true,每次从池里取连接先执行一条 SELECT 1 验证连接有效性,无效就丢弃重建。也可以把 MySQL 的 wait_timeout 调大,但连接池侧处理才是治本,C3P0 对应参数是 testConnectionOnCheckout 和 preferredTestQuery。这个排查方法是我自己在这类 JavaWeb 项目里踩得最多的坑,白天演示一切正常,隔夜后第一次点击就抛连接超时,没少被这套组合坑过。
6. 别让源码停在“能跑”:先做这三个小改造
这套源码跑通只是开始。答辩或面试时,能讲出的增量优化比“我下载了一个可以运行的项目”值钱得多。做过这几个改造,再回头看源码,理解会深一截。
第一个改造是密码存储。源码里如果管理员密码以明文存在 t_user 表,赶紧改。改造成本极低:登录和注册处把密码做 MD5,加一点盐再入库。MD5 本身不算强加密,但能挡住绝大多数“顺手查库”的读取。真正要求高的场景可以换成 SHA-256 加盐,对课设来说 MD5 足够讲清楚“为什么不能存明文”。
第二个改造是给退租结算加事务。第 3 章讲过,退租要同时改合同状态、房屋状态、生成结算账单,这三个动作不能被拆成三条独立执行的语句,用一个手动事务包起来:
Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); String updateContract = "UPDATE t_contract SET status = 3, end_date = NOW() WHERE id = ?"; String updateHouse = "UPDATE t_house SET status = 0 WHERE id = ?"; String insertBill = "INSERT INTO t_bill(contract_id, item_name, amount, status) VALUES(?, '押金结算', ?, 0)"; // 依次执行三个语句... conn.commit(); } catch (Exception e) { if (conn != null) conn.rollback(); throw new RuntimeException("退租结算失败", e); } finally { if (conn != null) { conn.setAutoCommit(true); DBUtil.close(conn); } }三个 UPDATE/INSERT 只有全部成功才 commit,中间任何一步报错都 rollback,房源状态和合同状态才不会错位。加上这个事务,答辩时说“我用 Connection 手动事务保证业务原子性”,比说“我用了 Spring 的 @Transactional”更能体现底层理解。
第三个改造是逐步把 JSP 里的 scriptlet 换成 EL + JSTL。操作方式在 2.2 提过,通用替换格式是:原来<% for (House h : list) { %>改成<c:forEach items="${list}" var="h">,原来<%=h.getAddress()%>改成${h.address}。一次改一个文件,先从列表页开始,改完跑一遍回归测试。好处是页面不再依赖具体类,视图和数据彻底分离。
把这三个改造做完,这套房屋租赁管理系统就从“能运行的源码”变成了“你亲手维护过的项目”。我自己的习惯是每个改造留一个 git 提交,这样复盘时能清楚说出每一步改了什么、为什么改。三个改造都不大,但每一个都指向真实的工程问题:安全、事务、可维护性。希望这条路能帮到你。
本文还有配套的精品资源,点击获取