☰
Servlet+JSP酒店客房预定系统:架构拆解与部署避坑指南
2026/10/4 14:12:03 网站建设 项目流程

简介:这是一套基于Servlet+Jsp实现的酒店客房预定管理系统,分设前台用户与后台管理员两个操作端,主要面向正在准备毕业设计的计算机相关专业学生,以及需要Servlet/JSP项目实战的Java学习者。资源包内含170个文件,主体为41个Java源文件、34个JSP页面、数据库SQL脚本,以及Tomcat运行所需的jar包和部分图片素材,压缩后整体约1.21MB。项目已通过严格调试,可直接导入开发工具运行,并提供用户注册登录、客房搜索、在线预约、留言反馈、预定记录查询,以及后台客房分类管理、会员管理、剩余房间与订房信息统计等完整功能模块。包内目录结构清晰,源码与数据库文件配套齐全,便于按需调整表结构和页面样式,适合作为毕业设计演示、课程项目或入门Web开发的进阶练习素材。目前已有340人学习下载,具有较好的参考价值。

1. 一套能直接跑的Servlet+Jsp酒店客房预定系统:先搞清楚它值不值得你花时间

做JavaWeb毕设或课设的人,最怕的不是不会写代码,而是拿到一套源码却跑不起来,或者跑起来了但不知道每个类在干什么。这套基于Servlet+Jsp实现的酒店客房预定管理系统,是典型的分前后台结构:用户端负责注册、登录、搜房、预约、留言,管理端负责客房维护、分类管理、会员管理、订房查询。它用到的技术栈非常干净——Servlet、Jsp、Tomcat7、MySQL5.0,没有Spring全家桶的复杂配置,也没有Maven依赖地狱,非常适合用来理解JavaWeb的原生请求-响应链路。

如果你正在做酒店管理类毕设,或者想练手Servlet+Jsp的原生开发,这套源码值得下载。它包含项目源码和数据库脚本,导入数据库、部署到Tomcat就能跑通全流程。下面我按「架构怎么拆、前台怎么跑、后台怎么管、坑在哪、怎么扩展」的顺序,把这套系统的关键代码和部署细节拆开讲。

2. 拆开看架构:Servlet+Jsp的前后台分离、MVC分层与数据表设计

2.1 别把Servlet+Jsp的MVC想复杂了:请求进来之后发生了什么

很多新手第一次看Servlet项目,容易被一堆类名搞晕。其实这套系统的套路非常固定:JSP负责展示,Servlet负责接收请求、调用数据库、再把结果交给下一个JSP。你可以把Servlet理解为「中转站」——它不直接输出HTML给你看,而是通过request.setAttribute()把数据塞进请求里,然后forward到JSP页面去渲染。

以客房列表为例,前端页面发起请求后,ListServlet接管了这个请求,查询数据库里的客房数据,然后转发给room_list.jsp。这个过程就是最朴素的MVC:JSP是View,Servlet是Controller,数据库操作类(通常封装成DAO)是Model。这套源码里的ListServlet、AddHotelServlet、ShoppingServlet、DelServlet都是这个模式下的Controller。

这里要给刚入门的朋友一个建议:不要一上来就追求Spring MVC那套注解开发。先手写一遍doGet/doPost,搞清楚request和response的生命周期,理解了转发(forward)和重定向(redirect)的区别,再去看框架就轻松得多。这套源码正好提供了这样一个完整的原生Servlet练习样本。

2.2 数据库设计解读:五张核心表如何支撑整个订房链路

拿到项目后先别急着跑,打开数据库脚本,把表结构看一遍。这套系统的表设计不复杂,但表之间的关联关系就是整个业务的核心。我把关键表整理成一张对照表,方便你理解每个表在业务里的位置:

