ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaWeb房屋租赁管理系统:servlet+jsp+mysql架构与部署避坑指南

JavaWeb房屋租赁管理系统:servlet+jsp+mysql架构与部署避坑指南 简介一套基于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房屋租赁管理系统源码servletjspmysql”发现大多数结果在卖打包好的项目文件真正把架构、建表和部署流程讲透的很少。这套系统的价值在于用最朴素的servletjspmysql三件套覆盖了登录、房源管理、租赁合同、账单统计这条完整业务链。对正在做JavaWeb课程设计的人或者想给单位快速搭一个内部小后台的人它比Spring Boot那套更轻也更容易在答辩时讲清楚每个类的职责。这篇文章不替任何源码背书只把架构、建表、核心代码和踩坑点拆开让你拿到手能改、能跑、能说清原理。2. 系统架构拆解servletjspmysql三件套如何撑起一个租赁业务2.1 分层思路servlet当控制器jsp当视图mysql当存储接手这类源码时最难受的不是看不懂代码而是不知道请求是怎么串起来的。这套系统的请求链路是经典MVC浏览器发起请求web.xml把URL映射到某个servletservlet在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, rolehouse房源信息id, address, area, rent, statustenant租客信息也可并入userid, name, phone, idcardcontract租赁合同id, house_id, tenant_name, start_date, end_date, rent, depositbill水电/月度账单id, contract_id, amount, status, pay_timepayment押金与交租记录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 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里我把tenant_name和tenant_phone直接用冗余字段放进合同表而不是硬关联到tenant表。原因是租赁合同是凭证签约那一刻的租客信息必须被冻结保存后头租客换手机号合同里保留的仍是签约时的号码这对应数据库设计里的“快照冗余”。房源表也不做物理删除只用status下架避免contract表里的house_id变成悬空引用。created_at字段用DEFAULT CURRENT_TIMESTAMP省去Java端手动设置时间。2.3 为什么选servletjsp而不是spring boot学习成本与部署轻量经常有同学问为什么不用Spring Boot。课题点名“JavaWeb房屋租赁管理系统”用servletjspmysql目标就不是微服务架构而是让你吃透JavaEE最底层的请求流程。servletjsp是JavaWeb的地基市面上几乎所有JavaWeb课程包括不少黑马JavaWeb笔记数据也都是从servlet讲起再过渡到框架。用这个组合做课设部署只需要一个Tomcat不需要Maven拉依赖不需要理解自动配置学习曲线明显平缓。缺点也很明显jsp页面和Java逻辑容易耦合前端模板能力弱做复杂交互很吃力。但对租赁管理这类后台系统表单、列表、统计页面已经够用。我建议的取舍是如果只做课设或内部工具坚持servletjsp把时间花在业务完整性和数据库设计上如果要做商业软件再考虑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] basedirD:/mysql-5.7.44-winx64 datadirD:/mysql-5.7.44-winx64/data port3306 character-set-serverutf8mb4 default-storage-engineInnoDB 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 rootlocalhost IDENTIFIED BY 123456; FLUSH PRIVILEGES;skip-name-resolve的目的是让连接不再做反向DNS解析速度更快代价是权限表里的host必须写IP或localhost。如果连接时提示Access denied for user rootlocalhost多半是host写错了。现在很多人喜欢用docker安装mysql失败率不低常见失败点是端口映射和字符集单机学习不建议折腾docker直接Windows本机装更省事。mysql 8.4.11 LTS也发布了但为了兼容这套servletjsp源码里的老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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;执行导入命令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但很多servletjsp源码用了Druid带监控更好用。配置druid.propertiesdriverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/house_rental?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20 maxWait60000然后在项目里用一个JdbcUtils类加载配置。注意JDBC驱动mysql 5.7用com.mysql.jdbc.Drivermysql 8.0改成com.mysql.cj.jdbc.Driver混用会报ClassNotFoundException。更关键的是连接池的管理方式用ThreadLocal保存当前线程的Connection让Service层在同一个事务里使用同一个连接避免各DAO拿到不同连接导致事务失控。具体写法在第4章新增合同时展开。4. 用servletjsp实现核心业务从登录到租房订单的完整链路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指令里禁用了isELIgnoredtrue那就要去掉这一项。4.2 房源列表与分页jspservlet怎么传参不乱码租赁管理里房源列表是高频页面分页不能只把全部数据塞进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(); ListHouse 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如果用户传pageabc会抛NumberFormatException。实战中我会加兜底解析失败就重置为1。请求参数的中文乱码有三个地方要同时做jsp页面用% page contentTypetext/html;charsetUTF-8 %servlet开头request.setCharacterEncoding(UTF-8)以及tomcat的server.xml里给Connector加URIEncodingUTF-8。少任何一个地址栏输入中文搜房源都可能变成问号。分页链接不要用out.println直接拼写在jsp的HTML片段里更好维护。如果不想引入JSTL可以用三元表达式但可读性差。下面是最简单的分页条div classpagination 共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${currentPage1}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项目配置后一直404IDEA里启动tomcat后页面404原因通常是两个。一是Artifact没部署到tomcat的Deployment里项目的war exploded没有添加。二是在IDEA的Run Configuration的Deployment标签页里添加Artifact时Application context没填对。比如你填了/house_rental_war_exploded访问路径就必须带这个前缀。解决方法是先确认Deployment里有ArtifactApplication context填/或项目名然后在浏览器按实际上下文访问。如果能看到tomcat欢迎页但项目资源没加载检查Facet里web.xml位置和web资源目录是否配置正确。直接把war拷进tomcat的webapps目录时要注意目录名是否符合预期比如house_rental.war解压后目录名是house_rentalURL就要带/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服务已启动。连接串里useSSLfalse不能少JDBC 8.0以上驱动如果没加serverTimezoneAsia/Shanghai会直接报时区错误。5.7数据库用com.mysql.jdbc.Driver8.0数据库用com.mysql.cj.jdbc.Driver两者不能混用。另外mysql 8.0默认认证插件导致老驱动连不上解决办法是装5.7或升级驱动依赖。5.3 现象jsp页面中文乱码中文字符显示成???或好原因有三个页面编码不是UTF-8、servlet没设置request编码、数据库连接串字符集不对。解决路径依次检查jsp第一行必须有% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%。servlet的doGet/doPost第一行加request.setCharacterEncoding(UTF-8)。mysql的my.ini里character-set-serverutf8mb4。表结构也要用utf8mb4。执行SHOW VARIABLES LIKE character%确认客户端、连接、服务器三个都是utf8。如果从数据库读出来的中文变成问号大概率是表字段字符集不对用ALTER TABLE house CONVERT TO CHARACTER SET utf8mb4修复。get请求参数乱码需要在tomcat的server.xml里给Connector设置URIEncodingUTF-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是否回到0bill表是否生成退款记录。我曾经因为漏掉“合同到期自动改状态”被质疑过后来在定时任务里补了一个UPDATE contract SET status0 WHERE end_date CURDATE() AND status1这才把完整闭环补上。这套系统做好后最好再补一个简单的操作日志表记录谁在什么时候改了什么房源不然有人误操作后查不到责任。我的习惯是在Service层统一写日志而不是在各个servlet里加这样能防止日志漏记。希望这个方向能帮到你也祝你的JavaWeb项目一次跑通。本文还有配套的精品资源点击获取
返回列表