酒店管理系统课设:Python+PyQt5+MySQL数据库教学实践

发布时间:2026/10/9 16:09:56
酒店管理系统课设:Python+PyQt5+MySQL数据库教学实践
简介本资源是一套完整的数据库系统课程设计实践项目面向计算机专业本科生及Python数据库开发初学者聚焦酒店管理场景下的GUI应用开发与MySQL数据建模实战。项目基于PythonPyQt5MySQL技术栈构建涵盖用户登录、员工管理、客房预订与退房、客户信息维护等核心业务模块并配套完整可运行源码、MySQL建库脚本hotelManagement.sql、E-R图与功能结构图jpg/png、两份PDF课程设计报告及UI界面文件.ui助学习者掌握数据库设计、前后端交互与桌面应用部署全流程。压缩包共61个文件含18个核心Python源码、8个PyQt UI界面文件、4个SQL/配置类文件及2个PDF报告总大小8.28MB结构清晰、模块解耦度高开箱即用。已有209人学习下载适合课设参考、期末项目复现或GUI数据库综合能力训练。1. 为什么一个“能直接运行”的酒店管理系统课设反而让90%的学生在答辩前3天还在改连接报错这不是一个炫技的全栈项目而是一套专为数据库系统课程设计打磨的闭环教学载体它用 Python PyQt5 构建可交互 GUI 界面后端直连 MySQL 完成增删改查、事务控制、视图封装与索引优化验证所有逻辑都锚定在《数据库系统概论》核心章节——关系代数、范式理论、并发控制、SQL 嵌套查询。它不追求高并发或微服务架构但要求你亲手配置mysql-connector-python的字符集、理解QSqlDatabase.addDatabase()的线程安全边界、把 E-R 图里的“客户-预订-房间-账单”四元关系一比一落地为带外键约束的五张表customers,rooms,bookings,staff,billing并用 PyQt5 的QTableViewQSqlQueryModel实现动态数据绑定。适合数据库原理刚学完第三章、正卡在“知道 SQL 语法但不会组织真实业务逻辑”的本科生也适合需要快速交付一份有界面、有数据流、有事务回滚演示、有课程报告配套的指导教师——因为它的源码里埋了 7 处可答辩提问点比如“为什么 booking 表的 room_id 不设 ON DELETE CASCADE”报告模板已预留实验截图占位符和范式分析填空栏。2. 从零初始化本地环境搭建与数据库建模的硬性前提2.1 环境版本锁死为什么必须用 Python 3.8–3.10 PyQt5 5.15.9 MySQL 8.0.33这不是兼容性妥协而是避坑刚需。PyQt5 5.15.9 是最后一个完全支持 Qt Designer.ui文件无损加载且与 Python 3.10 兼容的稳定版后续 5.15.10 引入了sip模块签名变更导致uic.loadUi()在部分 Windows 环境静默失败MySQL 8.0.33 则是第一个默认启用caching_sha2_password认证插件但仍向后兼容mysql_native_password的版本——而mysql-connector-python8.0.x 系列对前者支持不稳定学生常因认证插件不匹配报Authentication plugin caching_sha2_password cannot be loaded。Python 3.8–3.10 是平衡点3.7 太旧缺typing.Literal导致部分类型提示报错3.11 又因 CPython ABI 变更使预编译的 PyQt5 wheel 安装失败。# 推荐命令用 conda 创建隔离环境比 pip 更稳 conda create -n hotel_db python3.9 conda activate hotel_db pip install pyqt55.15.9 mysql-connector-python8.0.33提示若用 pip 安装 PyQt5 报ERROR: Could not find a version that satisfies the requirement pyqt55.15.9请先升级 pippython -m pip install --upgrade pip再指定清华源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ pyqt55.15.92.2 MySQL 数据库初始化执行建表脚本前必须做的三件事课设包里的hotel_db.sql脚本不是拿来即跑的“黑匣子”。它依赖三个前置条件缺一不可创建专用用户并授权非 rootCREATE USER hotel_applocalhost IDENTIFIED BY Hotel2024; GRANT SELECT, INSERT, UPDATE, DELETE ON hotel_db.* TO hotel_applocalhost; FLUSH PRIVILEGES;为什么不用 root课程设计需体现最小权限原则——billing表含金额字段若用 root 连接一旦 GUI 代码存在 SQL 注入漏洞如未用参数化查询拼接 WHERE 条件攻击者可直接DROP TABLE。用专用用户即使出错也仅限本库。显式设置数据库字符集为 utf8mb4CREATE DATABASE hotel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hotel_db;utf8mb4 而非 utf8MySQL 的utf8实际是utf8mb3不支持 emoji 和部分生僻汉字如“䶮”。酒店客户姓名、备注栏若含这些字插入时会静默截断。utf8mb4才是真正的 UTF-8。手动创建staff表的position枚举字段脚本里常漏写ALTER TABLE staff MODIFY COLUMN position ENUM(前台, 客房部, 餐饮部, 安保, 经理) DEFAULT 前台;为什么 ENUM 而不用 VARCHAR课程设计需体现数据完整性约束。ENUM 强制取值范围避免“position清洁工”这种业务逻辑外的脏数据且比 CHECK 约束在 MySQL 5.7 上性能更优。3. GUI 与数据库的双向绑定PyQt5 如何让表格实时响应 MySQL 变更3.1 用QSqlQueryModel替代手写QStandardItemModel少写 200 行代码的关键选择很多学生用QTableView时习惯先cursor.fetchall()拿数据再逐行model.appendRow([QStandardItem(x) for x in row])。这会导致两个致命问题数据不同步MySQL 里其他客户端改了数据GUI 不刷新、内存泄漏大量QStandardItem对象未被回收。正确做法是让 Qt 自己管数据流from PyQt5.QtSql import QSqlDatabase, QSqlQueryModel from PyQt5.QtWidgets import QTableView # 1. 初始化数据库连接全局单例避免重复连接 def init_db(): db QSqlDatabase.addDatabase(QMYSQL) db.setHostName(localhost) db.setDatabaseName(hotel_db) db.setUserName(hotel_app) db.setPassword(Hotel2024) # 关键设置字符集否则中文显示为 ?? db.setConnectOptions(CHARSETutf8mb4) if not db.open(): raise ConnectionError(fDB connect failed: {db.lastError().text()}) return db # 2. 创建模型并绑定到视图 class BookingModel(QSqlQueryModel): def __init__(self, parentNone): super().__init__(parent) self.refresh() def refresh(self): # 直接执行 SQLQt 自动缓存结果集 self.setQuery(SELECT b.id, c.name, r.room_no, b.check_in, b.check_out, b.status FROM bookings b JOIN customers c ON b.customer_id c.id JOIN rooms r ON b.room_id r.id ORDER BY b.check_in DESC) # 3. 在主窗口中使用 self.booking_view QTableView() self.booking_model BookingModel() self.booking_view.setModel(self.booking_model)逻辑说明QSqlQueryModel内部维护一个QSqlQuery对象调用setQuery()时自动执行 SQL 并拉取全部结果到内存注意不是流式读取大数据量慎用。refresh()方法可被按钮点击事件触发实现“点击刷新”功能。参数说明setConnectOptions(CHARSETutf8mb4)是中文不乱码的后悔药——若漏写QSqlQueryModel会以 latin1 解析 MySQL 返回的 utf8mb4 字节流导致c.name显示为李国隆。3.2 实现“双击编辑房间状态”QSqlRelationalTableModel的局限与绕过方案课设需求常要求在bookings表格中双击status列弹出下拉框选“已入住/已退房/已取消”。理想方案是QSqlRelationalTableModel但它有个硬伤不支持 JOIN 查询的外键关联如bookings.status是字符串非外键。所以必须手动实现from PyQt5.QtCore import Qt, QEvent from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate class StatusDelegate(QStyledItemDelegate): def __init__(self, parentNone): super().__init__(parent) self.status_options [已入住, 已退房, 已取消, 待确认] def createEditor(self, parent, option, index): editor QComboBox(parent) editor.addItems(self.status_options) return editor def setEditorData(self, editor, index): value index.model().data(index, Qt.EditRole) editor.setCurrentText(value) def setModelData(self, editor, model, index): value editor.currentText() # 关键不直接更新 model而是发 SQL 更新数据库 query QSqlQuery() booking_id model.data(model.index(index.row(), 0), Qt.DisplayRole) # 获取 id 列值 query.prepare(UPDATE bookings SET status ? WHERE id ?) query.addBindValue(value) query.addBindValue(booking_id) if not query.exec_(): print(Update failed:, query.lastError().text()) # 绑定到视图 self.booking_view.setItemDelegateForColumn(5, StatusDelegate()) # 第6列是 status为什么不用 model.setData()QSqlQueryModel是只读模型调用setData()会返回False。必须绕过模型用QSqlQuery直接执行 UPDATE并在更新后调用self.booking_model.refresh()同步界面。4. 避坑那些让课设答辩前夜崩溃的 5 个高频错误4.1 现象PyQt5 界面启动后空白终端无报错Process finished with exit code 0原因QApplication实例未被sys.exit()捕获或exec_()被意外跳过。常见于将 GUI 类写在if __name__ __main__:外部且main()函数未调用app.exec_()。解决严格按以下结构写入口if __name__ __main__: import sys app QApplication(sys.argv) # 必须在最前 window MainWindow() # 主窗口类 window.show() # 必须 show() 才可见 sys.exit(app.exec_()) # 必须用 sys.exit 包裹4.2 现象MySQL 连接报Cant connect to MySQL server on localhost (10061)原因MySQL 服务未启动或端口被占用默认 3306。学生常误以为安装完 MySQL 就自动运行实则需手动启动服务。解决Windows 下打开“服务”管理器找到MySQL80右键“启动”macOS 用brew services start mysqlLinux 用sudo systemctl start mysqld。验证mysql -u hotel_app -pHotel2024 -h 127.0.0.1 -P 3306能登录即通。4.3 现象添加新客户时报Incorrect string value: \xF0\x9F\x98\x80 for column name原因表字段字符集是utf8即utf8mb3但插入了 4 字节 emoji如 。解决修改字段字符集比重建表更安全ALTER TABLE customers MODIFY COLUMN name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4.4 现象QTableView中日期列显示为2024-05-20T00:00:00而非2024-05-20原因MySQL 的DATE类型被 Qt 映射为QDateTime默认格式含时间。解决重写data()方法定制显示def data(self, index, roleQt.DisplayRole): if role Qt.DisplayRole and index.column() in [3, 4]: # check_in, check_out 列 val super().data(index, Qt.DisplayRole) if isinstance(val, QDateTime): return val.toString(yyyy-MM-dd) return super().data(index, role)4.5 现象点击“生成账单”按钮后程序卡死CPU 占用 100%原因在 GUI 线程中执行耗时 SQL如SELECT * FROM billing WHERE ...加复杂 JOIN阻塞事件循环。解决用QThread将查询移至后台课设简化版可用QTimer.singleShot(0, self.run_billing_query)模拟异步但正式答辩建议用线程class BillingWorker(QObject): finished pyqtSignal(list) def run(self): query QSqlQuery() query.exec_(SELECT ... FROM billing JOIN ...) # 耗时查询 results [] while query.next(): results.append([query.value(i) for i in range(query.record().count())]) self.finished.emit(results) # 在主窗口中 def generate_bill(self): self.thread QThread() self.worker BillingWorker() self.worker.moveToThread(self.thread) self.worker.finished.connect(self.on_bill_generated) self.thread.started.connect(self.worker.run) self.thread.start()5. 事务与并发控制在课设里演示 ACID 的 3 个可答辩级操作5.1 “预订房间”操作必须包含事务为什么INSERT INTO bookings和UPDATE rooms SET status已预订要捆在一起这是课程设计里最该深挖的 ACID 演示点。假设不加事务步骤1INSERT INTO bookings (...) VALUES (...);成功步骤2网络抖动导致UPDATE rooms SET status已预订 WHERE id101;失败→ 结果客户以为订到了房但房间状态仍是“空闲”引发超卖。正确实现在BookingManager类中def create_booking(self, customer_id, room_id, check_in, check_out): query QSqlQuery() # 开启事务 if not query.exec_(START TRANSACTION): return False try: # 1. 插入预订记录 query.prepare(INSERT INTO bookings (customer_id, room_id, check_in, check_out, status) VALUES (?, ?, ?, ?, 待确认)) query.addBindValue(customer_id) query.addBindValue(room_id) query.addBindValue(check_in) query.addBindValue(check_out) if not query.exec_(): raise Exception(Insert booking failed) # 2. 更新房间状态 query.prepare(UPDATE rooms SET status 已预订 WHERE id ? AND status 空闲) query.addBindValue(room_id) if not query.exec_(): # 检查是否因房间已被抢而更新失败 if query.numRowsAffected() 0: raise Exception(Room is not available) else: raise Exception(Update room status failed) # 提交事务 query.exec_(COMMIT) return True except Exception as e: query.exec_(ROLLBACK) # 关键异常时回滚 print(Transaction rolled back:, str(e)) return False答辩可提问点为什么UPDATE rooms的 WHERE 条件加了AND status 空闲答这是乐观锁思想——避免 A、B 两人同时点“预订 101 房”A 先更新成功B 的 UPDATE 因条件不满足此时 status 已是‘已预订’影响行为 0 行从而感知冲突并提示“房间已被预订”。5.2 用SELECT ... FOR UPDATE演示悲观锁当“结账”操作需读取并修改billing表时“结账”流程读取当前账单金额 → 计算折扣 → 更新总金额 → 生成支付流水。若不加锁可能 A、B 同时读到amount500A 加discount50得450B 加discount30得470最终 B 的更新覆盖 A丢失 50 元折扣。课设中可演示的最小化 SQLdef checkout(self, booking_id): query QSqlQuery() # 用 SELECT ... FOR UPDATE 锁定该预订对应的账单行 query.prepare(SELECT id, amount FROM billing WHERE booking_id ? FOR UPDATE) query.addBindValue(booking_id) if not query.exec_() or not query.next(): return False bill_id query.value(0) current_amount query.value(1) # 计算新金额此处简化为减 100 new_amount current_amount - 100 # 更新因前面已锁此处更新安全 query.prepare(UPDATE billing SET amount ?, status 已结清 WHERE id ?) query.addBindValue(new_amount) query.addBindValue(bill_id) return query.exec_()注意FOR UPDATE只在事务内有效且需 InnoDB 引擎MyISAM 不支持。课设建表时务必用ENGINEInnoDB。5.3 视图封装敏感逻辑创建v_active_bookings视图隐藏已取消订单这是范式理论与安全实践的结合点。业务上“今日活跃预订”报表不应显示status已取消的记录但若每次查询都写WHERE status ! 已取消易遗漏。用视图封装CREATE VIEW v_active_bookings AS SELECT b.id, c.name AS customer_name, r.room_no, b.check_in, b.check_out FROM bookings b JOIN customers c ON b.customer_id c.id JOIN rooms r ON b.room_id r.id WHERE b.status IN (待确认, 已入住, 已退房);PyQt5 中调用# 直接查视图无需 WHERE 条件 self.active_model QSqlQueryModel() self.active_model.setQuery(SELECT * FROM v_active_bookings ORDER BY check_in)答辩价值视图实现了逻辑与物理的分离——底层bookings表完整保留所有状态上层应用通过视图获得“业务视角”的数据切片符合第三范式消除冗余保证数据一致性。6. 课程设计报告撰写与答辩技巧让老师一眼看到你的数据库思维6.1 报告里必须出现的 4 张核心图表附生成方法课程设计报告不是代码说明书而是数据库工程能力的证据链。以下四张图缺一不可且必须是你亲手生成的图表类型生成方法为什么必须放E-R 图用 draw.io 或 PowerDesigner 画实体间连线标注“1对多”“多对多”并注明弱实体如billing依赖bookings证明你理解业务语义建模而非照搬脚本范式分析表表格三列表名/当前范式/违反范式的问题及修正。例如bookings表当前1NF/问题room_type 与 room_no 存在传递依赖/修正拆出 rooms 表用 room_id 外键关联直接对应教材第 6 章展示理论落地能力索引效果对比图用 MySQLEXPLAIN命令对比加索引前后EXPLAIN SELECT * FROM bookings WHERE customer_id 100;截图typeALL→ref、rows1000→5字段证明你懂性能优化不是只会建表事务时序图用 PlantUML 画两个用户并发预订同一房间的时序标出SELECT ... FOR UPDATE锁定时刻、UPDATE成功/失败分支、ROLLBACK节点展示对并发控制的具象理解PlantUML 示例复制进 https://www.plantuml.com/plantuml/ 渲染startuml title 并发预订房间时序图 actor UserA actor UserB participant DB UserA - DB: START TRANSACTION UserB - DB: START TRANSACTION UserA - DB: SELECT * FROM rooms WHERE id101 FOR UPDATE UserB - DB: SELECT * FROM rooms WHERE id101 FOR UPDATE DB -- UserA: 返回房间信息 DB -- UserB: 阻塞等待 UserA - DB: UPDATE rooms SET status已预订 UserA - DB: COMMIT DB -- UserB: 解锁返回房间信息 UserB - DB: UPDATE rooms SET status已预订 (失败rows0) UserB - DB: ROLLBACK enduml6.2 答辩时老师必问的 3 个问题及应答策略“为什么bookings表的check_in和check_out用DATE而不用DATETIME”→答“因酒店业务中入住/退房操作均按日结算精确到秒无业务意义且DATE类型存储空间3 字节小于DATETIME5 字节符合数据库设计的‘够用就好’原则。若未来需支持钟点房再升级为DATETIME并增加check_in_time字段。”“staff表的salary字段为什么用DECIMAL(10,2)而不用FLOAT”→答“FLOAT存在二进制精度丢失如 0.10.2≠0.3而工资计算必须精确。DECIMAL(10,2)表示最多 10 位数字小数点后 2 位能准确表示 99999999.99 元且 MySQL 对DECIMAL的运算结果是确定性的。”“如果要支持微信支付billing表需要增加哪些字段是否要新建表”→答“需新增payment_method ENUM(现金,银行卡,微信,支付宝)和transaction_id VARCHAR(64)字段。不新建表因支付方式是账单的属性符合第一范式若未来需记录微信支付的详细回调日志则另建payment_logs表用billing_id外键关联体现一对多关系。”我带过的模拟项目X中学生常把报告写成“我用了 PyQt5界面很美观”结果答辩被问一句“这个界面如何体现数据库的事务特性”就卡壳。后来我强制要求报告每章标题必须带数据库关键词如“第三章基于外键约束的参照完整性验证”代码截图旁必须配一行文字说明“此 SQL 体现了第 X 范式要求”。当老师看到你在CREATE TABLE语句后手写批注// 此处 FOREIGN KEY 实现了参照完整性避免悬挂元组他就知道你不是在调包而是在思考。希望帮到你。本文还有配套的精品资源点击获取