数据表核心字段对应业务模块关联关系
用户表用户ID、用户名、密码、手机号用户注册/登录、会员管理被预约记录表引用
客房分类表分类ID、分类名称、床型、价格基准客房分类管理客房表引用分类ID
客房表客房ID、房号、分类ID、状态、剩余房间数客房管理/搜索/预约关联分类表
预约订单表订单ID、用户ID、客房ID、入住日期、状态订房/预定记录查询关联用户表和客房表
留言表留言ID、用户ID、内容、回复、时间在线留言/留言管理关联用户表

实际建表时,用户表和预约表之间会做外键约束,客房表和分类表之间也需要约束。如果你拿到的SQL脚本里没加外键,建议自己补上,不然删除分类时可能留下孤儿数据。

提示:导入数据库时注意字符集,建议统一使用utf8mb4,否则中文留言和客房名称可能出现乱码。

2.3 JDBC连接与数据库工具类:所有Servlet的数据入口

这套系统没有用连接池,而是直接用JDBC连接MySQL。每个Servlet里一般会调用一个公共的数据库工具类,比如DBUtil或BaseDao。这个类的核心作用就两件事:拿连接、释放资源。下面的代码是常见写法:

public class DBUtil { private static final String DRIVER = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/hotel_db?useUnicode=true&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs != null) rs.close(); if (ps != null) ps.close(); if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

这段代码里的几个参数需要你重点关注:URL里的hotel_db是你导入SQL脚本后建的数据库名,如果你的脚本里是别的名字(比如hotel或db_hotel),必须改成一致,否则启动后一查数据库就报错;characterEncoding=utf8是中文不乱码的关键;PASSWORD改成你自己本机MySQL的密码。我见过不少新手在这三个地方栽跟头,代码一行没改,就是连不上数据库。

这套源码的DAO层应该也为每张表提供了增删改查方法,PwdNameUserServlet调用用户表的查询和更新,AddHotelServlet和UpdateHostelServlet调用客房表的插入与更新。理解了这个基础结构,后面看任何Servlet逻辑都会很快。

3. 前台订房链路复现:注册、搜索、预约、留言的代码走读

3.1 用户注册与登录:PwdNameUserServlet的密码处理

前台第一个环节是用户注册和登录。这个系统的登录逻辑由PwdNameUserServlet承担,从名字就能看出来它同时处理密码和用户名相关操作。注册时用户提交用户名、密码、手机号,Servlet接收参数后先做一次「用户名是否已存在」的查询,再进行插入。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); String username = request.getParameter("username"); String password = request.getParameter("password"); if ("register".equals(action)) { UserDao dao = new UserDao(); if (dao.isUsernameExists(username)) { request.setAttribute("msg", "用户名已存在"); request.getRequestDispatcher("register.jsp").forward(request, response); return; } User user = new User(); user.setUsername(username); user.setPassword(password); boolean flag = dao.addUser(user); if (flag) { response.sendRedirect("login.jsp"); } else { request.setAttribute("msg", "注册失败"); request.getRequestDispatcher("register.jsp").forward(request, response); } } }

这里有个明显的安全点:密码是明文存储的。实际项目中绝对不能用明文密码,至少要MD5加盐或BCrypt加密。但毕设项目里很多都图省事直接存明文,如果你要拿这套系统去答辩,建议至少把UserDao里的addUser方法改成加密后再入库。

request.setCharacterEncoding("UTF-8")必须放在获取参数之前,否则中文用户名会乱码。这行代码我在无数项目里看到有人漏写,后果就是数据库里存进去的中文变成问号。

3.2 客房搜索与预约:ShoppingServlet的订房核心逻辑

客房搜索的入口是首页的搜索表单,用户选择入住日期、退房日期、客房分类,提交后由ListServlet接收条件并拼接SQL查询。搜索的关键是「动态SQL拼接」——不同条件组合拼出不同的where子句。

String sql = "SELECT * FROM room WHERE 1=1"; List<Object> params = new ArrayList<>(); String categoryId = request.getParameter("categoryId"); String status = request.getParameter("status"); if (categoryId != null && !categoryId.isEmpty()) { sql += " AND category_id = ?"; params.add(Integer.parseInt(categoryId)); } if (status != null && !status.isEmpty()) { sql += " AND status = ?"; params.add(status); }

