ARTICLE DETAIL

资讯详情

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

Servlet+JSP酒店客房预定管理系统前后台分离实战

Servlet+JSP酒店客房预定管理系统前后台分离实战 简介这是一套基于Servlet与JSP实现的酒店客房预定管理系统采用前后台分离设计面向计算机相关专业正在做毕业设计的学生以及需要Java Web项目实战练习的学习者可直接作为毕设选题使用。项目使用Tomcat作为Web服务器、MySQL存储数据开发工具支持Eclipse或IDEA并配套Navicat管理数据库。功能上用户端涵盖注册登录、预约房间、客房搜索、在线留言与预定记录管理员端包含客房管理、客房分类管理、会员管理、留言管理、剩余房间查询及订房信息查询业务闭环较为完整。资源包共170个文件约1.21MB以41个Java源文件、41个class编译文件、34个JSP页面为主另含GIF与JPG图片素材、jar依赖、XML配置及一份SQL数据库脚本源码与建库脚本齐备导入后即可运行调试。目前已有339人学习下载适合希望快速掌握ServletJSP开发流程、积累项目经验的读者参考。1. 基于ServletJSP的酒店客房预定管理系统前后台分离到底该怎么落地很多同学做毕设或者接私活一听到“酒店客房预定管理系统”就觉得是个烂大街的题目随便找个模板改改就交了。但真正动手写的时候才发现前台要展示房型、日期、价格后台要管订单、房态、用户两边数据还得实时对上——用纯 JSP 写吧页面里嵌一堆 Java 代码改个按钮颜色都得翻三遍文件用前后端分离吧又觉得 SpringBootVue 太重答辩时老师问“你这 Servlet 到底干嘛的”都答不上来。其实基于 ServletJSP 做前后台核心思路就一句话Servlet 当控制器接请求、调业务、存数据JSP 当视图只负责渲染中间用 Session 和 Request 传值。这套东西看着老但胜在透明——请求怎么走、数据怎么流全在你眼皮底下调试的时候不用猜框架黑匣子。适合谁适合刚学完 JavaWeb、想用一个完整项目把 Servlet 生命周期、JSP 内置对象、Session 管理、JDBC 事务串起来的人。下面我就按实际开发顺序把这个系统的前后台怎么拆、怎么连、怎么避坑讲清楚。2. 前后台功能拆解与数据库表设计先想清楚谁管什么2.1 前台和后台的边界在哪里前台面向客人核心动作就三个查房、下单、看订单。查房要支持按日期、房型、价格区间筛选下单要锁房、算价、生成订单号看订单要能根据当前登录用户拉出历史记录。后台面向管理员核心动作是管房态可订/已订/维修、管订单确认/取消/入住、管用户禁用/重置密码。这两边的数据源是同一套表但操作权限和展示字段完全不同。常见做法是前台只暴露room_type、room、order三张表的读和部分写后台则全表可操作。我一般会在 Servlet 层用WebServlet(/admin/*)和WebServlet(/api/*)把路径分开再配一个AuthFilter拦截/admin/*检查 Session 里的role字段。2.2 数据库表设计五张表撑起整个系统不要一上来就建十几张表酒店预定核心就五张表名关键字段说明userid, username, password, phone, rolerole0 客人role1 管理员room_typeid, type_name, price, bed_count, img_url房型基础信息roomid, room_no, type_id, statusstatus: 0可订 1已订 2维修orderid, order_no, user_id, room_id, check_in, check_out, total_price, statusstatus: 0待确认 1已确认 2已取消 3已入住admin_logid, admin_id, action, target_id, create_time后台操作留痕答辩加分项建表时注意order是 MySQL 关键字用反引号包起来或者改名orders。日期字段用DATE而不是DATETIME因为酒店按天算不按小时。total_price用DECIMAL(10,2)别用FLOAT否则算钱会出现0.30000000000000004这种玄学问题。2.3 用 SQL 脚本初始化测试数据-- 建库 CREATE DATABASE hotel_db DEFAULT CHARSET utf8mb4; USE hotel_db; -- 房型表 CREATE TABLE room_type ( id INT PRIMARY KEY AUTO_INCREMENT, type_name VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL, bed_count INT DEFAULT 1, img_url VARCHAR(255) ); -- 房间表 CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(10) UNIQUE NOT NULL, type_id INT, status TINYINT DEFAULT 0, FOREIGN KEY (type_id) REFERENCES room_type(id) ); -- 订单表注意反引号 CREATE TABLE order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE NOT NULL, user_id INT, room_id INT, check_in DATE NOT NULL, check_out DATE NOT NULL, total_price DECIMAL(10,2), status TINYINT DEFAULT 0, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 插入测试数据 INSERT INTO room_type (type_name, price, bed_count) VALUES (标准大床房, 299.00, 1), (豪华双床房, 459.00, 2), (行政套房, 899.00, 1); INSERT INTO room (room_no, type_id, status) VALUES (A101, 1, 0), (A102, 1, 0), (B201, 2, 0), (B202, 2, 0), (C301, 3, 0);这段脚本直接复制到 Navicat 或者命令行source执行就行。room_no加了唯一索引防止后台录入重复房间号。order_no用唯一索引后面生成订单时用UUID截取加时间戳避免并发重复。2.4 前后台交互的数据格式约定前台页面用 JSP 渲染但筛选房型这种局部刷新我建议用 AJAX 请求 Servlet 返回 JSON。统一约定{ code: 200, msg: success, data: {...} }。Servlet 里用response.setContentType(application/json;charsetUTF-8)然后手写 JSON 或者引gson-2.8.9.jar。别用PrintWriter直接拼字符串引号转义能把你逼疯。后台管理页面则直接用 JSP 转发因为管理员操作频率低没必要上 AJAX减少调试成本。3. Servlet 控制器与 JSP 视图的配合从请求到渲染的完整链路3.1 一个查房请求的完整生命周期假设用户在index.jsp选了入住日期2025-06-01、离店2025-06-03、房型“标准大床房”点击查询。浏览器发请求GET /hotel/api/room/search?checkIn2025-06-01checkOut2025-06-03typeId1。Tomcat 根据web.xml或WebServlet映射到RoomSearchServlet。Servlet 的doGet方法里用request.getParameter()拿三个参数做空值和日期格式校验。调RoomService.searchAvailableRooms()Service 层查room表里status0且id NOT IN (SELECT room_id FROM order WHERE check_in ? AND check_out ?)的房间。把结果ListRoom存到request.setAttribute(roomList, list)。request.getRequestDispatcher(/search_result.jsp).forward(request, response)。JSP 里用 JSTLc:forEach遍历roomList渲染卡片。整个链路里 Servlet 只做参数校验和调度业务逻辑在 ServiceSQL 在 DAOJSP 只负责显示。这样拆的好处是改查询条件只动 Service改页面样式只动 JSP互不干扰。3.2 用注解配置 Servlet 的代码模板WebServlet(/api/room/search) public class RoomSearchServlet extends HttpServlet { private RoomService roomService new RoomServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 拿参数 String checkIn req.getParameter(checkIn); String checkOut req.getParameter(checkOut); String typeIdStr req.getParameter(typeId); // 2. 基础校验 if (checkIn null || checkOut null || typeIdStr null) { req.setAttribute(error, 请选择完整日期和房型); req.getRequestDispatcher(/index.jsp).forward(req, resp); return; } try { int typeId Integer.parseInt(typeIdStr); // 3. 调业务 ListRoom roomList roomService.searchAvailableRooms(checkIn, checkOut, typeId); // 4. 存数据并转发 req.setAttribute(roomList, roomList); req.setAttribute(checkIn, checkIn); req.setAttribute(checkOut, checkOut); req.getRequestDispatcher(/search_result.jsp).forward(req, resp); } catch (NumberFormatException e) { resp.sendError(400, 房型参数格式错误); } } }注意doGet里用了forward而不是sendRedirect因为要把roomList带到 JSP。如果用了重定向Request 作用域就失效了JSP 里拿不到数据。另外WebServlet的路径不要和 JSP 文件重名比如/search和/search.jsp同时存在Tomcat 会优先匹配 ServletJSP 就永远访问不到了。3.3 JSP 页面里怎么安全地取数据% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html headtitle查询结果/title/head body h3入住${checkIn} 至 ${checkOut}/h3 c:if test${empty roomList} p该日期段没有可订房间请调整条件。/p /c:if c:forEach items${roomList} varroom div classroom-card span${room.roomNo}/span span${room.typeName}/span spanfmt:formatNumber value${room.price} pattern#,##0.00//span a hreforder/create?roomId${room.id}checkIn${checkIn}checkOut${checkOut}预订/a /div /c:forEach /body /html这里用${room.roomNo}这种 EL 表达式前提是Room实体类里有getRoomNo()方法。JSTL 的fmt:formatNumber能把299.0显示成299.00比在 Java 里String.format再塞回 Request 优雅得多。注意% taglib %的uri别写错JSTL 1.2 的 core 标签库是http://java.sun.com/jsp/jstl/core写错会报The absolute uri cannot be resolved。3.4 订单创建时的并发锁房问题用户点“预订”后OrderCreateServlet要做三件事检查房间在所选日期是否仍可订、算总价、写订单。这里有个坑两个用户同时查同一间房都显示可订然后同时下单就会超卖。常见做法是在 Service 层用synchronized锁住方法或者数据库层面用SELECT ... FOR UPDATE。我一般用后者// DAO 层 public Room findRoomForUpdate(int roomId, Connection conn) throws SQLException { String sql SELECT * FROM room WHERE id ? FOR UPDATE; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, roomId); ResultSet rs ps.executeQuery(); if (rs.next()) { Room room new Room(); room.setId(rs.getInt(id)); room.setStatus(rs.getInt(status)); return room; } } return null; }FOR UPDATE会锁住这一行直到事务提交。Service 里手动管理Connection的setAutoCommit(false)和commit()确保查房、写订单、改房态在一个事务里。注意FOR UPDATE必须在事务里才生效如果autoCommittrue锁会立刻释放等于没加。4. 避坑与排查那些让我熬夜到三点的血泪经验4.1 中文乱码GET 和 POST 要分开治现象前台输入“豪华双床房”查询后台收到æ±å。原因Tomcat 8 以后 GET 请求默认用 ISO-8859-1 解码POST 用请求头里的 charset。解决POST 在 Servlet 第一行写request.setCharacterEncoding(UTF-8)GET 要么在server.xml的 Connector 加URIEncodingUTF-8要么手动new String(request.getParameter(typeName).getBytes(ISO-8859-1), UTF-8)。我一般直接改server.xml一劳永逸。4.2 JSP 页面修改后不生效现象改了search_result.jsp的样式刷新浏览器还是旧样子。原因Tomcat 默认会编译 JSP 成 Servlet但有些 IDE 的热部署没配好或者浏览器缓存了。解决先CtrlF5强制刷新不行就删掉work/Catalina/localhost/项目名/下的编译产物再重启 Tomcat。IDEA 里检查On Update action是否设为Update classes and resources。4.3 Session 过期导致后台操作跳回登录页现象管理员在后台点了几个菜单后突然跳回登录页。原因web.xml里session-config的session-timeout默认 30 分钟但如果你在代码里手动session.invalidate()或者浏览器禁了 CookieSession 就丢了。解决后台每个 Servlet 开头检查session.getAttribute(admin)为空就response.sendRedirect(login.jsp)。另外别用 URL 重写传jsessionid那玩意儿在地址栏里很难看还容易被复制。4.4 订单日期重叠判断写反了现象用户订了 6 月 1 日到 3 日另一个人还能订 6 月 2 日到 4 日。原因SQL 条件写成了check_in ? AND check_out ?应该是check_in ? AND check_out ?新订单的入住日小于已有订单的离店日且新订单的离店日大于已有订单的入住日。解决画个时间轴把四种重叠情况列出来确保 SQL 覆盖所有交叉。这个逻辑我翻车过两次后来直接写单元测试用 JUnit 跑边界值。4.5 数据库连接池没配一并发就崩现象本地测试好好的用 JMeter 发 50 个并发请求报Too many connections。原因每个 Servlet 里DriverManager.getConnection()新建连接用完没关MySQL 默认最大连接数 151几下就满了。解决引druid-1.2.20.jar或HikariCP配initialSize5, maxActive20在ServletContextListener里初始化全局用DataSource拿连接。别忘了在contextDestroyed里dataSource.close()。5. 进阶技巧用 Filter 统一鉴权与日志让代码少写一半5.1 一个 Filter 搞定后台权限和操作日志后台有十几个 Servlet每个都写一遍if (session.getAttribute(admin) null)太蠢了。用WebFilter(/admin/*)拦截所有后台请求在doFilter里做两件事检查登录、记录操作日志。WebFilter(/admin/*) public class AdminAuthFilter implements Filter { private AdminLogService logService new AdminLogServiceImpl(); Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); // 1. 排除登录页和登录接口 String uri request.getRequestURI(); if (uri.contains(login)) { chain.doFilter(req, resp); return; } // 2. 鉴权 if (session null || session.getAttribute(admin) null) { response.sendRedirect(request.getContextPath() /admin/login.jsp); return; } // 3. 记录操作日志只记 POST 操作 if (POST.equalsIgnoreCase(request.getMethod())) { Integer adminId (Integer) session.getAttribute(adminId); String action request.getRequestURI(); logService.save(adminId, action, request.getParameter(id)); } chain.doFilter(req, resp); } }注意request.getSession(false)不会新建 Session避免给未登录用户创建无用 Session。日志记录放在chain.doFilter之前这样即使后续业务抛异常操作意图也留痕了。AdminLogService.save里用单独的线程或者ExecutorService异步写别阻塞主请求。5.2 用 Listener 在项目启动时加载房型缓存房型数据变动频率极低但前台每次查房都要SELECT * FROM room_type没必要。用ServletContextListener在contextInitialized里查一次存到ServletContext的roomTypeMap里JSP 里直接${applicationScope.roomTypeMap}取。后台修改房型后调servletContext.setAttribute更新缓存。这样前台查房少一次数据库交互响应能快 20ms 左右。WebListener public class RoomTypeCacheListener implements ServletContextListener { Override public void contextInitialized(ServletContextEvent sce) { RoomTypeService service new RoomTypeServiceImpl(); MapInteger, RoomType map service.getAllAsMap(); sce.getServletContext().setAttribute(roomTypeMap, map); } }5.3 打包 war 部署到独立 Tomcat 的注意事项IDEA 里跑得好好的mvn package打 war 扔到 Tomcatwebapps下就 404。检查三点pom.xml里packagingwar/packagingWEB-INF/lib下有没有漏掉gson、druid、jstl的 jarweb.xml的metadata-complete是不是true如果是true注解WebServlet全部失效得改回false或者手动配servlet。我习惯在pom.xml里用maven-war-plugin显式指定failOnMissingWebXmlfalse省得它报错。5.4 一个验证系统是否跑通的最小检查清单部署完别急着点页面按这个顺序过一遍访问/admin/login.jsp看能不能打开用错误密码登录看 Filter 有没有拦住登录成功后访问/admin/room/list看房态表格有没有数据前台选日期查房看 JSTL 有没有渲染出卡片点预订看数据库order表有没有新记录把order表里status改成 1刷新后台看订单状态变没变。这六步走完系统基本就稳了。做这个系统最大的教训是别一上来就写代码先把表结构和请求路径画在纸上。我第一版没画写到订单模块发现room表和order表的外键关系搞反了删了重来花了一整天。另外ServletJSP 虽然老但面试和答辩时能讲清楚forward和redirect的区别、Session和Cookie的关系、Filter和Listener的执行顺序比只会 SpringBoot 注解的人更有底气。希望帮到你。本文还有配套的精品资源点击获取
返回列表