简介面向计算机相关专业学生的宾馆管理系统课程设计资料包是一套可直接参考的完整项目方案。系统以宾馆日常运营为业务背景围绕客房、客户、预订、入住、退房等核心数据对象实现了房间信息维护、客户登记、订单管理、结算统计等典型功能模块业务逻辑连贯且贴近实际工程场景。该方案曾获98分源码采用Java编写数据库脚本预置了多张关联数据表及初始数据配套报告则完整记录了需求分析、E-R设计、关系模式、功能结构、界面截图与测试过程能够帮助学习者快速把握课程设计从设计到成文的完整脉络。压缩包大小约23.53MB主要文件类型包括Java源文件、SQL脚本、Word版报告等整体结构清晰、便于按文档索引查阅。目前已有88人学习下载尤其适合计算机专业本科生用于期末大作业、课程设计答辩准备或数据库项目实践训练既可对照实现功能也可借鉴规范化文档写法具有较高的完成度和实用价值。1. 数据库课程设计选宾馆管理系统一门课就把增删改查、事务、报表全练了数据库课程设计最常翻车的并不是代码本身而是“选了个需要造一堆假数据的题材结果交上去的老是拼凑出来的功能”。宾馆管理系统之所以常年霸榜是因为它业务闭环清晰订房、入住、退房、结账、房态统计每一步都天然对应数据库课程里必须考的知识点——增删改查、实体关系建模、事务边界、视图和触发器。这套题目拿 95 分以上的关键也在于此功能量不需要多但要让老师看到你“把数据真正管起来了”。这份笔记面向正在做课程设计、需要尽快交源码和报告的人。我会从表结构设计、JDBC 核心业务实现、报告评分点、答辩演示到常见翻车现场按一套可以直接落地的路径写透。2. 先做数据库设计而不是先写代码ER图、关系模式与表的落法2.1 需求怎么拆把“宾馆”拆成五个业务对象拿到题目先别急着建表。课程设计的评分重点往往不是界面多漂亮而是“你的数据模型能不能说清楚”。宾馆管理系统常见的实体有五个房型、房间、顾客、预订单、账单。房型和房间是一对多房间和预订单是一对多顾客和预订单是一对多预订单和账单是一对一。把这些先画成简单的 E-R 图等于把整篇报告的第二章“需求分析”提前写完了。拆需求的时候有一个容易被忽略的点预订单本身是有状态的。很多初学者把“预订”和“入住”当成两张表结果退房的时候数据对不上报表统计更是无从下手。实际上预订单表里加一个状态字段就够了booked已预订、in_use入住中、checked_out已退房、cancelled已取消。这样从预订到退房的整条生命周期都在一行记录里流转写代码和写报告都省事。另外别把“顾客信息”和“预订记录”合并。有的同学为了省事把顾客姓名、电话直接塞进预订表最后老师一问“同一个顾客第二次入住怎么查出他的历史记录”就答不上来。顾客单独建表证件号做唯一键这是宾馆管理系统里最能体现规范化设计的地方。2.2 关系模式设计三张核心表把预订状态机立住关系模式是报告的加分枢纽。如果你只交一张“总表”评语大概率是“未体现关系数据库设计”。按下述方案关系模式可以写成四句话房型房型编号房型名称基准价格可住人数房间房间编号房型编号楼层房态顾客顾客编号姓名证件号电话预订单订单编号顾客编号房间编号状态入住时间退房时间账单账单编号订单编号金额创建时间加下划线的主键带(房型编号)之类的是外键。把这套关系模式写进报告“逻辑结构设计”一节已经比多数提交版本高一个档次。这里有一个加分细节预订表里的“入住时间”“退房时间”用 DATETIME不要拆成日期和时间两个字段。一来聚合函数可以直接算入住时长二来触发器生成账单时可以省掉一次字符串拼接。我自己见过不少代码把日期和时间分开存结果算房费时先拼字符串再转类型多做一步还容易乱。2.3 用 SQL 把表建出来字段类型、默认值与索引的取舍表结构按下面的 SQL 在 MySQL 8.0 里可以一次跑通。字符集统一用utf8mb4这是中文乱码最常见的一道防线。CREATE DATABASE IF NOT EXISTS hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hotel_db; CREATE TABLE room_type ( id INT AUTO_INCREMENT PRIMARY KEY, type_name VARCHAR(20) NOT NULL UNIQUE COMMENT 房型名称, base_price DECIMAL(10,2) NOT NULL COMMENT 基准价格, capacity INT NOT NULL DEFAULT 2 COMMENT 可住人数 ) ENGINEInnoDB; CREATE TABLE room ( id INT AUTO_INCREMENT PRIMARY KEY, room_number VARCHAR(10) NOT NULL UNIQUE COMMENT 门牌号, type_id INT NOT NULL, floor INT NOT NULL COMMENT 入住楼层, status ENUM(available,booked,occupied,maintenance) NOT NULL DEFAULT available COMMENT 房间状态, CONSTRAINT fk_room_type FOREIGN KEY (type_id) REFERENCES room_type(id) ) ENGINEInnoDB; CREATE TABLE customer ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(30) NOT NULL, id_card VARCHAR(18) NOT NULL UNIQUE COMMENT 证件号一般用身份证, phone VARCHAR(20) ) ENGINEInnoDB; CREATE TABLE booking ( id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, room_id INT NOT NULL, status ENUM(booked,in_use,checked_out,cancelled) NOT NULL DEFAULT booked COMMENT 预订单状态, check_in_time DATETIME, check_out_time DATETIME, CONSTRAINT fk_booking_customer FOREIGN KEY (customer_id) REFERENCES customer(id), CONSTRAINT fk_booking_room FOREIGN KEY (room_id) REFERENCES room(id) ) ENGINEInnoDB;字段类型的选择有三个值得说的地方。第一DECIMAL(10,2)而不是FLOAT金额计算不出现浮点尾巴。第二ENUM存状态字段写代码时不容易传错字符串报告里也能解释成“用约束保证数据合法性”。第三room_number加UNIQUE这是业务上“一间房不能有两个房号”的底线约束也方便后面做演示数据的可读性。索引建议在booking表上加一个组合索引(room_id, status)。这是查询“某个房间现在是否可订”“哪些房间正在入住”时的高频路径后面讲 EXPLAIN 验证时会再提到它。而customer.id_card的唯一索引天然就是索引不需要额外建。3. 用 Java JDBC 实现核心业务从“能连库”到“不丢订单”3.1 一套连库配置JDBC 连接 MySQL 的最小工程课程设计的常见技术栈是 Java MySQL用 JDBC 直连不引入 MyBatis反而容易在答辩时讲清楚每一条 SQL 的行为。先用一个工具类把连接管起来import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DbUtil { private static final String URL jdbc:mysql://localhost:3306/hotel_db ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }参数说明里最容易被漏掉的是characterEncodingutf8。漏掉它你会在插入中文顾客姓名时看到一堆问号而且这个 bug 是后面所有功能跑起来之后才出现的排查时绕一圈还找不到根源。serverTimezoneAsia/Shanghai是 MySQL 8.0 与 JDBC 驱动的时区对齐不加会在连库时直接抛异常。端口没改过的机器用 3306改了的话记得同步。3.2 预订到入住的状态流转事务边界写在哪宾馆管理系统最容易出现的数据问题是两段代码分别改了room.status和booking.status房间改成“入住中”了订单状态还是“已预订”后来统计入住率时永远少一行。正确的做法是让两个状态在同一个事务里更新。以“办理入住”为例核心代码如下public boolean checkIn(int bookingId, int roomId) throws SQLException { String updateRoomSql UPDATE room SET status occupied WHERE id ? AND status booked; String updateBookingSql UPDATE booking SET status in_use, check_in_time NOW() WHERE id ? AND status booked; Connection conn DbUtil.getConnection(); conn.setAutoCommit(false); try { PreparedStatement psRoom conn.prepareStatement(updateRoomSql); psRoom.setInt(1, roomId); int roomRows psRoom.executeUpdate(); if (roomRows ! 1) { conn.rollback(); return false; // 房间状态不是 booked说明房态已被改动 } PreparedStatement psBooking conn.prepareStatement(updateBookingSql); psBooking.setInt(1, bookingId); int bookingRows psBooking.executeUpdate(); if (bookingRows ! 1) { conn.rollback(); return false; } conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); } }这段代码里最关键的地方是WHERE ... AND status booked。同一时刻只有一个请求能把booked改成occupied天然挡住了“两拨客人同时入住同一间房”的并发问题。如果改成先SELECT查状态、再用UPDATE改两步之间有窗口期并发一上来就会超卖。学会在面试时把这个“条件更新代替先查后改”的思路讲出来比堆十个功能点都管用。3.3 避免 SQL 注入与参数字段对不齐PreparedStatement 的使用习惯课程设计源码的常见扣分点之一是字符串拼接 SQLString sql SELECT * FROM booking WHERE customer_id customerId;这种写法既容易注入也经不起留心眼的老师一句“如果我传一个1 OR 11你会怎样”。换成PreparedStatement只需要改半段代码public ListBooking listByCustomer(int customerId) throws SQLException { String sql SELECT id, room_id, status, check_in_time, check_out_time FROM booking WHERE customer_id ? ORDER BY check_in_time DESC; ListBooking result new ArrayList(); try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, customerId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Booking b new Booking(); b.setId(rs.getInt(id)); b.setRoomId(rs.getInt(room_id)); b.setStatus(rs.getString(status)); b.setCheckInTime(rs.getTimestamp(check_in_time)); b.setCheckOutTime(rs.getTimestamp(check_out_time)); result.add(b); } } } return result; }两个容易踩的细节第一setInt的下标从 1 开始不是 0写多参数 SQL 时经常有人在这个地方对不齐第二try-with-resources会自动关闭Connection、PreparedStatement、ResultSet顺序是反着的别手动关Connection又去关ResultSet。课程设计不要求性能优化但连接用完就关是好习惯答辩时提一句“我保证了连接生命周期受控”效果比空谈设计模式实在。4. 报告与源码怎么准备才够“95 分”评分点是写出来的4.1 课程设计报告的结构与评分点分布不少同学把源码写完报告当晚拼凑结果代码 90 分、报告 60 分总评被拉下来。从老师评分角度看一份数据库课程设计报告通常按以下比例给分报告章节约占分常见丢分点需求分析10%只写“系统有登录和增删改查”没有业务流程描述概念结构设计E-R 图20%实体间缺关系、不标主外键、不写联系类型逻辑结构设计关系模式15%只有表名没有主外键和约束说明数据字典10%字段名和代码对不上、类型不一致功能实现说明20%只说“实现了”不贴关键 SQL 和代码截图测试与运行15%没有测试数据和测试用例描述总结与心得10%一句话带过这里做个算术你就能明白报告里的概念结构和逻辑结构加起来占 35 分比功能实现的 20 分还高。也就是说即便你的界面朴素一点只要数据模型画得清楚、说得明白及格线已经过了。4.2 数据字典这一节要写细字段名、类型、约束、说明一个都不能少数据字典是最容易被抄模板、也是最好写出区分度的部分。每张表现成的写法是表名、字段名、数据类型、是否为空、默认值、约束说明六列一张表像下面这样描述booking表字段名类型可空默认值说明idINT否自增订单编号主键customer_idINT否无顾客编号外键引用 customer.idroom_idINT否无房间编号外键引用 room.idstatusENUM否booked状态流转booked/in_use/checked_out/cancelledcheck_in_timeDATETIME是NULL实际入住时间check_out_timeDATETIME是NULL实际退房时间写数据字典有一个隐藏的要求字段名要和源码里的实体类字段、JSP/前端表单里的 name 对齐。老师拿到报告抽查代码时最喜欢做的一件事就是拿着数据字典去源码里对比字段。如果你报告里写customer_id代码里写custId当场就会被扣“文档与实现不一致”的分数。这个细节不需要任何技巧就看你有没有认真检查一遍。4.3 答辩演示脚本按角色演示比按功能演示更出效果答辩时间通常只有五分钟。按“游客入住流程”串起来演示比按“登录、查询、删除、退出”这种菜单顺序演示要顺得多。我会把演示脚本写成四条场景场景一前台登记一位新顾客录入姓名、证件号、电话系统自动创建顾客档案场景二为顾客预订一间 5 楼的标间此时房间状态由 available 变为 booked场景三顾客到店办理入住演示订单状态变为 in_use房间状态变为 occupied场景四退房结账展示账单自动生成金额和入住时长对得上订单状态变为 checked_out。这个脚本的好处是把状态机完整走了一遍。老师看到的不再是孤立的增删改查而是一条连贯的业务链路。演示时还要准备好“看完一条数据后打开 SQL 或 Navicat 秀一下对应的行”这能证明数据是真实落库的不是写死在界面上。5. 常见问题与避坑清单从选题到答辩五个真实翻车现场5.1 中文乱码连接串里少写了 characterEncoding现象界面输入中文姓名保存到数据库变成“”查询回来也是问号但在 Navicat 里手动插入的中文却能正常显示。原因Java 连接 MySQL 时驱动默认使用的字符集和表字符集不一致。表用了utf8mb4但连接串里没有声明characterEncodingutf8导致字节流在传输过程中被错误解码。解决在 JDBC URL 中补上characterEncodingutf8并确保数据库连接、表结构、页面编码三处统一为 UTF-8。如果用的是 MySQL 5.x还要检查连接串里是否漏了useUnicodetrue。这个问题最坑的是它不会报错只在数据入库时悄悄损坏。5.2 预订状态不闭环今天该退房的订单查不出来现象退房功能做完了但“今日应退房列表”一直只有一部分数据统计报表里的入住率对不上。原因退房时只改了room.status为 available没有同步把booking.status改成 checked_out或者反过来了。订单状态和房间状态失去同步后续所有基于状态的查询都会出错。解决把状态流转收敛到一个事务里就像第 3.2 节那样一条路径同时更新两张表更进一步可以在booking上加一个“状态机”约束思路只允许booked - in_use - checked_out取消只在booked状态发生用应用层代码保证不依赖数据库触发器。写报告时把这个过程描述成“事务边界设计”是加分说法。5.3 外键约束删不掉删除房型时被约束拦截现象想删掉一个已有房间引用的房型删除按钮点击后报错页面直接抛异常。原因room.type_id外键指向room_type.id数据库在ON DELETE默认行为下拒绝删除被引用的父表记录。很多课程设计报告里根本没提这个异常答辩被问到时支支吾吾。解决先处理子表再删除父表。业务上正确顺序是下架相关房间、删除或转移房间记录最后删除房型。也可以在建表时把外键声明为ON DELETE RESTRICT然后在报告里专门讲一小段“为什么选择禁止级联删除”——因为宾馆数据需要保留入住历史防止误删账目。这个回答反而是加分项。5.4 房价用 float 存储账单金额对不上现象房价是 128.00入住两晚应收 256.00报表里却显示 255.9999998。原因FLOAT/DOUBLE是浮点数二进制无法精确表示部分十进制小数进行乘法聚合时会累积误差。金额字段用了 float是一切“钱数不对”的源头。解决表结构中的金额字段一律改为DECIMAL(10,2)Java 侧对应使用BigDecimal而不是double。门票类似的还有“平均房价”这种统计指标在 SQL 里用CAST(SUM(amount) / COUNT(*) AS DECIMAL(10,2))收口。把这一点写进报告“设计约束”小节说明你考虑了浮点精度问题属于一眼就能发现的细节分。5.5 MySQL 8.0 连接报错 caching_sha2_password现象用 JDBC 连接 MySQL 8.0 时报Unable to load authentication plugin caching_sha2_password换了驱动版本也没解决。原因MySQL 8.0 默认认证插件是caching_sha2_password旧版 JDBC 驱动不认识。课程设计的机器上常装的是旧驱动或者从网上下了一个过时 jar。解决优先把mysql-connector-java换成 8.x 版本并在连接串里继续保持SSL关闭。如果实验环境不允许动驱动可以用管理员账号执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;把认证插件退回去但这句话不用写进报告答辩时提“我用的是新版驱动兼容 8.0 认证方式”就够了。6. 从“能跑”到“95 分”三个加分项和一套验收演示6.1 用 EXPLAIN 证明你的索引优化有效报告里写了索引老师不一定会信。最直接的验证方式是执行EXPLAIN SELECT * FROM booking WHERE room_id 1 AND status in_use;把执行计划截图贴进报告type显示ref、possible_keys命中了组合索引、key不是 NULL这几行就是最硬的证据。常见的翻车是索引建了但 SQL 写法让索引失效比如在WHERE里对索引列做函数运算WHERE DATE(check_in_time) CURDATE()这会让索引失效。改成WHERE check_in_time CURDATE() AND check_in_time CURDATE() INTERVAL 1 DAY索引才能生效。把这个前后对比写进测试小节效果远好于一句“我建了索引”。6.2 触发器自动生成账单让报告多一个设计亮点课程设计不用触发器也能及格但用了明显是个加分项。以“退房时自动计算房费”为例MySQL 8.0 里可以这样写CREATE TRIGGER trg_booking_after_update AFTER UPDATE ON booking FOR EACH ROW BEGIN IF NEW.status checked_out AND OLD.status checked_out THEN INSERT INTO billing(booking_id, amount, created_at) SELECT NEW.id, TIMESTAMPDIFF(HOUR, NEW.check_in_time, NEW.check_out_time) * rt.base_price / 24, NOW() FROM room r JOIN room_type rt ON r.type_id rt.id WHERE r.id NEW.room_id; END IF; END;这段逻辑说清楚了三件事数据库中billing表的金额来源、避免应用层漏算、状态变更与账单生成的一致性。要注意不同数据库的触发器语法差别很大SQLite 写AFTER UPDATE OF statusSQL Server 用INSERTED表如果课程设计指定了别的数据库提前确认语法。另外触发器建议单独写在报告“高级设计”一节不要在答辩现场临时展示建触发器的过程。6.3 验收演示套路从菜单演示升级为数据流演示最后一次自测不要按菜单点要按数据流走开两个终端窗口一边开 Java 程序界面另一边开 Navicat 或 MySQL 命令行。每执行一步操作就切到数据库端查一次对应表的记录变化。查room、booking、billing三张表的行能干净地看到状态从booked到in_use再到checked_out的流转账单金额也对应生成这个演示的说服力是最强的。再准备一个“后悔药”演示取消一笔状态还停留在booked的订单把房间恢复为 available然后指出“已入住的订单不允许取消在业务代码中已经限制了状态机的合法跳转”。这句话不大但能显示你思考过异常流程。我做课程设计时最深的教训是把八成时间花在数据库设计和状态一致性上而不是拼命加功能按钮。能跑通的页面一抓一大把状态不乱的系统才是少数。按这个思路把表设计、事务、报告逻辑串起来95 分以上不是玄学是每一步都有据可查的结果。希望帮到你。本文还有配套的精品资源点击获取