news 2026/9/28 1:56:35

Servlet+JSP+JDBC房屋租赁管理系统开发实战:从设计到部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Servlet+JSP+JDBC房屋租赁管理系统开发实战:从设计到部署

简介:这是一套基于Servlet+Jsp+JDBC的房屋租赁管理系统完整毕业设计资料包,面向计算机相关专业正在准备毕设的学生,以及需要JavaWeb实战练习的开发者。系统功能覆盖前台会员登录、免费注册、忘记密码、信息检索、留言板和公告浏览,以及后台出租求租、出售求购、交易记录、统计报表和房屋租金报表等模块,技术栈涉及JavaWeb、Servlet、JSP、jQuery、CSS,运行于JDK8,支持IDEA或Eclipse配合Navicat连接SQL Server数据库。包体包含项目源码、数据库脚本、毕业论文、系统详细配置指导以及PPT,压缩包约13.69MB。其中源码经调试可导入开发工具直接运行,数据库脚本用于初始化房屋与交易数据,配置指导帮助快速搭建环境,论文与PPT便于完成毕业设计文档与答辩展示,整体结构清晰,可直接作为毕业设计基础或项目实战练手。目前已有250人学习/下载,对于需要一套可运行、易扩展的房屋租赁管理系统参考方案的读者而言,具有很高的实用价值。

1. 基于 Servlet+JSP+JDBC 的房屋租赁管理系统:这套老技术栈为什么还有人在做

房屋租赁管理系统是 JavaWeb 方向最常见的毕业设计题之一,也是新手能真正跑通“浏览器 → Servlet → JDBC → MySQL 数据库”全链路的最小完整项目。这套系统要管的不只是“把房源信息存进数据库”,而是围绕「房东、租客、房屋、合同」四个核心对象,完成信息发布、租赁登记、合同生成和统计查询。对正在选毕设题或者刚学完 JavaWeb 的读者来说,它最大的价值在于:不需要接触 Spring 全家桶,只要弄懂 HTTP 请求、Servlet 生命周期、JSP 标签、JDBC 增删改查这四件事,就能把系统完整落地。本文按一个能交付验收的标准来讲这套系统的选型理由、数据库设计、核心代码写法、环境配置和上线打包的完整路径。

2. 为什么还在用 Servlet+JSP+JDBC:技术选型与开发环境搭建

2.1 这套技术栈的真实边界在哪里

很多人一听到 Servlet+JSP 就觉得过时,但实际做这套租赁系统,它反而是最能讲清楚原理的选择。Spring Boot 能帮你省掉配置,但也会把请求怎么进 Servlet、参数怎么绑定、连接怎么管理这些关键环节全部封装成黑匣子。毕业设计答辩时老师问“登录状态怎么保持的”“查询条件怎么拼接的”,如果你从 Spring Security 和 MyBatis-Plus 写起,大概率只能背术语;用 Servlet+JSP+JDBC,你自己写的每一行代码都能成为答辩素材。

这套技术栈的边界也很明确:适合并发量不大、业务逻辑清晰、表数量在十张以内的管理系统。房屋租赁系统恰好符合——没有高并发秒杀,没有复杂支付,没有分布式需求。最重的操作也就是房东发布房源、租客检索房源、生成租赁合同、管理员统计这几个动作。如果未来要扩展,业务层可以换成 Spring,但 Servlet 层和数据库层的设计仍然可以复用。

2.2 开发环境版本搭配与下载清单

做这套系统前,先把版本对齐,否则后面排查环境问题的时间比写代码还长。我建议的版本组合是:

组件推荐版本说明
JDK1.8最稳妥,兼容 Tomcat 8.5 和 9.x
Tomcat8.5.x 或 9.0.x支持 Servlet 3.1/4.0
MySQL5.7 或 8.05.7 对新手友好,8.0 需要配置驱动参数
开发工具IDEA 或 EclipseIDEA 社区版足够
MySQL 驱动mysql-connector-java 5.1.49(对应 MySQL 5.7)/ 8.0.x(对应 MySQL 8.0)经典坑:驱动版本与数据库版本不匹配

