简介:针对数据库课程设计中的酒店管理场景,这套以客房管理为核心的实践资源,适合高校计算机相关专业学生作为课程设计或期末项目参考。项目围绕客房、客户、订单、入住记录与退房记录等核心实体,给出了关系型数据库的表结构设计,包括主键外键约束、字段类型分配,以及入住退房、预订和房间状态更新等典型业务流程。学习者既能借此理解数据库原理,也能掌握从需求分析到数据库实现、再到接口联调的完整思路。压缩包为rar格式,共三十个文件,包括十个Java源文件、十八个class编译文件与两张jpg格式界面预览图。Java源码覆盖客房入住、结算、新客户维护等模块,可直接阅读和二次开发;编译好的class文件便于快速运行体验;图片展示了界面效果,方便写文档或答辩时使用。资源包仅四百零四KB,内容紧凑。目前已有1876人学习下载,通过该系统可以快速获取酒店客房管理模块的建表SQL、JDBC连接方式、界面事件处理范例,并可在其基础上扩展预订、换房、统计等附加功能。
1. 数据库课设选酒店客房管理:比你想的更能拉开差距
数据库课程设计里,酒店管理系统(客房管理)是最容易被低估的题目。看起来就是几张表加几个页面,很多同学直接拿现成模板改个名就交了,结果答辩时被老师一句话问住:“你的客房状态是怎么保证不会重复卖出去的?”这个题目真正的难点不在写代码,而在数据建模、事务边界和状态流转的严谨性。它是典型的MIS(管理信息系统)课设,覆盖面刚好:ER设计、范式、完整性约束、CRUD、简单统计,难度适中,工作量可控,非常适合作为课程设计的选题。
这篇笔记我从数据建模讲到建库脚本、再讲到功能实现和边界坑,全程用一套完整可用的方案说话。如果你正在纠结题目怎么拆、表怎么建、代码怎么写得像样,或者写完不知道哪里会翻车,照着走一遍就能直接拿去答辩。内容不依赖任何框架,只讲最可靠、最容易讲清楚原理的落地路径。
2. 业务模型与ER设计:先把房间状态讲清楚,再谈表
2.1 酒店客房管理的核心业务流
做课程设计最容易犯的错是一上来就建表。我一般会先在纸上把业务流走一遍:客人到店后,前台查询当前哪些房间是空闲的,根据房型、价格、楼层给客人推荐;客人确认后办理入住,房间状态从“空闲”变成“已入住”;入住期间客人可能续住、换房、加床,也可能会产生消费(餐饮、洗衣、赔偿等);离店时办理退房,结算房费和其他消费,房间状态变回“空闲”,同时进入清洁状态,等保洁打扫完再重新变为“可售”。
这条链路是酒店管理系统的“业务主干”。你会发现,所有的数据表和功能页面都服务于“房间状态”的流转,而不是像网上很多模板那样,把系统做成一个花哨的“增删改查大杂烩”。老师问“一个房间怎么从空闲变成已入住又变回空闲”,你的回答如果只是“改一下状态字段”,那是没有掌握这门课设的答题要领。关键点在于:房间状态的每一次变化,背后都要有一张业务单据(订单、入住单、退房单)作为依据,而且状态不能凭空跳变。
2.2 从ER图到范式检查:为什么订单和账目要分表
基于上面的业务流,核心实体大概有这几类:客房(Room)、房型(RoomType)、客户/住客(Guest)、预订/订单(Order)、入住单(CheckIn)、账单(Bill)、操作员/前台用户(User)。实体间关系也很直观:一个房间属于一种房型;一个入住单对应一个房间和一位客人;一个订单可能包含一次或多次入住记录;一张账单可能包含多笔消费明细。
接下来是范式检查。很多同学的翻车点就在这里:把“客人姓名”直接写在“订单表”里,同时把“房型名称”“房间号”也冗余到订单表,一张表搞定一切,结果出现大量重复存储和更新异常。正确做法是:订单表只存客人的外键ID、房间的外键ID、入住日期、退房日期和订单状态;客人姓名、联系方式、房型名称这些都要通过关联查询获得。订单的金额明细放在独立的账单表中,不要直接塞进订单表,否则订单多次入住时金额无法拆分,第三范式就过不了。课设评审表里“ER图是否合理、是否满足范式要求”是专项评分点,这步值得认真做。
ER图建议用工具画,常见的制图工具都行。画完先自查两件事:多对多关系有没有转换成中间表?日期类和金额类字段是不是都设计在订单/账单表里了?自查没问题,再开始建库。
3. 技术选型与建库落地:Swing + MySQL 的最小可靠组合
3.1 技术选型逻辑:为什么不用SSM也能高分
数据课设的评分重点不是框架多新,而是“数据库设计是否合理、功能是否完整、是不是你自己能讲清楚”。常见做法是Java Swing + JDBC + MySQL,或者JavaWeb的Servlet + JSP + MySQL。我建议优先考虑前者,理由有两条:一是本地跑起来没有容器部署的额外负担,二是代码量可控,所有数据库操作都写在JDBC层,答辩时你能直接回答“这条SQL为什么这么写”。
Web方向不是不可以,但如果你只有两到三周做课设,Servlet + JSP的页面跳转和请求转发逻辑会分走你很多精力;如果选题评审需要你演示“系统是一个完整可运行的管理信息系统”,Swing足够满足。至于SSM、Spring Boot这类框架,如果不是课程要求,不建议在课设里强行引入——框架会掩盖你对SQL和数据库设计的理解,老师一问“连接池是怎么配置的”“事务是怎么控制的”,你反而说不清楚。这不是框架不好,是课设场景不适合拿它当展示主体。要把重心放在这一门课的考察点上:数据库本身。
3.2 建库脚本与字段设计:一张房态表读出九种状态
对于客房管理,最核心的是房间表。不要只放一个“状态”字符串,这样统计和维护都很困难。我建议用“状态码 + 状态说明”双字段设计,状态码用TINYINT类型,说明用VARCHAR。下面给出建库脚本,你可以直接复制到MySQL命令行执行,也可以根据自己的数据库微调。
-- 创建数据库(指定字符集,避免中文乱码) CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hotel_db; -- 房型表 CREATE TABLE room_type ( type_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '房型编号', type_name VARCHAR(30) NOT NULL UNIQUE COMMENT '房型名称(单人间/标准间/豪华间)', price DECIMAL(10,2) NOT NULL COMMENT '门市参考价', bed_count TINYINT NOT NULL DEFAULT 1 COMMENT '床位数', description VARCHAR(200) COMMENT '备注' ) ENGINE=InnoDB; -- 客房表 CREATE TABLE room ( room_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '房间ID', room_no VARCHAR(10) NOT NULL UNIQUE COMMENT '房间号,如 301', type_id INT NOT NULL COMMENT '所属房型ID', floor TINYINT NOT NULL COMMENT '楼层', status TINYINT NOT NULL DEFAULT 0 COMMENT '房间状态:0空闲 1已入住 2脏房/待清理 3维修 4预留 5锁定', comment VARCHAR(200) COMMENT '房间备注', CONSTRAINT fk_room_type FOREIGN KEY (type_id) REFERENCES room_type(type_id) ) ENGINE=InnoDB; -- 住客表 CREATE TABLE guest ( guest_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客人ID', guest_name VARCHAR(30) NOT NULL COMMENT '姓名', id_card VARCHAR(18) NOT NULL COMMENT '证件号', phone VARCHAR(20) COMMENT '联系电话', address VARCHAR(100) COMMENT '联系地址' ) ENGINE=InnoDB; -- 订单表 CREATE TABLE orders ( order_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID', guest_id INT NOT NULL COMMENT '客人ID', room_id INT NOT NULL COMMENT '房间ID', checkin_date DATE NOT NULL COMMENT '入住日期', checkout_date DATE NOT NULL COMMENT '应退房日期', order_status TINYINT NOT NULL DEFAULT 0 COMMENT '0进行中 1已完成 2已取消', create_user_id INT NOT NULL COMMENT '操作员ID', create_time DATETIME NOT NULL COMMENT '下单时间', CONSTRAINT fk_order_guest FOREIGN KEY (guest_id) REFERENCES guest(guest_id), CONSTRAINT fk_order_room FOREIGN KEY (room_id) REFERENCES room(room_id), CONSTRAINT fk_order_user FOREIGN KEY (create_user_id) REFERENCES user(user_id) ) ENGINE=InnoDB; -- 账单表 CREATE TABLE bill ( bill_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '账单ID', order_id INT NOT NULL COMMENT '关联订单ID', item_name VARCHAR(50) NOT NULL COMMENT '费用项目:房费/餐饮/洗衣/赔偿', amount DECIMAL(10,2) NOT NULL COMMENT '金额', bill_time DATETIME NOT NULL COMMENT '发生时间', CONSTRAINT fk_bill_order FOREIGN KEY (order_id) REFERENCES orders(order_id) ) ENGINE=InnoDB; -- 操作员表 CREATE TABLE user ( user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '操作员ID', username VARCHAR(30) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT '登录密码(建议存SHA-256摘要)', real_name VARCHAR(30) COMMENT '姓名', role TINYINT NOT NULL DEFAULT 1 COMMENT '角色:0管理员 1前台' ) ENGINE=InnoDB;上面的脚本有几个核心设计点值得说明。状态字段用TINYINT而非VARCHAR,是因为状态是一个固定枚举集合,用数字存储能避免脏数据进入,同时也方便写统计SQL时按数字分组。价格字段用DECIMAL(10,2)而不是FLOAT或者DOUBLE,是因为金额不允许出现精度漂移,这在后面的避坑章节会细讲。订单表里只存“应退房日期”,实际退房日期放在另一次操作记录中,是为了支持“提前退房”和“续住”的需求——如果你把实际退房日期也塞在orders表里,那订单状态一改,历史信息就没了。账单表里的order_id外键关联订单,但不会把房价写成字段冗余进去,因为价格会随季节调整,查历史账单时直接通过关联查询拿当次订单的金额,而不是拿房型当前价格去推算,这样账目更严谨。
建完表后可以做一次简单验证:用SHOW CREATE TABLE orders;检查外键是否生效,用INSERT插入一条测试数据再尝试撤销,确认约束正常运行。注意InnoDB才支持外键约束,MyISAM建表不会报错但外键会失效,很多人踩过这个坑。
4. 功能实现:登录、房态看板与入住退房闭环
4.1 JDBC 基础封装:连接、事务和查询的通用写法
功能实现的第一步是把数据库访问层写稳。很多同学直接把DriverManager.getConnection()写在每个界面代码里,连接散落各处,改一个密码要全局替换,更致命的是连接用完不关,程序跑一段时间就报连接数超限。我一般会做一个工具类来管理连接,顺便解决资源释放的问题。
import java.sql.*; public class DbUtil { private static final String URL = "jdbc:mysql://localhost:3306/hotel_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("数据库驱动加载失败", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException ignored) {} try { if (stmt != null) stmt.close(); } catch (SQLException ignored) {} try { if (conn != null) conn.close(); } catch (SQLException ignored) {} } }这段代码有几个关键设计:连接URL里显式声明了useSSL=false和serverTimezone,避免新版MySQL驱动在本地连接时的SSL告警和时区报错;字符编码用utf8保证中文不乱码。close方法的重载很重要,如果你只关statement不关resultSet,某些驱动下资源不会被真正释放。这里没有用连接池,因为课设单用户场景完全够用;但你要知道,真实系统里不会裸用DriverManager,而是用连接池管理。遇到“Too many connections”报错时,先检查是不是代码里出现了未关闭的连接。
然后封装一个通用的查询方法,把重复的“创建PreparedStatement、执行、遍历结果集”过程收拢:
public static <T> List<T> query(String sql, Object[] params, ResultSetMapper<T> mapper) { List<T> list = new ArrayList<>(); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DbUtil.getConnection(); ps = conn.prepareStatement(sql); if (params != null) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } } rs = ps.executeQuery(); while (rs.next()) { list.add(mapper.map(rs)); } } catch (SQLException e) { e.printStackTrace(); } finally { DbUtil.close(conn, ps, rs); } return list; } @FunctionalInterface public interface ResultSetMapper<T> { T map(ResultSet rs) throws SQLException; }ResultSetMapper是一个函数式接口,你只需要在调用处写明“如何把一行ResultSet转换为一个对象”。这种写法比在业务代码里写一堆rs.getString("xxx")清爽得多,而且业务层只依赖接口,不依赖具体数据库操作。基于这个工具方法,查询“所有空闲房间”只需要一行调用:
String sql = "SELECT r.room_id, r.room_no, t.type_name, t.price FROM room r JOIN room_type t ON r.type_id = t.type_id WHERE r.status = 0"; List<Room> freeRooms = DbUtil.query(sql, null, rs -> { Room room = new Room(); room.setRoomId(rs.getInt("room_id")); room.setRoomNo(rs.getString("room_no")); room.setTypeName(rs.getString("type_name")); room.setPrice(rs.getBigDecimal("price")); return room; });这里有个常被忽略的细节:JOIN查询时如果两张表都有type_id字段,SQL里要写r.type_id或t.type_id,你写rs.getInt("type_id")会直接抛Column 'type_id' is ambiguous异常。课设里这种错误很常见,属于看报错能解决的,但会消耗不少时间。表字段设计时尽量让外键名有区分度,或者查询时用别名规范一下,就能从根本上避免。
4.2 入住退房的核心代码:状态更新为什么必须放在同一个事务里
入住流程的业务逻辑是:检查房间状态是否为0(空闲),更新房间状态为1(已入住),插入一条订单记录,同时写一条账单记录作为押金或首日房费。退房流程相反:核对订单状态,更新房间状态为0(空闲)或2(脏房),更新订单状态为已完成,写入账单的尾款记录。
这四个步骤如果分开执行,中间任何一步失败,数据库就会处于不一致状态——比如房间状态更新了,但订单没插入成功,前台看到房间“已入住”,系统里却找不到是谁住的。解决办法是让这四个操作共享同一个数据库连接,并且在同一个事务中提交或回滚。核心代码如下,注意看事务控制的结构:
public void checkIn(int roomId, int guestId, Date checkinDate, Date checkoutDate, int userId) { Connection conn = null; try { conn = DbUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 防止并发下单:锁定该房间行(悲观锁) String lockSql = "SELECT status FROM room WHERE room_id = ? FOR UPDATE"; PreparedStatement lockPs = conn.prepareStatement(lockSql); lockPs.setInt(1, roomId); ResultSet rs = lockPs.executeQuery(); int status = -1; if (rs.next()) { status = rs.getInt("status"); } rs.close(); lockPs.close(); if (status != 0) { throw new RuntimeException("房间当前不可入住,状态码:" + status); } // 2. 更新房间状态为已入住 String updateRoomSql = "UPDATE room SET status = 1 WHERE room_id = ?"; PreparedStatement updateRoomPs = conn.prepareStatement(updateRoomSql); updateRoomPs.setInt(1, roomId); updateRoomPs.executeUpdate(); updateRoomPs.close(); // 3. 插入订单 String insertOrderSql = "INSERT INTO orders(guest_id, room_id, checkin_date, checkout_date, order_status, create_user_id, create_time) VALUES(?,?,?,?,0,?,NOW())"; PreparedStatement insertOrderPs = conn.prepareStatement(insertOrderSql, Statement.RETURN_GENERATED_KEYS); insertOrderPs.setInt(1, guestId); insertOrderPs.setInt(2, roomId); insertOrderPs.setDate(3, new java.sql.Date(checkinDate.getTime())); insertOrderPs.setDate(4, new java.sql.Date(checkoutDate.getTime())); insertOrderPs.setInt(5, userId); insertOrderPs.executeUpdate(); // 取出自增主键 ResultSet keys = insertOrderPs.getGeneratedKeys(); int orderId = -1; if (keys.next()) { orderId = keys.getInt(1); } keys.close(); insertOrderPs.close(); // 4. 写首日房费账单 String insertBillSql = "INSERT INTO bill(order_id, item_name, amount, bill_time) VALUES(?, ?, ?, NOW())"; PreparedStatement insertBillPs = conn.prepareStatement(insertBillSql); insertBillPs.setInt(1, orderId); insertBillPs.setString(2, "房费(首日)"); insertBillPs.setBigDecimal(3, getRoomPrice(roomId, conn)); insertBillPs.executeUpdate(); insertBillPs.close(); conn.commit(); // 全部成功才提交 conn.setAutoCommit(true); } catch (Exception e) { if (conn != null) { try { conn.rollback(); // 任何一步失败,全部回滚 } catch (SQLException ex) { ex.printStackTrace(); } } throw new RuntimeException("入住登记失败:" + e.getMessage(), e); } finally { if (conn != null) { try { conn.setAutoCommit(true); } catch (SQLException ignored) {} try { conn.close(); } catch (SQLException ignored) {} } } }这段代码的要点我拆开讲。
最忌讳的是把四个步骤写在四个独立方法里,每个方法内部各自getConnection,那样即便每个方法都开了事务,也不是同一个事务,中间断了仍然不一致。正确做法是这段代码体现的:一个方法贯穿到底,用一个连接对象。
SELECT ... FOR UPDATE是行级悲观锁,作用是当两个前台同时给同一个房间办理入住时,后一个事务会等待前一个提交后才读取到最新状态。课设演示阶段你未必会真的遇到并发,但答辩时能主动讲出“我用行级锁防止房间超卖”,是一个很加分的细节——老师常常会追问“两个人同时点入住按钮怎么办”,这个问题会拦住很多只做了单线程CRUD的同学。
还有两个实现层面的小细节:插入订单时用了Statement.RETURN_GENERATED_KEYS来拿自增主键,避免额外查一次MAX(order_id)这种并发下不安全的写法;日期参数用的是java.sql.Date而不是java.util.Date,否则PreparedStatement的setDate方法不认。房费不是从房间信息里查完直接写死金额,而是调用getRoomPrice(roomId, conn)传入同一个连接去取,保证了账单数据与房型表当前价格的一致性。这个方法内部用SELECT t.price FROM room r JOIN room_type t ON r.type_id = t.type_id WHERE r.room_id = ?就好,字段price要取出来转成BigDecimal再写入账单。
退房操作结构类似,差别在于把房间状态改为2(脏房)还是0(空闲)。常见做法是:退房后房间进入“脏房”状态,由保洁员在系统中执行“清洁完成”操作后变为“空闲”,这样系统才能衔接酒店的保洁流程。你可以用一个按钮或者一个单独的功能页来演示这个流转。如果你的课设不需要演示保洁环节,退房时直接把状态改成0也可以,但要在答辩时说明:这里是简化处理。
5. 课设避坑指南:状态错乱、金额失真与界面不同步
5.1 房态更新丢事务:数据错乱来得静悄悄
现象:房间状态明明显示“空闲”,但查询订单时发现这个房间有一个“进行中”的订单。也就是“房间没人住,订单却没关”,或者反过来“房间显示已入住,订单列表里找不到记录”。
原因:入住和退房的操作没有放在同一个事务里,或者代码里try-catch只打了日志,没有真正执行rollback()。再有一种情况是:你做了事务,但连接是从两个不同位置获取的,事务根本没有覆盖所有步骤。
解决:按上一节的方式,把“状态更新+订单变更+账单写入”放到同一个方法、同一个Connection、同一个事务里。代码写完可以用一个小技巧验证:在插入订单步骤后面人为造一个异常(比如int x = 1 / 0;),跑一次入住,然后去数据库看房间状态有没有被改动。如果房间状态没变,说明回滚生效了。
5.2 金额用 double 计算:一分钱对不上账
现象:账单里房费是 328.00,早餐是 28.50,合计显示 356.50,看着没问题。但读数据库里存的是 356.49999999999994,并且在极端情况下,统计报表的总数会时不时多一分钱或少一分钱。
原因:Java的double和MySQL的FLOAT/DOUBLE都是浮点存储,某些十进制小数无法用二进制精确表示,累积多了就会出现精度漂移。这是典型的“黑匣子”问题,不报错,不提醒,但对账的时候就暴露了。
解决:数据库金额字段统一用DECIMAL(10,2),Java实体类对应字段使用BigDecimal,禁止在代码里用double接收金额。计算时用BigDecimal.setScale(2, RoundingMode.HALF_UP)处理。记住一个习惯:跟钱有关的运算,只允许BigDecimal出现。
5.3 日期区间判断出边界:晚退房早退房都算错
现象:客人订了3月1日到3月3日的房间,退房时间按日期差算被计成2天,实际应算3天(因为1号当天也算住)。还有客人续住一天,你只改了订单的checkout_date,但账单没有补记一天的房费。
原因:日期区间的天数是(checkout - checkin),这样计算会把入住当天漏掉。而房费计费一般是按“过夜数”来算,客人1号入住3号退房,住了两晚;但如果系统按日期差直接算,就是2天,跟“含当天”的计费方式搞混了。
解决:先明确你的计费规则。按过夜数计费,则天数 = checkout_date - checkin_date没问题,但要保证1号入住、2号凌晨入住这类边界情况被判定清楚;按”含入住当天“计费,则天数 = 差值 + 1。更稳妥的做法是不算天数,直接按“订单里每天一条账单记录”存储,把生成账单的逻辑放在入住时批量插入每一天的房费记录。这样无论提前退房还是续住,只删记录或加记录,金额都是自动对齐的。课设阶段我建议用“每日账单明细”来设计,靠谱也容易答辩。
5.4 删除房型被外键拦住:你以为的“清空”其实带走了整条订单链
现象:你想在系统里删掉“豪华间”这个房型,结果数据库报错Cannot delete or update a parent row。有些同学为了解决这个报错,直接去数据库里把外键删掉,结果后续操作变得非常随意,数据完整性全面失守。
原因:房型是room表的外键父表,只要还有房间挂在“豪华间”下,或者相关订单还引用着这些房间,数据库就会阻止删除。这是外键约束在保护你的数据,不是Bug。
解决:课设里不需要“硬删除”房型。把房型表加一个is_active字段(1启用/0停用),删除操作变成更新操作。停用的房型新建订单时不可选,但历史订单仍然可以正常查询。这样既满足了“房型管理”的功能要求,又不破坏数据链。
5.5 界面数据不刷新:数据库对了你屏幕上还是旧房态
现象:用管理员账号办理完一间房的退房后,切换到前台账号刷新房态列表,房间依旧显示“已入住”。再退一次,系统报错“房间状态不允许退房”。
原因:客户端界面用了缓存的List对象,没有在每次打开页面时重新从数据库加载。很多课设的房态展示是启动时load一次,之后就一直用内存里的旧数据。
解决:在窗口激活事件或每次刷新按钮里重新执行查询,强制重新加载数据。Swing里可以给房态面板加一个addComponentListener监听,窗口每次切换到该页时自动刷新;点击按钮刷新是最直白的做法,两个都做上最保险。代码层面就一句话:btnRefresh.doClick();或者在showPage()方法里重新调用查询方法。这种问题翻车率极高,写进你自己的检查清单里。
6. 答辩加分技巧:一张操作日志表把“管理”两个字做实
做完增删改查,很多同学觉得系统已经“完成”了,但答辩时老师经常会问一句:“你的系统能不能看出谁在什么时候办理了入住?如果客人投诉说前台算错了钱,你如何追溯?”这其实是在考察系统有没有“审计”能力。加一张操作日志表,就能把这个问句变成你的加分点。
操作日志表记录每一次关键业务动作,包含操作人、动作类型、关联订单、操作时间和操作前后状态。核心字段设计如下:
CREATE TABLE operation_log ( log_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT '操作员ID', action_type VARCHAR(20) NOT NULL COMMENT '动作:CHECKIN/CHECKOUT/CLEAN_ROOM/CANCEL_ORDER', order_id INT DEFAULT NULL COMMENT '关联订单ID', room_id INT NOT NULL COMMENT '操作房间ID', detail VARCHAR(200) COMMENT '操作摘要', operate_time DATETIME NOT NULL COMMENT '操作时间' ) ENGINE=InnoDB;每次入住、退房、换房、取消订单,在前面的业务方法里统一加一行“写日志”操作,比如在checkIn方法中、事务提交前插入一条action_type='CHECKIN'的记录。注意要放在同一个事务里,日志操作本身失败时业务也回滚,这样日志才可信。做完之后,做一个“操作日志查询”页面,支持按日期、操作员、房间号查询。这个功能本身工程量很小,但它在答辩时能回答三类问题:系统做了哪些安全管理、如何追溯操作责任、事务边界在哪里。
如果想要更进一步,再加一个“经营报表”:按日统计出租率、各房型收入、某段时间内的入住数量。报表数据量不大时,你完全可以直接用一条聚合SQL解决,不必引入报表框架。比如统计近7天每天的收入:
SELECT DATE(bill_time) AS biz_date, SUM(amount) AS total_amount FROM bill WHERE bill_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(bill_time) ORDER BY biz_date;然后通过一个柱状图或表格展示在“统计报表”页里。这招能直接拉开你和大部分模板的差距——不是功能多花哨,而是你的系统在“数据”层面能说话。最后说一条我自己的教训:课设别急着写界面,先把数据库脚本和核心业务方法调通,界面是包装不是内核。你按住Ctrl+Alt+Del结束进程再重启后数据不乱,这种“抗造”的系统,答辩老师看了都省心。希望你这份课设能一次过,少熬夜,多睡会儿。
本文还有配套的精品资源,点击获取