简介:这份资源是面向高校计算机相关专业学生的数据库课程设计参考方案,围绕宾馆管理业务场景,采用SQL Server作为后台数据库、Java Swing构建桌面端界面,适合正在完成数据库课设或需要酒店管理类项目练手的同学对照学习。压缩包共40个文件,约2.02MB,包含SQL建库脚本与数据库文件、Java源码及编译后的class文件、可运行jar包,以及一份使用说明文档,覆盖从建库、编码到运行演示的完整链路。目前已有378人学习下载,可作为课设选题与实现思路的参考。读者可从中获取数据库表结构设计、Swing界面与SQL Server的连接方式、项目目录组织方式,以及一份可直接运行体验的成品程序,便于快速理解宾馆管理系统的功能划分与实现细节,并在此基础上进行改造与扩展。
1. 从一张房态表说起:这个宾馆管理系统到底能干什么
很多数据库课设做到最后,都会落到一个“看着简单、写起来全是细节”的场景上——宾馆管理。表面看就是开房、退房、查房态,真动手才发现:房间状态要实时同步、押金和消费要分开算、换房要同时改两张表、凌晨两点退房和中午十二点退房计费逻辑还不一样。这份用 SQL Server + Swing 实现的宾馆管理系统,就是冲着这些真实业务细节去的。它适合两类人:一类是正在找数据库课设参考、需要一套能跑通、表结构完整的同学;另一类是已经会写 Java 界面,但没做过完整 CRUD + 事务 + 报表的开发者。整个项目把“数据库设计”和“桌面端交互”绑在一起,不是那种只画个 ER 图就交差的空壳。
2. 技术选型与整体架构:为什么是 SQL Server + Swing 而不是别的
2.1 选型背后的现实考量
先把这个组合说清楚。Swing 是 JDK 自带的桌面 UI 库,不需要额外装前端框架,对课设环境最友好;SQL Server 在 Windows 上装完就能用,SSMS 图形化建表、写存储过程都方便。两者通过 JDBC 连接,中间加一层 DAO(数据访问对象)把 SQL 语句和界面代码隔开。常见做法是:界面层只负责收集输入和展示结果,业务逻辑放在 Service 层,数据库操作全部收口到 DAO 层。这样改需求时不用满屏找 SQL。
项目整体分四层:
| 层次 | 职责 | 典型类 |
|---|---|---|
| 视图层 | 窗口、表格、按钮事件 | MainFrame、RoomPanel |
| 业务层 | 入住/退房/换房逻辑、计费 | RoomService、BillService |
| 数据层 | 增删改查、事务控制 | RoomDao、OrderDao |
| 数据库 | 表、视图、存储过程 | HotelDB |
2.2 数据库连接与配置
连接串一般写在配置文件里,不硬编码。下面是一个典型的 JDBC 工具类:
public class DBUtil { // 从 db.properties 读取,避免把密码写死在代码里 private static final String URL = "jdbc:sqlserver://localhost:1433;databaseName=HotelDB;encrypt=false"; private static final String USER = "sa"; private static final String PASSWORD = "your_password"; public static Connection getConnection() throws SQLException { // 加载驱动,SQL Server 用 mssql-jdbc return DriverManager.getConnection(URL, USER, PASSWORD); } // 统一关闭资源,防止连接泄漏 public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }参数说明:encrypt=false在本地开发时避免证书报错;databaseName换成你实际建的库名。注意 SQL Server 默认端口 1433,如果装的是 Express 版可能是动态端口,要去配置管理器里确认。
2.3 核心表结构设计
宾馆管理的表不用多,但关系要清楚。最少需要这几张:
-- 房间表:记录房间基本信息和当前状态 CREATE TABLE Room ( roomId INT PRIMARY KEY IDENTITY(1,1), roomNo VARCHAR(10) NOT NULL UNIQUE, -- 房间号,如 8301 roomType VARCHAR(20) NOT NULL, -- 标准间/大床房/套房 price DECIMAL(10,2) NOT NULL, -- 每晚单价 status VARCHAR(10) DEFAULT '空闲' -- 空闲/已入住/维修 ); -- 订单表:一次入住对应一条记录 CREATE TABLE Orders ( orderId INT PRIMARY KEY IDENTITY(1,1), roomId INT FOREIGN KEY REFERENCES Room(roomId), guestName NVARCHAR(50) NOT NULL, idCard VARCHAR(18) NOT NULL, checkInTime DATETIME DEFAULT GETDATE(), checkOutTime DATETIME, deposit DECIMAL(10,2) DEFAULT 0, -- 押金 totalCost DECIMAL(10,2) DEFAULT 0 -- 最终结算金额 );status字段是整套系统的“开关”,入住时改成“已入住”,退房时改回“空闲”,换房时两张房间记录要同时更新——这里必须用事务,否则会出现两个房间都显示已入住或者都空闲的玄学问题。
3. 核心模块拆解:入住、退房、换房三条链路怎么走
3.1 入住登记:从界面输入到数据库落库
入住是第一个要跑通的链路。界面上选房间、填客人姓名和身份证、收押金,点确认后要做三件事:插入订单、更新房间状态、刷新房态表。这三步必须在一个事务里。
public boolean checkIn(int roomId, String guestName, String idCard, double deposit) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 插入订单 String sql1 = "INSERT INTO Orders(roomId, guestName, idCard, deposit) VALUES(?,?,?,?)"; PreparedStatement ps1 = conn.prepareStatement(sql1); ps1.setInt(1, roomId); ps1.setString(2, guestName); ps1.setString(3, idCard); ps1.setDouble(4, deposit); ps1.executeUpdate(); // 2. 更新房间状态 String sql2 = "UPDATE Room SET status='已入住' WHERE roomId=? AND status='空闲'"; PreparedStatement ps2 = conn.prepareStatement(sql2); ps2.setInt(1, roomId); int rows = ps2.executeUpdate(); if (rows == 0) { conn.rollback(); // 房间不是空闲,回滚 return false; } conn.commit(); return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { DBUtil.close(conn, null, null); } }关键点在UPDATE语句里的AND status='空闲'。这是乐观锁的思路:如果两个人同时点同一个房间,只有第一个能更新成功,第二个返回 0 行直接回滚。不加这个条件,就会出现同一间房开出两张订单的翻车现场。
3.2 退房结算:计费逻辑与押金处理
退房比入住复杂,因为涉及计费。常见规则是:按入住天数算房费,超过中午 12 点加半天,超过下午 6 点加全天。这部分逻辑放在 Service 层,不要写进 SQL。
public double calculateCost(Orders order, Room room) { LocalDateTime in = order.getCheckInTime().toLocalDateTime(); LocalDateTime out = LocalDateTime.now(); long hours = Duration.between(in, out).toHours(); long days = hours / 24; long remainHours = hours % 24; // 不足一天按规则折算 if (remainHours > 0 && remainHours <= 6) { // 6小时内按半天 return room.getPrice() * days + room.getPrice() * 0.5; } else if (remainHours > 6) { return room.getPrice() * (days + 1); } return room.getPrice() * Math.max(days, 1); // 至少算一天 }参数说明:room.getPrice()是房间单价,days是完整天数。实际结算时还要加上订单里的其他消费(迷你吧、洗衣等),再减去押金。退房成功后,把房间状态改回“空闲”,订单表补上checkOutTime和totalCost。
3.3 换房操作:两张表同时更新的坑
换房是课设里最容易出 bug 的地方。逻辑是:旧房间释放、新房间占用、订单关联的 roomId 要改。三步缺一不可,而且必须在同一个事务里。
public boolean changeRoom(int orderId, int oldRoomId, int newRoomId) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 释放旧房间 PreparedStatement ps1 = conn.prepareStatement( "UPDATE Room SET status='空闲' WHERE roomId=?"); ps1.setInt(1, oldRoomId); ps1.executeUpdate(); // 2. 占用新房间,同样加空闲校验 PreparedStatement ps2 = conn.prepareStatement( "UPDATE Room SET status='已入住' WHERE roomId=? AND status='空闲'"); ps2.setInt(1, newRoomId); if (ps2.executeUpdate() == 0) { conn.rollback(); return false; } // 3. 更新订单关联 PreparedStatement ps3 = conn.prepareStatement( "UPDATE Orders SET roomId=? WHERE orderId=?"); ps3.setInt(1, newRoomId); ps3.setInt(2, orderId); ps3.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { DBUtil.close(conn, null, null); } }注意第 2 步的空闲校验:如果新房间已经被别人住了,整个换房回滚,旧房间也不会被错误释放。这就是事务的“后悔药”。
3.4 房态看板:Swing 表格的实时刷新
界面层用JTable展示房态,数据来自RoomDao.findAll()。每次入住、退房、换房后都要重新查一遍数据库刷新表格,不要试图在内存里改状态——内存和数据库不一致是桌面端最常见的黑匣子。
public void refreshRoomTable() { List<Room> rooms = roomDao.findAll(); DefaultTableModel model = (DefaultTableModel) roomTable.getModel(); model.setRowCount(0); // 清空旧数据 for (Room r : rooms) { model.addRow(new Object[]{r.getRoomNo(), r.getRoomType(), r.getPrice(), r.getStatus()}); } }setRowCount(0)比removeRow循环更干净。如果数据量大,可以只更新变化的那一行,但课设规模没必要。
4. 避坑与排查:课设里最容易翻车的五个点
4.1 连接报错“The TCP/IP connection to the host has failed”
现象:程序一启动就抛异常,连不上数据库。原因通常是 SQL Server 的 TCP/IP 协议没启用,或者端口不对。解决:打开 SQL Server 配置管理器,把 TCP/IP 启用,重启服务;确认实例端口是 1433,Express 版要去 IP 地址标签页看动态端口。
4.2 中文乱码
现象:客人姓名存进去变成问号。原因:数据库排序规则不是中文,或者 JDBC URL 没指定编码。解决:建库时选Chinese_PRC_CI_AS,连接串加;sendStringParametersAsUnicode=true。
4.3 事务没回滚导致房态错乱
现象:入住失败后房间还是显示“已入住”。原因:setAutoCommit(false)之后没有在异常分支里rollback()。解决:所有事务方法统一用 try-catch-finally 模板,catch 里回滚,finally 里关连接。
4.4 Swing 界面卡死
现象:点查询按钮后窗口无响应。原因:数据库操作在 EDT(事件调度线程)里执行。解决:把耗时操作放到SwingWorker的doInBackground里,结果在done里更新 UI。
4.5 身份证号重复入住
现象:同一个人可以开两间房。原因:业务上没做校验。解决:在入住前查一下Orders表里有没有该身份证且未退房的记录,有就弹窗提示。
5. 二次开发与改造:从课设到能用的桌面工具
这套代码跑通之后,最值得做的改造有三个方向。第一个是加报表导出,用JasperReports或者最简单的POI把订单表导成 Excel,营业日报就有了。第二个是把计费规则抽成策略模式,现在calculateCost里一堆 if-else,改成BillingStrategy接口,钟点房、全天房、会员价各实现一个类,加新规则不用动老代码。第三个是加登录和权限,User表加role字段,前台只能开退房,经理才能改房价和看报表。
验证改造是否成功,我一般用一套固定用例:开一间房 → 换房 → 退房 → 查报表,看金额和房态是否对得上。特别是换房后计费,旧房间的单价和新房间的单价可能不同,结算时要按实际入住时段拆分,这个逻辑不写测试很容易漏。
// 改造示例:用策略模式替换 if-else public interface BillingStrategy { double calculate(long hours, double price); } public class StandardBilling implements BillingStrategy { public double calculate(long hours, double price) { long days = hours / 24; long remain = hours % 24; if (remain == 0) return price * Math.max(days, 1); if (remain <= 6) return price * days + price * 0.5; return price * (days + 1); } }从那以后我每次改计费逻辑,都强制先把旧订单跑一遍回归,确认金额一分不差才提交。希望这套东西能帮到你,少熬两个通宵。
本文还有配套的精品资源,点击获取