WHERE 1=1这个写法看着不优雅,但它在动态SQL里非常实用——后续每追加一个条件都不用判断是不是第一个条件,直接拼AND就行。params列表配合PreparedStatement的setObject方法填充占位符,既能防止SQL注入,又能避免字符串拼接带来的引号地狱。

预约客房的逻辑在ShoppingServlet里。它做的事情是:接收客房ID、用户ID、入住日期,先检查该房间在选定日期段内是否已被预约,如果可订就生成订单,同时把客房表的剩余房间数减一。这一步涉及两张表的操作,理论上应该放在事务里,否则可能出现「订单生成了但房间数没减」的数据不一致问题。源码里如果没加事务,你可以在Connection上手动开启:

conn.setAutoCommit(false); // 执行插入订单 + 更新剩余房间数 conn.commit();

3.3 在线留言与预定记录:两个容易被忽视的前台功能

在线留言的前台逻辑比较简单:用户提交留言内容,Servlet把用户ID和留言内容插入留言表,然后跳转到留言列表页展示所有留言。这里值得注意的一个细节是留言列表通常要关联用户表查用户名,而不是直接显示用户ID,不然页面上全是数字,用户根本看不出是谁留的言。SQL大概是SELECT m.*, u.username FROM message m JOIN user u ON m.user_id = u.id ORDER BY m.create_time DESC。

预定记录功能则是对应当前登录用户的订单列表。查询逻辑不复杂,但有一个体验细节:订单状态最好做中文映射。比如数据库里存的是0和1,页面显示时要翻译成「已预约」和「已取消」,不然用户看到一串状态值会莫名其妙。这个映射可以直接在JSP里做,也可以在后端先把状态值翻译好再传给页面。

4. 后台管理落地:客房维护、分类管理、会员与留言管理

4.1 客房管理:AddHotelServlet与UpdateHostelServlet的双向校验

后台的客房管理是Servlet方法最密集的部分,涉及新增、删除、修改、列表查询四个操作。AddHotelServlet处理客房新增,UpdateHostelServlet处理客房信息更新,DelServlet负责删除。这三个类配合起来就是完整的客房CRUD。

新增客房时,管理员提交房号、分类、价格、描述等字段。AddHotelServlet拿到参数后,先做一次「房号是否重复」的校验,再插入数据库:

String roomNo = request.getParameter("roomNo"); String categoryId = request.getParameter("categoryId"); double price = Double.parseDouble(request.getParameter("price")); RoomDao dao = new RoomDao(); if (dao.isRoomNoExists(roomNo)) { request.setAttribute("msg", "该房号已存在"); request.getRequestDispatcher("admin/room_add.jsp").forward(request, response); return; } Room room = new Room(); room.setRoomNo(roomNo); room.setCategoryId(Integer.parseInt(categoryId)); room.setPrice(price); boolean flag = dao.addRoom(room); if (flag) { response.sendRedirect("ListServlet?action=admin"); } else { request.setAttribute("msg", "添加失败"); request.getRequestDispatcher("admin/room_add.jsp").forward(request, response); }

这里的关键是Double.parseDouble——如果管理员在价格输入框里填了非数字内容,这里会直接抛NumberFormatException导致500错误。在实际项目里,这种前端参数校验应该在提交时就用JavaScript拦一道,后端再try-catch兜底。这套源码未必做了双端校验,所以你拿到后可以在JSP页面加一个onsubmit校验函数。

UpdateHostelServlet的修改逻辑类似,但多了「回显」这一步。点击编辑按钮时,会先根据客房ID查出原有信息并填充到表单里,提交后再做更新。这两个类建议对比着看,你会发现它们调用的DAO方法几乎一一对应。

4.2 客房分类管理:分类删不掉的边界情况

