基于Spring Boot与MySQL的酒店客房管理系统数据库设计与实现 简介这份资源是面向高校计算机相关专业学生的数据库课程设计参考包聚焦酒店管理系统中的客房管理模块涵盖客房、客户、订单、入住与退房记录等核心实体的数据建模以及基于关系型数据库的表结构设计、主外键约束与完整性维护适合正在完成课程设计或需要理解数据库技术如何落地于实际业务场景的学习者。压缩包共30个文件以18个class编译文件与10个java源码为主另附2张jpg图片整体约404KB源码可直接导入IDE运行调试便于对照理解预订、入住、查询等功能的实现逻辑。目前已有1877人学习下载读者可从中获得从需求分析、表结构设计到SQL增删改查与前端界面联动的完整实践思路并参考测试场景与安全性考量快速搭建自己的课程设计框架。1. 从一张房态表说起这个酒店客房管理系统到底能干什么做过数据库课程设计的人大概都有过这种体验需求文档写得像模像样真到建表的时候客房、订单、入住记录三张表的关系就开始打架改一处牵动三处。这个酒店管理系统客房管理就是冲着这个痛点来的——它把「房态」当成整个系统的核心状态机围绕房间从空闲、预订、入住、退房到清洁的流转把客户、订单、账单、员工几条线串起来。适合正在做数据库课设、想找一个结构完整又能跑通的参考项目的同学也适合想拿它当模板改造成民宿、公寓管理系统的开发者。整套东西不追求花哨重点在表结构设计和 SQL 的落地属于那种「看得懂、跑得起来、改得动」的类型。2. 技术栈与目录结构先看清这套系统由哪些部分组成2.1 技术选型为什么是这套组合这类课程设计项目技术栈通常不会太激进核心诉求是「数据库能体现设计功底界面能演示业务流程」。常见的做法是后端用 JavaSpring Boot或 PythonFlask/Django配 MySQL前端用简单的 HTML 模板引擎或者 Vue 单页。我一般会推荐 Spring Boot MyBatis MySQL 这套原因很实际MyBatis 的 XML 里能直接写复杂 SQL客房查询、订单联表、账单统计这些操作写起来直观答辩时老师一眼能看懂你的 SQL 功底而 Spring Boot 的自动配置省掉了大量环境搭建时间把精力留给数据库设计本身。数据库选 MySQL 8.x字符集用 utf8mb4存储引擎 InnoDB这样外键约束和事务都能用上——客房预订和入住涉及并发改房态事务是绕不开的。前端如果不想折腾Thymeleaf 模板足够想界面好看点Vue3 Element Plus 也行但要注意别把课设做成前端秀重点还是表结构和业务逻辑。2.2 目录结构一份能照着建的工程骨架下面是我按这个项目场景整理的典型目录实际拿到源码时结构可能略有出入但模块划分基本一致hotel-management/ ├── src/main/java/com/hotel/ │ ├── controller/ # 接口层房间、订单、客户、账单 │ │ ├── RoomController.java │ │ ├── OrderController.java │ │ └── BillController.java │ ├── service/ # 业务层房态流转、订单校验 │ │ ├── RoomService.java │ │ └── OrderService.java │ ├── mapper/ # 数据访问层MyBatis 接口 │ │ ├── RoomMapper.java │ │ └── OrderMapper.java │ ├── entity/ # 实体类与表一一对应 │ │ ├── Room.java │ │ ├── Customer.java │ │ └── Order.java │ └── config/ # 数据源、事务、拦截器配置 ├── src/main/resources/ │ ├── mapper/ # MyBatis XML复杂 SQL 都在这 │ │ ├── RoomMapper.xml │ │ └── OrderMapper.xml │ ├── static/ # 前端静态资源 │ ├── templates/ # 页面模板 │ └── application.yml # 数据库连接、端口配置 ├── sql/ │ ├── schema.sql # 建表语句 │ └── data.sql # 初始化测试数据 └── pom.xml # Maven 依赖这个结构的关键在于sql/目录单独放建表和初始化脚本答辩演示时直接执行schema.sql就能把库建起来比手动在 Navicat 里点半天靠谱得多。mapper/下 XML 和接口分离是 MyBatis 的标准玩法复杂查询写在 XML 里简单 CRUD 用注解也行。2.3 核心表设计五张表撑起整个业务客房管理系统的表不用多但关系要理清。核心是这几张表名作用关键字段room客房信息room_id, room_no, room_type, price, statuscustomer客户信息customer_id, name, phone, id_cardorders订单/预订order_id, customer_id, room_id, check_in, check_out, statusbill账单bill_id, order_id, amount, pay_statusroom_type房型字典type_id, type_name, base_priceroom.status是整套系统的灵魂字段用枚举值表示房态0 空闲、1 已预订、2 已入住、3 清洁中、4 维修。所有业务操作本质上都是在改这个字段改之前必须校验当前状态是否允许这次流转——比如「已入住」的房间不能再被预订这就是后面要讲的状态机校验。3. 核心模块拆解房态流转、订单校验与账单生成3.1 建表脚本把约束写进 DDL 里先看建表约束能交给数据库的就别交给代码这是血泪经验。下面这段是room和orders的核心 DDLCREATE TABLE room ( room_id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(10) NOT NULL UNIQUE COMMENT 房间号如 801, room_type INT NOT NULL COMMENT 房型ID关联 room_type, price DECIMAL(10,2) NOT NULL COMMENT 当前房价, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1已预订 2已入住 3清洁 4维修, floor INT COMMENT 楼层, CONSTRAINT fk_room_type FOREIGN KEY (room_type) REFERENCES room_type(type_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE orders ( order_id INT PRIMARY KEY AUTO_INCREMENT, customer_id INT NOT NULL, room_id INT NOT NULL, check_in DATE NOT NULL, check_out DATE NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待确认 1已确认 2已入住 3已完成 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_order_customer FOREIGN KEY (customer_id) REFERENCES customer(customer_id), CONSTRAINT fk_order_room FOREIGN KEY (room_id) REFERENCES room(room_id), CONSTRAINT chk_date CHECK (check_out check_in) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个细节值得说。room_no加了 UNIQUE房间号重复是低级错误但真有人犯chk_date这个 CHECK 约束保证退房日期晚于入住日期MySQL 8.0.16 之后才真正生效老版本会忽略这点要注意。status用 TINYINT 而不是 VARCHAR省空间且查询快但代价是可读性差所以注释必须写清楚每个值的含义——不然过两周自己都忘了 3 是清洁还是维修。3.2 房态流转一个方法管住所有状态变更房态变更如果散落在各个 Controller 里迟早出乱子。我一般会收口到一个 Service 方法用状态机的方式校验Service public class RoomService { Autowired private RoomMapper roomMapper; // 允许的状态流转当前状态 - 可流转到的目标状态集合 private static final MapInteger, SetInteger TRANSITIONS Map.of( 0, Set.of(1, 2, 4), // 空闲 - 可预订/直接入住/维修 1, Set.of(2, 0), // 已预订 - 入住/取消回空闲 2, Set.of(3), // 已入住 - 退房后清洁 3, Set.of(0), // 清洁完成 - 空闲 4, Set.of(0) // 维修完成 - 空闲 ); Transactional public void changeStatus(Integer roomId, int targetStatus) { Room room roomMapper.selectById(roomId); if (room null) { throw new BizException(房间不存在); } SetInteger allowed TRANSITIONS.getOrDefault(room.getStatus(), Set.of()); if (!allowed.contains(targetStatus)) { throw new BizException( String.format(房态非法流转%d - %d, room.getStatus(), targetStatus)); } roomMapper.updateStatus(roomId, targetStatus); } }这段代码的价值在于把「哪些流转合法」显式定义成一张表而不是写一堆 if-else。TRANSITIONS用Map.of声明读起来一目了然空闲房可以直接入住散客也可以先预订已入住的房只能退房进清洁不能直接回空闲——因为中间必须经过打扫。Transactional保证查状态和改状态在同一个事务里避免并发下两个请求同时把一间房改成不同状态。参数targetStatus传目标状态值方法内部负责校验调用方不用关心规则。3.3 订单校验防止同一间房被重复预订预订环节最容易翻车的是日期重叠。两个订单订同一间房日期有交集就是冲突。SQL 层面这样查!-- OrderMapper.xml查询指定房间在给定日期区间内的有效订单数 -- select idcountConflictOrders resultTypeint SELECT COUNT(*) FROM orders WHERE room_id #{roomId} AND status IN (0, 1, 2) !-- 待确认、已确认、已入住都算占用 -- AND check_in lt; #{checkOut} !-- 已有订单的入住日 新订单退房日 -- AND check_out gt; #{checkIn} !-- 已有订单的退房日 新订单入住日 -- /select日期重叠的判断逻辑是「区间相交」两个区间[a1, a2]和[b1, b2]相交的条件是a1 b2 AND a2 b1。这里用严格小于和大于是因为退房当天房间可以再入住不算冲突。status IN (0,1,2)排除了已取消和已完成的订单这两个状态不占用房间。Service 层拿到冲突数大于 0 就直接拒绝下单并提示具体冲突日期用户体验会好很多。3.4 账单生成退房时一次算清退房时生成账单涉及房费、可能的加时费、押金退还。核心计算逻辑public Bill generateBill(Order order) { Room room roomMapper.selectById(order.getRoomId()); // 按实际入住天数计费退房当天不计 long days ChronoUnit.DAYS.between(order.getCheckIn(), order.getCheckOut()); if (days 0) days 1; // 钟点房或当天退房按 1 天算 BigDecimal roomFee room.getPrice().multiply(BigDecimal.valueOf(days)); Bill bill new Bill(); bill.setOrderId(order.getOrderId()); bill.setAmount(roomFee); bill.setPayStatus(0); // 未支付 billMapper.insert(bill); return bill; }ChronoUnit.DAYS.between算天数比手动减日期靠谱跨月跨年都不会错。days 0的兜底是给当天入住当天退的情况准备的不然账单金额为 0 就闹笑话了。金额用BigDecimal而不是 double这是财务计算的铁律浮点误差在账单上会被放大。4. 跑起来环境准备、建库与启动验证4.1 环境清单与版本对齐动手之前先把版本对齐版本不匹配是新手最常见的翻车点。需要准备JDK 17Spring Boot 3.x 要求、Maven 3.8、MySQL 8.0。如果源码是 Spring Boot 2.xJDK 8 或 11 也能跑看pom.xml里的spring-boot-starter-parent版本决定。MySQL 一定要 8.0 以上前面提到的 CHECK 约束和 utf8mb4 默认排序规则都依赖这个版本。4.2 建库与初始化数据拿到源码后第一步不是急着启动而是先把库建好# 登录 MySQL mysql -u root -p # 创建数据库指定字符集 CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 执行建表和初始化脚本 mysql -u root -p hotel_db sql/schema.sql mysql -u root -p hotel_db sql/data.sql执行完用SHOW TABLES;确认五张表都在再SELECT * FROM room LIMIT 5;看看测试数据有没有进去。如果data.sql报外键错误多半是插入顺序不对——先插room_type和customer再插room和orders被引用的表必须先有数据。4.3 改配置与启动打开application.yml把数据库连接改成自己的spring: datasource: url: jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver server: port: 8080serverTimezoneAsia/Shanghai这个参数别漏不然日期字段可能差 8 小时订单日期对不上会让你怀疑人生。改完执行mvn spring-boot:run或直接跑主类控制台看到Started ... in x seconds就说明起来了。浏览器访问http://localhost:8080能看到房间列表页就算跑通。4.4 验证核心流程跑通不等于跑对按这条链路走一遍新增客户 → 选一间空闲房下单预订 → 房态变「已预订」 → 办理入住 → 房态变「已入住」 → 退房 → 生成账单、房态变「清洁」 → 清洁完成回「空闲」。每一步都去数据库SELECT status FROM room WHERE room_id ?核对状态对得上才算真跑通。中间任何一步状态没变先看 Service 有没有被正确调用再看事务有没有回滚。5. 避坑与排查那些让我返工的坑5.1 房态改了但页面没变现象退房操作后数据库里 status 已经是 3但列表页还显示「已入住」。原因通常是前端缓存了旧数据或者查询接口没加ORDER BY导致分页错乱。解决接口返回前强制刷新前端请求加时间戳参数绕过缓存分页查询统一按room_no排序保证结果稳定。5.2 并发预订导致超卖现象两个人同时订同一间房都成功了。原因先查冲突再插入两步之间没有锁。解决在countConflictOrders查询上加FOR UPDATE行锁或者给orders表加唯一索引兜底同一房间同一入住日唯一让数据库来拦。课设演示并发场景不多但答辩老师爱问这个。5.3 日期差一天现象1 号入住 3 号退房账单算成 3 天。原因用了check_out - check_in 1的算法把退房当天也算进去了。解决退房当天不计费直接用ChronoUnit.DAYS.between不加 1。这个坑我在两个项目里都踩过后来统一封装成工具方法。5.4 外键导致删数据失败现象想删一个客户报外键约束错误。原因该客户还有关联订单。解决这是设计意图不该硬删。业务上做软删除给customer加is_deleted字段查询时过滤真要物理删除先处理关联订单。5.5 中文乱码现象客户姓名存进去变成问号。原因数据库、连接、表三处字符集不一致。解决建库用 utf8mb4连接串加characterEncodingutf8表也确认是 utf8mb4。三处对齐乱码自然消失。6. 二次开发从课设到能用的改造思路拿到这套代码多数人不会止步于演示总想加点东西让它更像回事。我按改造价值排个序从易到难说。第一层是补全基础功能。原项目通常只有房间和订单可以加「员工管理」和「操作日志」两张表员工表记录前台、保洁角色日志表记录每次房态变更的操作人和时间。日志表结构简单log_id, room_id, from_status, to_status, operator, op_time在changeStatus方法里插一条就行。这一层改动小、收益大答辩时能讲「可追溯」。第二层是优化查询。房间列表页如果数据量上来SELECT *加全表扫描会明显变慢。给room(status)和orders(room_id, check_in, check_out)加联合索引查询计划立刻不一样。可以用EXPLAIN对比加索引前后的type字段从ALL变成ref就是效果。这个演示在答辩里很加分。第三层是引入缓存或定时任务。房态是高频读低频写的数据用 Redis 缓存房间列表写操作时删缓存能明显降低数据库压力。再配一个定时任务每天凌晨把「清洁中」超过 24 小时的房间自动置为「空闲」避免保洁忘了点完成导致房间一直不可用。定时任务用 Spring 的Scheduled就够cron 表达式0 0 2 * * ?表示每天凌晨 2 点执行。改造完怎么验证没改坏我的习惯是保留一份原始schema.sql和data.sql每次改完重建库跑一遍完整流程再对比关键表的记录数和状态分布。另外给核心 Service 方法补几个单元测试尤其是changeStatus的非法流转用例改状态机时能第一时间发现回归。说个我自己的教训早期做这类系统我总想一步到位把表设计得很全结果字段加了一堆业务逻辑根本用不上反而让关联查询变复杂。后来我改成「先跑通最小闭环再按需加表」每次加字段前先问自己「哪个页面或哪个操作会用到它」用不上就不加。从那以后我每次动表结构前都强制走一遍这个自问省了不少返工。希望这套拆解能帮你把课设顺利跑起来也留出足够的改造空间。本文还有配套的精品资源点击获取