ARTICLE DETAIL

资讯详情

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

JSP+MySQL酒店住宿管理系统:从入住登记到退房结算的完整实现

JSP+MySQL酒店住宿管理系统:从入住登记到退房结算的完整实现 简介这份资源是《酒店住宿管理系统的设计与实现》毕业设计文档面向计算机相关专业学生及需要完成管理信息系统课程设计的学习者帮助解决选题落地、需求分析与系统设计思路梳理等问题。压缩包内共1个doc文件约1.61MB完整呈现论文式结构涵盖摘要、系统背景与意义、关键技术、系统分析、系统设计、系统实现、系统测试与结论等章节。文档以B/S架构为主线结合HTML、JavaScript、JSP与MySQL围绕客房管理、财务管理、订单管理和管理员管理四大模块展开并给出开发环境、可行性分析及测试用例设计思路。读者可借此了解管理信息系统的完整开发流程掌握数据库建立与功能模块间数据关联的处理方法也可作为同类酒店管理类毕业设计的参考模板与写作范例。目前已有109人学习适合需要系统化参考文档与设计思路的读者。1. 酒店住宿管理系统的设计与实现从入住登记到退房结算一套 JSPMySQL 方案能跑多远很多做课程设计或接私活的朋友第一次拿到“酒店住宿管理系统”这个题目时脑子里浮现的往往是前台小姐姐点几下鼠标就完成入住的画面。但真正动手写起来你会发现核心难点根本不在界面而在于房态实时同步、押金与消费的账目对冲、以及退房时那几笔杂费怎么算进 MySQL 的事务里。这套系统本质上是一个带时间维度的库存管理系统房间是库存入住是出库退房是归还中间还夹杂着换房、续住、钟点房这些非标准流程。我见过太多版本用 JSP 直接写 SQL 拼字符串最后死在并发入住和脏读上。这篇笔记面向的是正在做 JSPMySQL 课程设计、或者想用最传统的技术栈把业务闭环跑通的开发者。我会把表结构、关键事务、JSP 页面里那些容易翻车的细节拆开讲目标只有一个让你做出来的东西能真正演示“同一间房不能卖两次”。2. 先定房态模型与数据库表别急着写 JSP 页面2.1 为什么房态不能只用一个 status 字段新手最容易犯的错是在 room 表里塞一个 status 字段值设为“空闲/已入住/维修”。这个模型在演示时没问题一旦遇到“今天退房、明天入住”的预订场景就崩了。因为房态不是房间的固有属性而是时间轴上的占用区间。正确的做法是把“房间物理信息”和“占用记录”分开。room 表只存房号、类型、楼层、价格check_in 表存入住记录包含 room_id、入住时间、预计退房时间、实际退房时间、状态。判断某间房在某个时间段是否可订靠的是查询重叠区间而不是读一个静态字段。常见做法是用WHERE room_id ? AND check_in_time ? AND check_out_time ?来判断冲突。这个逻辑写进 DAO 层JSP 页面只负责展示结果。2.2 五张核心表的建表语句与字段说明下面是我一般会用的最小表结构跑在 MySQL 5.7 或 8.0 上都行。注意字符集用 utf8mb4不然客人姓名里的生僻字会变成问号。-- 房间表只存物理属性 CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(10) NOT NULL UNIQUE COMMENT 房号如 8301, type_id INT NOT NULL COMMENT 关联房型表, floor INT DEFAULT 1, status TINYINT DEFAULT 0 COMMENT 0 可用 1 维修 2 锁定, INDEX idx_room_no (room_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 房型表价格存在这里避免改价时改多行 CREATE TABLE room_type ( id INT PRIMARY KEY AUTO_INCREMENT, type_name VARCHAR(20) NOT NULL, price DECIMAL(10,2) NOT NULL, hour_price DECIMAL(10,2) DEFAULT 0 COMMENT 钟点房单价 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 入住记录表核心表所有冲突判断都靠它 CREATE TABLE check_in ( id INT PRIMARY KEY AUTO_INCREMENT, room_id INT NOT NULL, guest_name VARCHAR(50) NOT NULL, id_card VARCHAR(20), phone VARCHAR(20), check_in_time DATETIME NOT NULL, expect_out_time DATETIME NOT NULL, actual_out_time DATETIME DEFAULT NULL, deposit DECIMAL(10,2) DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1 在住 2 已退房 3 已换房, INDEX idx_room_time (room_id, check_in_time, expect_out_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 消费记录表退房时一起结算 CREATE TABLE consume ( id INT PRIMARY KEY AUTO_INCREMENT, check_in_id INT NOT NULL, item_name VARCHAR(50), amount DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 操作日志表出问题时的后悔药 CREATE TABLE operate_log ( id INT PRIMARY KEY AUTO_INCREMENT, operator VARCHAR(30), action VARCHAR(100), detail TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时有两个参数值得留意。idx_room_time这个联合索引是为了加速冲突查询顺序必须是 room_id 在前、时间在后。DECIMAL(10,2)用于金额别用 FLOAT否则退房结算时会出现 0.01 元的误差对账对到怀疑人生。status字段在 room 表里只表示物理可用性不表示入住状态这一点要在 DAO 注释里写清楚不然接手的人会乱改。2.3 用 SQL 直接验证房态冲突逻辑在写 Java 代码之前先在 MySQL 命令行里把逻辑跑通。假设 8301 房已有一条记录2025-01-01 14:00 入住2025-01-03 12:00 退房。现在要查 2025-01-02 到 2025-01-04 能不能订。SELECT COUNT(*) FROM check_in WHERE room_id 1 AND status 1 AND check_in_time 2025-01-04 14:00 AND expect_out_time 2025-01-02 14:00;返回大于 0 就说明冲突。这个条件的意思是已有记录的入住时间早于新预订的退房时间且已有记录的预计退房时间晚于新预订的入住时间。两个区间有交集。注意时间边界如果旧记录 12:00 退房新记录 14:00 入住中间留了 2 小时打扫不算冲突。这个缓冲时间我一般设 2 小时写在配置里别硬编码。3. 用 JSPServlet 把入住登记跑通从表单到事务提交3.1 入住登记的完整流程与页面字段设计入住登记页面需要收集房号、客人姓名、身份证、电话、押金、预计退房时间。房号不要用文本框让用户手输用下拉框从 room 表里查可用房间。JSP 页面里用 JSTL 遍历别用 scriptlet 写 Java 代码不然维护起来像考古。表单提交到 CheckInServletServlet 里做三件事校验参数、开启事务、插入记录并更新房态。这里的关键是“先查冲突再插入”而且查询和插入必须在同一个事务里否则两个前台同时提交同一间房都查到没冲突然后都插入成功就翻车了。3.2 带事务的入住 Servlet 核心代码// CheckInServlet.java 核心片段 protected void doPost(HttpServletRequest req, HttpServletResponse resp) { Connection conn null; PreparedStatement psCheck null; PreparedStatement psInsert null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 int roomId Integer.parseInt(req.getParameter(roomId)); String inTime req.getParameter(checkInTime); String outTime req.getParameter(expectOutTime); // 1. 在事务内查冲突加行锁 String checkSql SELECT id FROM check_in WHERE room_id? AND status1 AND check_in_time ? AND expect_out_time ? FOR UPDATE; psCheck conn.prepareStatement(checkSql); psCheck.setInt(1, roomId); psCheck.setString(2, outTime); psCheck.setString(3, inTime); rs psCheck.executeQuery(); if (rs.next()) { conn.rollback(); req.setAttribute(msg, 该房间在所选时段已被占用); req.getRequestDispatcher(checkin.jsp).forward(req, resp); return; } // 2. 插入入住记录 String insertSql INSERT INTO check_in(room_id,guest_name,id_card,phone, check_in_time,expect_out_time,deposit,status) VALUES(?,?,?,?,?,?,?,1); psInsert conn.prepareStatement(insertSql); psInsert.setInt(1, roomId); psInsert.setString(2, req.getParameter(guestName)); psInsert.setString(3, req.getParameter(idCard)); psInsert.setString(4, req.getParameter(phone)); psInsert.setString(5, inTime); psInsert.setString(6, outTime); psInsert.setBigDecimal(7, new BigDecimal(req.getParameter(deposit))); psInsert.executeUpdate(); conn.commit(); // 提交事务 resp.sendRedirect(roomList.jsp); } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); } finally { DBUtil.close(rs, psCheck, psInsert, conn); } }这段代码里最值钱的是FOR UPDATE。它会在查询到的行上加排他锁第二个并发请求会阻塞在这里直到第一个事务提交。没有这个锁两个线程同时查到空结果然后都插入数据库里就会出现同一间房两条在住记录。参数方面check_in_time和expect_out_time用字符串传入 MySQL 的 DATETIME 字段格式必须是yyyy-MM-dd HH:mm:ssJSP 表单里用input typedatetime-local的话提交的值带 T需要在 Servlet 里替换成空格。这个坑我踩过页面报错“Data truncation”查了半天才发现是格式问题。3.3 房态展示页面的 JSP 写法与刷新策略房态图页面要展示所有房间的当前状态。不要每查一个房间就发一条 SQL用一条 JOIN 查询把房间和当前在住记录一起拉出来。SELECT r.room_no, r.floor, rt.type_name, rt.price, c.guest_name, c.expect_out_time FROM room r JOIN room_type rt ON r.type_id rt.id LEFT JOIN check_in c ON c.room_id r.id AND c.status 1 WHERE r.status 0 ORDER BY r.floor, r.room_no;JSP 里用c:forEach遍历根据guest_name是否为空来显示“空闲”或“在住”。页面加载完后如果想定时刷新房态用 JavaScript 的setTimeout配合location.reload()间隔设 30 秒以上别设 5 秒不然数据库连接池会被打满。热搜词里有人问“jsp页面让加载完后刷新一次”其实就是在body末尾加一段脚本但要注意刷新会丢失表单填写内容所以只用在纯展示页。4. 退房结算与押金处理把账算对把事务写稳4.1 退房结算的账目构成与计算顺序退房时客人应付的总金额 房费 消费 - 押金。房费按实际入住时长算这里有个业务决策是按天算还是按小时算。常见做法是超过 12:00 退房加收半天超过 18:00 加收全天。这个规则写在 Java 方法里别写在 SQL 里因为逻辑会变。消费记录从 consume 表里按 check_in_id 汇总。押金是入住时收的退房时多退少补。计算顺序必须是先算总消费再扣押金最后得出应退或应补金额。如果应补金额大于 0说明押金不够要提醒前台向客人收取。4.2 退房事务的代码实现与行锁// CheckOutServlet.java 核心片段 conn.setAutoCommit(false); // 锁定该入住记录防止同时退房 String lockSql SELECT id,deposit,check_in_time,expect_out_time FROM check_in WHERE id? AND status1 FOR UPDATE; ps conn.prepareStatement(lockSql); ps.setInt(1, checkInId); rs ps.executeQuery(); if (!rs.next()) { conn.rollback(); throw new RuntimeException(该入住记录不存在或已退房); } BigDecimal deposit rs.getBigDecimal(deposit); // 计算房费 BigDecimal roomFee calcRoomFee(rs.getTimestamp(check_in_time), rs.getTimestamp(expect_out_time), new Date()); // 汇总消费 String sumSql SELECT IFNULL(SUM(amount),0) FROM consume WHERE check_in_id?; ps conn.prepareStatement(sumSql); ps.setInt(1, checkInId); rs ps.executeQuery(); rs.next(); BigDecimal consumeTotal rs.getBigDecimal(1); BigDecimal total roomFee.add(consumeTotal); BigDecimal refund deposit.subtract(total); // 更新入住记录为已退房 String updateSql UPDATE check_in SET actual_out_timeNOW(),status2 WHERE id?; ps conn.prepareStatement(updateSql); ps.setInt(1, checkInId); ps.executeUpdate(); conn.commit();FOR UPDATE在这里同样关键。如果两个前台同时给同一间房办退房没有锁的话两个事务都会读到 status1然后都执行更新第二个更新会覆盖第一个账就乱了。加上锁之后第二个事务会等待等它拿到锁时 status 已经变成 2rs.next()返回 false直接提示已退房。参数方面calcRoomFee方法里要处理 null 的 actual_out_time用当前时间兜底。金额计算全部用 BigDecimaladd、subtract、multiply方法别用-*。4.3 押金不足时的处理与日志记录如果 refund 是负数说明押金不够。这时候不能直接退房要在页面上弹出提示让前台确认是否收取差额。确认后把差额记到 consume 表里item_name 写“补收押金差额”然后再执行退房。同时往 operate_log 表里插一条记录写明操作人、时间、房间号、补收金额。这个日志表平时没用一旦客人事后投诉说“我押金明明交够了”翻日志就能说清楚。日志的 detail 字段用 JSON 格式存方便以后解析。5. 避坑与排查JSPMySQL 酒店系统最常见的五个翻车点5.1 中文乱码从 JSP 到 MySQL 一路飘红现象入住登记时客人姓名存进数据库变成??或者页面显示å¼ ä¸‰。原因JSP 页面、Servlet 请求、JDBC 连接、MySQL 表字符集四个环节只要有一个不是 utf8mb4就会乱。解决JSP 顶部加% page contentTypetext/html;charsetUTF-8 %Servlet 里request.setCharacterEncoding(UTF-8)JDBC URL 加?useUnicodetruecharacterEncodingutf8建表时指定DEFAULT CHARSETutf8mb4。四个地方都对齐乱码就没了。5.2 时间格式带 T 导致插入失败现象用input typedatetime-local提交MySQL 报Incorrect datetime value。原因浏览器提交的值是2025-01-01T14:00MySQL 只认2025-01-01 14:00:00。解决在 Servlet 里用value.replace(T, )如果秒数缺失补上:00。或者干脆用input typetext让用户手输但体验差。我一般在前端用 JavaScript 监听 change 事件把 T 替换掉再提交。5.3 连接池耗尽导致页面卡死现象演示时点几下页面就转圈最后报Could not get a connection。原因JSP 里直接DriverManager.getConnection每次请求都新建连接用完没关。解决用 Tomcat 的 JNDI 数据源或者 Druid 连接池在context.xml里配maxActive20maxWait3000。DAO 里用 try-with-resources 确保Connection、PreparedStatement、ResultSet都关闭。别在 JSP 里写new连接那是血泪教训。5.4 并发入住导致同一间房两条在住记录现象两个人同时提交同一间房的入住数据库里出现两条 status1 的记录。原因查询冲突和插入之间没有锁两个事务都查到空结果。解决在冲突查询 SQL 末尾加FOR UPDATE并且把conn.setAutoCommit(false)放在查询之前。注意FOR UPDATE必须在事务里才生效如果 autocommit 是 true锁会立即释放等于没加。5.5 退房后房态没更新房间永远显示在住现象退房操作成功但房态图里那间房还是红色“在住”。原因房态图查询用的是check_in表里 status1 的记录退房时更新了 status2但页面缓存了旧数据。解决退房成功后用response.sendRedirect重定向到房态页而不是forward强制浏览器发新请求。另外检查房态查询 SQL 的 JOIN 条件确保c.status 1写在 ON 子句里不是 WHERE 里否则 LEFT JOIN 会退化成 INNER JOIN空闲房间查不出来。6. 用一条 SQL 做房态日历把可订日期算给前端房态图只能看“现在”但前台经常需要查“下周有哪些房能订”。这时候可以用一条 SQL 生成未来 7 天的可订房列表。思路是先查出所有可用房间再对每个房间检查未来 7 天是否有冲突记录。用 MySQL 的日期函数和NOT EXISTS实现。SELECT r.room_no, rt.type_name, rt.price, DATE_ADD(CURDATE(), INTERVAL seq DAY) AS query_date FROM room r JOIN room_type rt ON r.type_id rt.id CROSS JOIN ( SELECT 0 AS seq UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 ) days WHERE r.status 0 AND NOT EXISTS ( SELECT 1 FROM check_in c WHERE c.room_id r.id AND c.status 1 AND c.check_in_time DATE_ADD(DATE_ADD(CURDATE(), INTERVAL seq DAY), INTERVAL 1 DAY) AND c.expect_out_time DATE_ADD(CURDATE(), INTERVAL seq DAY) ) ORDER BY r.room_no, query_date;这条 SQL 返回每个房间未来 7 天中可订的日期。前端拿到结果后用 JavaScript 渲染成日历表格可订的格子绿色不可订的灰色。参数方面seq从 0 到 6 代表今天到第 6 天如果要查 30 天把 UNION 扩展到 29 就行但别超过 100性能会下降。DATE_ADD(CURDATE(), INTERVAL seq DAY)得到查询日期冲突判断的边界是入住时间早于查询日期的次日且退房时间晚于查询日期当天。这个逻辑和前面的冲突查询一致只是把固定时间换成了动态日期。我一般会把这个查询封装成 DAO 方法返回ListMapString, ObjectJSP 里用 JSTL 遍历生成表格。如果数据量大可以在check_in表的(room_id, check_in_time, expect_out_time)上建联合索引查询速度会快很多。最后说一个习惯每次改完 SQL先在 MySQL 命令行里用EXPLAIN看一眼执行计划确认走了索引再写进代码。这个习惯帮我省了无数次线上排查的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表