客房分类管理通常是一张独立的分类表,和客房表通过category_id关联。这里有一个典型的业务边界:如果某个分类下已经挂了客房,直接删这个分类会导致客房成为「无主数据」。所以DelServlet在删除分类前,应该先查一下该分类下有没有客房:

String id = request.getParameter("id"); int count = roomDao.countByCategoryId(Integer.parseInt(id)); if (count > 0) { request.setAttribute("msg", "该分类下还有客房,无法删除"); request.getRequestDispatcher("admin/category_list.jsp").forward(request, response); return; } categoryDao.deleteById(Integer.parseInt(id));

这个判断就是「外键约束的业务层实现」。如果数据库里没建外键,这个校验就是最后一道防线;如果建了外键,数据库层面也会拦截。两边都做才稳妥。另外,后台的剩余房间查询本质上是SELECT * FROM room WHERE status='可订',配合分类条件做筛选,逻辑不复杂,但要注意状态字段的值前后端必须统一——数据库存的是1,页面就判断1,别出现前端用"可订"字符串去比对数据库1整数的低级错误。

4.3 会员与留言管理:管理员视角的交互闭环

会员管理在后台就是「查看注册用户列表」加「删除恶意账号」。ModifyUserServlet负责修改用户信息,比如管理员可以重置用户密码。这里有个细节:重置密码时,如果前后端都没有加密处理,新密码会明文入库并显示在列表里,注意在答辩时解释清楚你的安全策略。

留言管理是「留言-回复」闭环的后半段。用户在客户端的留言进入后台列表,管理员可以回复或删除。回复功能通常是在留言表加一个reply字段,管理员提交回复文本后更新该字段。前台留言列表展示时,先显示用户留言内容,如果有回复,就在下方以不同样式展示管理员回复。

这个模块的代码主要以JSP页面展示为主,配合一个更新回复的Servlet。看起来不起眼,但它是前台「在线留言」功能能否形成闭环的关键——很多毕设只做了留言展示,没有回复功能,评委一问就答不上来。

5. 避坑与常见问题:从Tomcat路径到SmartUpload上传的典型翻车现场

5.1 数据库连不上:永远先查URL、用户名、密码这三个参数

现象:项目部署到Tomcat后,打开页面报Cannot create PoolableConnectionFactory或Connection refused。

原因:九成是DBUtil里的数据库名、用户名、密码和你本机MySQL不一致。比如脚本导入时建库名是hotel_db,代码里写的却是hotel;或者本机MySQL密码是root,代码里写的是123456。

解决:打开DBUtil或BaseDao,核对URL中的库名、USER、PASSWORD三个值。改完后重启Tomcat。另外检查MySQL服务是否启动,Windows下可以在命令行执行net start mysql。

5.2 启动报错:Tomcat版本与JDK版本不匹配

现象:Tomcat启动时抛出UnsupportedClassVersionError,或直接启动失败,日志里提示版本冲突。

原因:Tomcat7默认要求JDK6或JDK7,如果你本机装的是JDK11或JDK17,字节码版本不兼容。这个项目是按Tomcat7的标准写的,用太高版本的JDK反而出问题。

解决:一劳永逸的做法是装一个JDK8,同时把Tomcat换成Tomcat8.5或Tomcat9。JDK8是目前兼容性最好的版本,绝大多数老项目都能跑。Eclipse里记得把项目的Java Compiler级别改成1.8。

5.3 JSP页面反复刷新才能看到新数据

现象:前台新增一条留言或后台修改客房信息后,浏览器需要手动刷新甚至重启Tomcat才能看到变化。

原因:浏览器缓存了旧JSP页面,或者Tomcat的JSP自动重编译没生效。JSP本质是servlet,修改后的JSP需要重新编译,如果开发模式下缓存没关,就会一直显示旧页面。

解决:开发阶段在JSP页面头部加一行禁用缓存的meta标签:

<% response.setHeader("Cache-Control", "no-cache"); %>