常见做法是直接用 IDEA 新建一个普通的 Java Web 工程,而不是 Maven 工程。原因很实际:Maven 会自动联网下载依赖,如果网络不稳或者私服配置有问题,经常出现 IDEA 自动下载 Maven 依赖失败。传统方式只要手动把 mysql-connector-java 的 jar 包拷进 WEB-INF/lib,就不会有这类问题。

提示:开发环境不是越新越好。JavaWeb 老项目用 JDK 8 + Tomcat 8.5 + MySQL 5.7 是能少踩很多坑的组合,尤其是做毕业设计时间紧张的同学,别在环境上折腾太久。

2.3 创建工程的标准目录结构

一个规范的 JavaWeb 工程应该这样组织,后面扩展时不会乱:

house-rental/ ├── src/ │ ├── com/house/rental/ │ │ ├── servlet/ // 控制层:LoginServlet、HouseServlet... │ │ ├── service/ // 业务层:HouseService、ContractService │ │ ├── dao/ // 数据访问层:HouseDao、UserDao │ │ ├── entity/ // 实体类:House、User、Contract │ │ └── util/ // 工具类:DBUtil、StringUtil │ └── com/house/rental/filter/ // 编码、登录拦截过滤器 ├── web/ │ ├── static/ // CSS、JS、图片 │ ├── jsp/ // house-list.jsp、house-add.jsp... │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ // mysql-connector-java.jar 等 └── sql/ └── house_rental.sql // 建库建表脚本

这个分层方式对应 MVC 思想:Servlet 接收请求并控制页面跳转,Service 写业务规则(比如发布房源前校验房东身份),DAO 用 JDBC 操作数据库,JSP 只负责展示。把分层做好,后面答辩时“为什么这么设计”这一问就稳了。

3. 核心功能实现:从用户登录到房屋检索与合同生成

3.1 登录功能与 Session 状态管理:servlet 生命周期的一次完整演示

登录功能是所有管理系统的入口。这里你会发现 servlet 生命周期不是纯理论——用户点“登录”按钮之后,一个请求先经过 Tomcat,Tomcat 判断没有对应实例就调用构造器和 init(),然后执行 service() 分发到 doPost(),你在这里验证用户名密码,最后用完进入 service() 返回响应。整个流程走一遍比背十遍“init—service—destroy”都管用。

登录逻辑代码示例:

@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"); // 调用 DAO 层验证用户,注意密码比对要在 Service 层做 MD5 加密后比对 User user = userDao.findByUsernameAndPassword(username, MD5Util.md5(password)); if (user == null) { request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("/jsp/login.jsp").forward(request, response); return; } // 登录成功,将用户信息存入 Session,供 JSP 页面用 HttpSession session = request.getSession(); session.setAttribute("loginUser", user); // 重定向到首页而不是转发,避免刷新页面重复提交表单 response.sendRedirect(request.getContextPath() + "/house/list"); } }

这段代码里有三个细节值得记住。第一,密码不能明文比对,Service 层里统一用 MD5(再加盐更好)处理后再查库。第二,request.getParameter 取到的全是字符串,涉及金额、日期时要注意类型转换。第三,登录成功后用 sendRedirect 而不是 forward——因为重定向是让浏览器重新发起一次 GET 请求到 /house/list,地址栏变化了,F5 刷新时不会把登录表单重新提交一遍,这是新手很容易踩的“刷新页面导致重复登录/重复下单”坑。

3.2 房屋发布与多表关联查询:JDBC 增删改查的完整业务载体

房屋发布是租赁系统的核心动作。房源上架时要同时写入房屋基本信息和房东信息,状态还要设置为“待审核”。这一步背后是 service 层事务控制——房屋表和房东表要同时成功,要么都失败。来看一个带事务的 DAO 写法:

