SQL Server + Swing 宾馆管理系统:数据库课设中的事务与CRUD实战

发布时间:2026/10/21 17:50:56
SQL Server + Swing 宾馆管理系统:数据库课设中的事务与CRUD实战
简介这份资源是面向高校计算机相关专业学生的数据库课程设计参考方案围绕宾馆管理业务场景采用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数据库表、视图、存储过程HotelDB2.2 数据库连接与配置连接串一般写在配置文件里不硬编码。下面是一个典型的 JDBC 工具类public class DBUtil { // 从 db.properties 读取避免把密码写死在代码里 private static final String URL jdbc:sqlserver://localhost:1433;databaseNameHotelDB;encryptfalse; 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(); } } }参数说明encryptfalse在本地开发时避免证书报错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() { ListRoom 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 启用重启服务确认实例端口是 1433Express 版要去 IP 地址标签页看动态端口。4.2 中文乱码现象客人姓名存进去变成问号。原因数据库排序规则不是中文或者 JDBC URL 没指定编码。解决建库时选Chinese_PRC_CI_AS连接串加;sendStringParametersAsUnicodetrue。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); } }从那以后我每次改计费逻辑都强制先把旧订单跑一遍回归确认金额一分不差才提交。希望这套东西能帮到你少熬两个通宵。本文还有配套的精品资源点击获取