或者设置Tomcat的web.xml里jsp相关的development=true。如果改了Java类,Tomcat默认不会热部署,需要右键项目选择Clean再重启。这不是代码bug,是开发环境配置问题,排查时别死磕代码。

5.4 SmartUpload上传文件总是不成功或中文文件名乱码

现象:使用SmartUpload组件上传客房图片时,文件没保存到指定目录,或保存后文件名中文变成乱码。

原因:SmartUpload组件在老项目中很常见,但它对UTF-8的支持不算好。常见问题有两个:一是上传目录在Tomcat里不存在,二是setCharacterEncoding的位置不对导致文件名取出来就是乱码。另外,SmartUpload上传时如果表单里有普通文本字段,取值方式要改用su.getRequest().getParameter(),不能用request.getParameter(),否则拿不到值。

解决:上传目录用绝对路径,比如在Servlet里动态获取:

String savePath = getServletContext().getRealPath("/upload"); File dir = new File(savePath); if (!dir.exists()) { dir.mkdirs(); }

文件名统一用时间戳重命名,避免中文文件名带来的编码问题。比如System.currentTimeMillis() + ".jpg"。这招还能顺便避免重名覆盖问题。

提示:如果你打算把客房图片做成可点击定位的,注意SmartUpload保存的图片路径要存到数据库,JSP里展示时用<img src="upload/xxx.jpg">相对路径。页面加载完成后配合JavaScript计算图片坐标,别在图片还没加载完时就去读偏移量,否则拿到的是0。

5.5 预约房间后数据对不上

现象:用户预约成功后,后台查「剩余房间查询」发现房态没变,或者订单记录和房间数不一致。

原因:ShoppingServlet在处理预约时,插入订单和更新房间数两步操作没有放在同一个事务里。第一步成功、第二步失败,数据就对不上了。

解决:在Servlet的数据库操作中显式开启事务:

Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 执行插入订单SQL // 执行更新房间剩余数SQL conn.commit(); } catch (SQLException e) { conn.rollback(); e.printStackTrace(); } finally { conn.setAutoCommit(true); DBUtil.close(conn, null, null); }

这是我建议你重点改的一处代码,答辩时如果被问「为什么订单和房间数会不一致」,这个事务处理就是你的加分项。

6. 部署验证与扩展技巧:30分钟跑通全流程

拿到源码后,我建议你按下面的清单做一次完整的部署验证,确保每一步都走通再开始改代码。

第一步,用Navicat新建数据库,导入SQL脚本,核对表名和数据库名;第二步,修改DBUtil里的连接参数;第三步,把项目导入Eclipse或IDEA,配置Tomcat,注意项目的Deploy Path要指向webapps;第四步,启动Tomcat,浏览器访问首页,注册一个新用户,搜房、预约、留言走一遍;第五步,用管理员账号登录后台,添加客房分类、添加客房、回复留言、查询订房信息,全流程测一遍。

验证时重点看三个地方:中文是否乱码、图片上传是否成功、预约后房间数是否变化。这三个点是最容易出问题的。

如果你想让这套毕设往上拔一个档次,我建议做三个小改造:把密码改成MD5加盐存储,可能只需要改UserDao里的插入和查询方法;给JDBC操作加上事务管理,重点处理预约房间那段逻辑;把DAO层的重复代码抽成一个BaseDao,提供通用的增删改查方法。做完这三个改造,代码量不会增加多少,但架构清晰度和安全性能上一个台阶。

最后说一个我自己的习惯:每次部署这种老项目,我都会先把数据库脚本从头到尾执行一遍,确认没有报错再连代码。以前我图省事,直接导入备份的SQL文件,结果某个表的字段类型对不上,排错排了两小时才发现是脚本没执行全。从那以后,凡是拿到带SQL脚本的项目,我都强制走一遍「建库→执行脚本→查表结构→核对字段名」的流程,确认无误后再启动Tomcat。你拿到这套源码,也建议先做这一步,数据库稳了,后面就顺了。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询