public boolean addHouseAndLandlord(House house, Landlord landlord) { Connection conn = null; PreparedStatement pstmtHouse = null; PreparedStatement pstmtLandlord = null; try { conn = DBUtil.getConnection(); // 手动开启事务,JDBC 默认自动提交,多条 SQL 必须关掉这个默认值 conn.setAutoCommit(false); String sqlLandlord = "INSERT INTO landlord(name, phone, id_card) VALUES(?, ?, ?)"; pstmtLandlord = conn.prepareStatement(sqlLandlord, Statement.RETURN_GENERATED_KEYS); pstmtLandlord.setString(1, landlord.getName()); pstmtLandlord.setString(2, landlord.getPhone()); pstmtLandlord.setString(3, landlord.getIdCard()); pstmtLandlord.executeUpdate(); // 取出自增主键作为房屋的外键 landlord_id ResultSet rs = pstmtLandlord.getGeneratedKeys(); int landlordId = 0; if (rs.next()) { landlordId = rs.getInt(1); } rs.close(); String sqlHouse = "INSERT INTO house(address, area, price, landlord_id, status) VALUES(?, ?, ?, ?, 'PENDING')"; pstmtHouse = conn.prepareStatement(sqlHouse); pstmtHouse.setString(1, house.getAddress()); pstmtHouse.setBigDecimal(2, house.getArea()); pstmtHouse.setBigDecimal(3, house.getPrice()); pstmtHouse.setInt(4, landlordId); pstmtHouse.executeUpdate(); conn.commit(); // 两条 SQL 都成功才提交 return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); // 任何一条出错回滚全部 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { // 资源释放顺序:ResultSet → Statement → Connection // Connection 如果是连接池的,这里不是真正关闭,而是归还给池子 DBUtil.close(pstmtHouse, pstmtLandlord, conn); } }

这段代码的要点在于:一,setAutoCommit(false) 是事务开始标志,很多人漏掉这一行,导致第一条 SQL 执行完自动提交,第二条失败时无法回滚;二,用 getGeneratedKeys 回填自增主键,避免查两次数据库;三,finally 块里资源释放顺序不能乱,如果先把 Connection 释放了再关 Statement,连接池中的连接会残留未关闭的语句对象,数据库连接池一段时间后就被“占满”。

多表关联查询是租赁系统检索功能的基础。前台检索“某区域可租房源+房东联系方式”时,一条 SQL 就能带出全部信息:

SELECT h.id, h.address, h.area, h.price, h.status, l.name AS landlord_name, l.phone AS landlord_phone FROM house h INNER JOIN landlord l ON h.landlord_id = l.id WHERE h.status = 'AVAILABLE' AND h.address LIKE CONCAT('%', ?, '%') AND h.price BETWEEN ? AND ? ORDER BY h.create_time DESC LIMIT ?, ?;

这条 SQL 里的 LIMIT 是分页的实现核心。传入三个参数:关键词、价格下限、价格上限。JDBC 层用 PreparedStatement 的 setString/setBigDecimal 填充这三个问号,注意 LIKE 语句在 PreparedStatement 里不能用 %?% 这样写,必须用 CONCAT('%', ?, '%'),这是很多新手直接拼字符串后碰到的第一个 SQL 注入漏洞点。

3.3 JSP 页面与分页组件:让租客能按条件筛选房源

JSP 页面负责展示和收集数据。一个合格的房源列表页要完成三件事:展示分页数据、保留查询条件、管理按钮按角色显示。用 JSTL 拼接 HTML 是传统 JSP 项目打包 war 后最常用的做法。

<%-- house-list.jsp 核心部分 --%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <div class="filter-bar"> <form action="${pageContext.request.contextPath}/house/list" method="get"> <input type="text" name="keyword" value="${param.keyword}" placeholder="输入区域关键字"/> <input type="number" name="minPrice" value="${param.minPrice}" placeholder="最低价"/> <input type="number" name="maxPrice" value="${param.maxPrice}" placeholder="最高价"/> <button type="submit">搜索</button> </form> </div> <table class="house-table"> <tr> <th>地址</th> <th>面积</th> <th>月租</th> <th>状态</th> <th>房东</th> <th>操作</th> </tr> <c:forEach items="${page.list}" var="house"> <tr> <td>${house.address}</td> <td>${house.area}㎡</td> <td><fmt:formatNumber value="${house.price}" pattern="¥#,##0.0"/></td> <td> <c:choose> <c:when test="${house.status == 'AVAILABLE'}">可租</c:when> <c:when test="${house.status == 'RENTED'}">已租</c:when> <c:otherwise>待审核</c:otherwise> </c:choose> </td> <td>${house.landlordName} / ${house.landlordPhone}</td> <td> <c:if test="${sessionScope.loginUser.role == 'ADMIN'}"> <a href="house/edit?id=${house.id}">编辑</a> <a href="house/delete?id=${house.id}" onclick="return confirm('确定删除该房源?')">下架</a> </c:if> <a href="contract/create?houseId=${house.id}">签约</a> </td> </tr> </c:forEach> </table> <%-- 分页组件 --%> <div class="pagination"> <c:if test="${page.currentPage > 1}"> <a href="house/list?page=${page.currentPage - 1}&keyword=${param.keyword}">上一页</a> </c:if> <span>第 ${page.currentPage} / ${page.totalPages} 页</span> <c:if test="${page.currentPage < page.totalPages}"> <a href="house/list?page=${page.currentPage + 1}&keyword=${param.keyword}">下一页</a> </c:if> </div>

分页参数是这套系统的必调参数。页码 page 从前端传入后必须做边界校验——小于 1 就置为 1,大于总页数就置为总页数,否则用户手动改 URL 参数 ?page=999 会得到一个空白页甚至 SQL 异常。每页条数我一般设为 10,如果房源数据量大到上千条,要靠索引和延迟加载配合,而不是单纯调大每页条数。JSP 里用 param.keyword 回显搜索条件才能保证翻页时条件不丢,这个细节做不好就会让用户点第二页时搜索条件重置。

4. 数据库设计与 JDBC 优化:表结构、连接池与多数据源注意点

4.1 五张核心表的字段设计与外键约束

房屋租赁系统的数据模型要围绕“租赁合同”这个核心业务来设计。最少需要五张表,表结构直接决定 Servlet 和 DAO 的编写量。

表名关键字段说明
userid, username, password, role系统登录用户,role 区分 ADMIN/AGENT
landlordid, name, phone, id_card房东信息,一个房东可有多套房
houseid, address, area, price, status, landlord_id, create_time核心房源表,status 用枚举字符串
tenantid, name, phone, id_card, create_time租客信息,签约前先建档
contractid, house_id, tenant_id, landlord_id, start_date, end_date, monthly_rent, deposit, status租赁合同表,外键最多

设计时要注意三个点。第一,金额字段用 DECIMAL(10,2) 而不是 FLOAT,避免租金计算出现 0.0000001 的精度问题。第二,外键约束要建,但要分清楚逻辑外键和物理外键——正式项目里所有关联都建物理外键会影响性能和迁移,毕设项目建物理外键反而能在答辩时展示你懂数据完整性。第三,status 字段用字符串 VARCHAR 加 CHECK 约束,比如 'AVAILABLE'、'RENTED'、'PENDING',不要用 0/1 数字,可读性差而且扩展第三种状态时不直观。

初始化建库脚本这样写:

CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE house_rental; CREATE TABLE landlord ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, id_card VARCHAR(18), create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE house ( id INT AUTO_INCREMENT PRIMARY KEY, address VARCHAR(200) NOT NULL, area DECIMAL(6,2) NOT NULL, price DECIMAL(10,2) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'PENDING', landlord_id INT NOT NULL, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_house_landlord FOREIGN KEY (landlord_id) REFERENCES landlord(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- tenant、contract 表结构按相同规范继续加

这里 ENGINE=InnoDB 必须写清楚。InnoDB 支持外键和事务,而 MyISAM 不支持——如果建表时没写,MySQL 5.5 之前默认是 MyISAM,事务代码就废了。utf8mb4 用四字节存储,租房合同里如果出现生僻字或 emoji 才会正常显示,这是 jdbc 连接项里的 characterEncoding 写入 SQL 脚本时最容易出现的字符集口径不一致问题。

4.2 数据库连接池参数与 JDBC API 封装

传统 JDBC 的 Connection 每次创建和销毁开销很大。在 Tomcat 里用连接池只需要配置 context.xml,不需要额外引入 jar 包。下面是一套适配 MySQL 8.0 的最小 Database 配置:

<?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/houseRentalDB" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/house_rental?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&characterEncoding=UTF-8" username="root" password="your_password" maxTotal="20" maxIdle="10" minIdle="5" maxWaitMillis="10000" initialSize="5" validationQuery="SELECT 1" /> </Context>

关键参数里,maxTotal=20 是最大连接数,超过后请求会排队,如果线上并发超过这个数就要关注慢 SQL,不能盲目加大。maxWaitMillis=10000 是拿到连接的等待超时,超过 10 秒抛异常,配太短高峰期会被误杀,配太长四五个慢查询就能占满线程。validationQuery 配置成 SELECT 1,作用是每次从池里取出连接时验证可用性,防止 MySQL 默认的 8 小时超时断开导致拿到的 Connection 变成“死连接”。

配套的工具类封装核心是“把连接归还池子而不是关闭”这个语义。DBUtil 的 close 方法要注意这个细节:

public class DBUtil { private static DataSource dataSource; static { try { Context ctx = new InitialContext(); dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/houseRentalDB"); } catch (NamingException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { // 从连接池借出连接,不是 new 一个新连接 return dataSource.getConnection(); } public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

解释一下为什么 close 方法里 Connection 的关闭是安全的归还:连接池返回的 Connection 是代理对象,它的 close() 不是真的断开 TCP,而是把这条连接标记为空闲并放回池中。新手最容易犯的错是在 Service 层里把 conn 暴露给两个 DAO 方法顺序使用,第一个方法调用 close 后第二个方法操作一个已经“归还”的连接,就会报 Connection is closed。

4.3 多数据源场景下的“数据库同步”问题

一个系统同时连接多个库(比如日志库和业务库分离),或者用数据库同步工具做主从读写分离,尤其是毕设答辩时老师极可能追问的一套接口。这里只提跟 jdbc 最相关的一点:多数据源时一个 Connection 对应一个库,不要让 DAO 自己决定用哪条数据源——事务应该控制在 Service 方法内部。具体做法是在 Service 里传入数据源名称参数,DAO 方法签名必须接受 Connection 作为参数,而不是内部隐藏数据源切换,这样同一个事务方法里两条 SQL 才能保证用的是同一个 Connection,主从切换不会造成“空事务”。

5. 常见问题排查:驱动加载失败、中文乱码、事务与连接池踩坑记录

5.1 现象:Class.forName("com.mysql.jdbc.Driver") 报 ClassNotFoundException

这是用 MySQL 8.0 数据库但驱动 jar 还停留在 5.x 时的经典报错。MySQL 8.0 的驱动类名变成了 com.mysql.cj.jdbc.Driver,旧名称不再生效。另一种情况是 jar 包放在了 /lib 目录而不是 WEB-INF/lib——Tomcat 的类加载器只认自己环境下的 lib 目录。解决方法是去 mysql-connector-java 8.0.x 版本里找 mysql-connector-java-8.0.26.jar 这类文件,拷进 WEB-INF/lib 并重新构建 Artifact。如果用的 IDEA,还要记得在项目结构里把 jar 包“添加到库”,否则编译能过、运行时报错。

5.2 现象:页面中文全部显示为“???”

字符集有三道关口:JSP 文件本身的编码、Servlet 中 request 与 response 的编码、MySQL 表结构和 JDBC URL 的编码。四者只要有一个不一致,中文就乱。我现在的习惯是建立文件时全部选 UTF-8,web.xml 里再加一个强制过滤器,不给漏网之鱼。

<!-- web.xml 中配置字符集过滤器,位置要在所有 Servlet 之前 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>com.house.rental.filter.EncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

对应的 Filter 类实现里必须同时设置 request 和 response 的编码。很多教程只写了 request.setCharacterEncoding,结果从数据库读出来中文正常,但 through response 写回浏览器的 JSON 或页面文字乱码。另外 GET 请求的 query string 编码是 Tomcat 在 URI 层面处理的,要在 Tomcat 的 server.xml 里给 Connector 加 URIEncoding="UTF-8" 才能真正解决 URL 参数乱码,很多人改了 filter 没用就是因为漏了这一步。

5.3 现象:更新或删除房源后数据没有变化

这类“修改不生效”的问题十有八九是事务没提交。排查顺序三条:第一,确认代码里有没有 conn.setAutoCommit(false) 和 conn.commit() 成对出现;第二,看是否在方法中途 try-catch 吞掉了 SQLException,一旦 catch 住没往外抛,rollback 不会执行事务也不会提交;第三,如果是连接池环境,确认 close 方法是不是放在 finally 里,如果提前 close,事务被自动回滚。

一个容易被忽略的原因:PreparedStatement 复用了同一个 Connection 但没用同一个 transaction 边界。比如一个 Service 方法调用了两个 DAO 方法,每个 DAO 方法内部都调用了 DBUtil.getConnection(),这实际上是两条连接,事务失效。要解决必须把 Connection 从 Service 层传入 DAO 方法内部,所有操作共用一段事务。

5.4 现象:IDEA 自动下载 Maven 依赖失败(plugin 报 download from maven failed)

如果用了 Maven 管理依赖,这是最常见的“环境怪现象”。原因是中央仓库地址网络慢或证书问题。解决方法是换阿里云镜像,在 settings.xml 里加镜像,或者在项目 pom.xml 里拉下来的依赖缺少时先手动安装了 .m2 缓存。但我的习惯是毕设项目不用 Maven,手动管理 jar。将 Tomcat 的 lib 目录下自带的一些 jar(比如 servlet-api.jar、jsp-api.jar)复制到项目的 WEB-INF/lib,再在 IDE 里“添加为库”,编译运行时完全不用去网上下东西,这是最不受环境影响的做法。

5.5 现象:intellij idea 里用 SQL Server 数据源的 JDBC 自动下载同样报错

换了一个库就报相同的 Failure 现象,根本原因是 Maven 布鲁克坐标解析出意外版本导致下载失败时 IDEA 会多次自动重试,网络不给力就卡死。我们的项目是 MySQL,如果在写作业时交叉用了 SQLServer,正确做法是手动下载对应版本的 mssql-jdbc 包,通过项目结构导入而非让 IDE 网络解析。不管什么数据库,jdbc targetServertype 这类新驱动参数在旧项目里根本不存在,不要照抄别人 Oracle 的配置——不同数据库驱动 URL 写法完全不同,这属于“看帖不认版本”造成的翻车。

6. 打包上线与维护:从 War 部署到性能参数的进阶技巧

6.1 传统 JSP 项目打包 War 并部署到 Tomcat

开发完成后,最常见的交付方式是打包成 War 文件直接丢到 Tomcat 的 webapps 目录。步骤分四步:

第一步,在 IDEA 里选择 Build → Build Artifacts → 选择你的项目 Artifact(如果没配置,在 Project Structure → Artifacts 里新建一个 Web Application Exploded,再点 Fix 把所有依赖 jar 打包进去)。第二步,点击 Build Artifact 后选择 Build,生成的 War 文件在 out/artifacts/ 目录下。第三步,把这个 War 拷到 Tomcat 的 webapps/ 下,启动 Tomcat 后它会自动解压部署。第四步,浏览器访问 http://localhost:8080/项目名/jsp/login.jsp。

部署前的三件检查事项:第一,确认 lib 下的 mysql 驱动 jar 没有和 Tomcat 自带的 servlet-api.jar 冲突——把 servlet-api 从 lib 中删掉只留 Tomcat 的;第二,检查 context.xml 里的数据库密码是不是明文且写对的;第三,确认 web.xml 中欢迎页列表配置了 login.jsp。

War 包名尽量用短英文,不要带版本号和中文。如果 Tomcat 启动后页面 404,在 logs 目录下的 catalina.out 里搜索 Exception,大多数情况要么是连接数据库失败,要么是 jar 冲突。

6.2 上线后的三个必调参数

阶段目标是把“能跑起来的项目”变成“能稳定跑的项目”,三个关键调整项值得先做。

第一,JVM 内存参数。Tomcat 的 bin/setenv.sh(Windows 是 setenv.bat)里添加 CATALINA_OPTS="-Xms256m -Xmx102m",Xms 等于初始堆大小,Xmx 是最大堆,毕设系统 256m 起步。

第二,数据库连接池连接数上限。maxTotal 默认是 8,对房屋租赁这种并发不高的系统,20 就够了;调大意味着数据库要预留相应连接数,MySQL 默认 max_connections 151,别盲目调大。

第三,定期清理临时文件。Tomcat 的 webapps 下每次启动都会创建临时缓存目录,长期运行会产生大量 session 文件和日志,用 crontab 定期把 work/Catalina 目录下超过 7 天的缓存文件做清理。

6.3 从“能跑”到“可用”的验证思路与维护习惯

完成基本功能后,验证系统是否达到交付标准,可以从用户体验和代码健康度两个维度自检:

验证维度检查项预期结果
功能完整性房东发布→管理员审核→租客签约→合同到期退租全链路数据一致
数据正确性合同结束日期等于退租日期,押金冻结状态正常释放字段更新正确
安全基础未登录直接访问 /house/list 会被拦截到登录页重定向生效
并发基础5 个用户同时浏览房源页面不卡,连接池不爆

安全的拦截器很少有人写。写一个 LoginFilter,对所有 /jsp/ 下的页面和 /house/、/contract/这些 servlet 路径做校验,session 里没有 loginUser 就重定向回 login.jsp。这一步代码量很少但答辩加分极大,因为业务系统最忌讳未登录操作后台接口。

维护层面我的习惯是每次改数据库结构都在 sql/ 目录下留一份增量脚本,命名为 db_2024_v2.sql,不直接改原表。租房合同这类核心数据每月导出一次备份 SQL,放在本地。这样即使 Tomcat 崩溃重装,半小时内能恢复全部数据。

整套系统里最值得反复看的不是彩色页面,而是 Servlet 中每个 try-catch 的 rollback 和 finally 的资源释放代码。我把“事务要么全成功要么全失败、连接只借不永不关闭”这两条规则刻进了自己的编码习惯里。这个道理在 Spring 的 @Transactional 里也适用,只是框架帮你隐藏了细节。希望帮到你,下次处理这类管理系统时,先把数据库表结构和连接池参数定下来,再动手写第一行 Servlet 代码,你会发现整个开发流程顺很多。

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

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

BMS中AFE同步采样与分片上报的工程本质

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:53:21

CODESYS安装与汉化新手指南:适配国产PLC的工业自动化入门路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:53:01

高速避障无人机配置指南:Livox MID360与FAST-LIO2实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:52:25

Windows下RP2040 PICO SDK开发环境搭建与VSCode配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:52:09

Altium Designer导出真实尺寸PDF的三大可靠方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:51:32

ARXML文件操作全指南:从导入到删除报文,搞定CANoe数据库管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华