简介:这份资源是面向高校计算机相关专业学生的数据库课程设计参考方案,以SQL Server作为后台数据库、Java Swing构建桌面交互界面,实现了一套功能完整的宾馆管理系统,适合正在准备数据库课设、需要对照完整项目案例的初学者与中级学习者。压缩包共40个文件,约2.02MB,包含SQL建库脚本与数据库文件、Java源码与编译后的class文件、可运行jar包,以及使用说明文档,覆盖从建库、编码到运行演示的完整链路。目前已有378人学习下载,可作为课设选题与实现思路的参考。读者可从中获取数据库表结构设计、Swing界面布局与事件处理、JDBC连接SQL Server等关键实现细节,并借助说明文档快速部署运行,理解宾馆管理业务中客房、入住、退房等模块的组织方式,为独立完成同类课程设计提供可借鉴的工程结构与排错思路。
1. 从一张房态表说起:这套 SQL Server + Swing 的宾馆管理系统到底能干什么
很多做数据库课设的同学,第一次打开需求文档看到“宾馆管理”四个字,脑子里蹦出来的就是一张房态表加几个增删改查按钮。真动手才发现,前台开房要联动房态、换房要改押金、退房要算消费、夜审还要把在住订单重新过一遍——这些业务逻辑全塞进一个JButton的监听里,代码很快就变成一锅粥。这套用 SQL Server 做数据层、Java Swing 做客户端的宾馆管理系统,解决的正是这个问题:它把客房、预订、入住、消费、结算、用户权限拆成独立模块,用 JDBC 串起来,适合拿来做数据库课程设计,也适合想练手桌面端三层架构的开发者。整套代码不依赖 Web 容器,装好 JDK 和 SQL Server 就能跑,对新手足够友好,对熟手也能看到 DAO 分层和事务控制的完整实现。
2. 技术选型与工程结构:为什么是 SQL Server + Swing 这套组合
2.1 选型逻辑:桌面端课设为什么绕不开这两个
宾馆管理系统的业务特点是数据关系密集、并发量不大、部署环境简单。客房、订单、消费记录之间有明确的外键约束,用关系型数据库最顺手。SQL Server 在课设场景里出现频率高,一是它有图形化的 SQL Server Management Studio,建表、写存储过程、看执行计划都直观;二是 T-SQL 语法和标准 SQL 接近,写触发器、事务的示例资料多。客户端选 Swing 而不是 JavaFX 或 Web 前端,核心原因是零额外依赖——JDK 自带,不用配 Node、不用起 Tomcat,一个main方法就能把窗口拉起来,答辩演示时不会因为环境问题翻车。
这套组合的短板也要说清楚:Swing 的界面观感偏旧,默认 Metal 主题在高分屏上发虚;SQL Server 的 JDBC 驱动需要手动引入mssql-jdbc的 jar 包。但作为课设,这些都不是硬伤,反而能逼着你把 JDBC 连接、事务提交、异常回滚这些基本功练扎实。
2.2 目录结构:三层架构怎么落到文件夹上
项目按经典三层架构组织,包名和职责对应得很清楚:
hotel-system/ ├── src/ │ ├── com/hotel/ │ │ ├── entity/ # 实体类,与数据库表一一对应 │ │ │ ├── Room.java │ │ │ ├── Guest.java │ │ │ ├── Order.java │ │ │ └── User.java │ │ ├── dao/ # 数据访问层,封装所有 SQL │ │ │ ├── RoomDao.java │ │ │ ├── OrderDao.java │ │ │ └── UserDao.java │ │ ├── service/ # 业务逻辑层,处理事务和校验 │ │ │ ├── CheckInService.java │ │ │ └── CheckOutService.java │ │ ├── ui/ # Swing 界面层 │ │ │ ├── MainFrame.java │ │ │ ├── RoomPanel.java │ │ │ └── CheckInDialog.java │ │ └── util/ # 工具类 │ │ └── DBUtil.java │ └── lib/ │ └── mssql-jdbc-9.4.1.jre8.jar └── sql/ └── hotel_db.sql # 建库建表脚本entity层是纯 POJO,字段和表字段同名,方便手写映射;dao层每个类对应一张主表,方法名统一用insert、update、deleteById、findByCondition;service层是这套系统的灵魂,开房、退房这种跨多张表的操作都在这里用事务包起来;ui层只负责收集输入和展示结果,不写 SQL。这种分法的好处是,答辩时老师问“你的业务逻辑写在哪”,你能直接指到service包,而不是在一堆actionPerformed里翻。
2.3 数据库连接:DBUtil 怎么写才不埋雷
连接管理是最容易出问题的地方。常见做法是写一个DBUtil工具类,用静态代码块加载驱动,提供一个getConnection()方法。这里有个血泪经验:不要把Connection做成单例全局共享,Swing 是单线程事件分发模型,但如果你在事件里开了后台线程做查询,共享连接会直接抛“连接已关闭”异常。
public class DBUtil { private static final String URL = "jdbc:sqlserver://localhost:1433;databaseName=hotel_db;encrypt=false"; private static final String USER = "sa"; private static final String PASSWORD = "your_password"; static { try { // 加载 SQL Server JDBC 驱动 Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); } catch (ClassNotFoundException e) { throw new RuntimeException("驱动加载失败,检查 jar 是否引入", 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里的encrypt=false是 SQL Server 新版驱动的要求,不加会报证书错误;databaseName指定默认库,省得每条 SQL 都写hotel_db.dbo.前缀。close方法的关闭顺序必须是 ResultSet → Statement → Connection,反过来关会丢异常。参数USER和PASSWORD建议从配置文件读,硬编码在课设里能接受,但答辩时被问到“密码怎么管理的”会扣分。
3. 核心模块拆解:开房、退房、房态联动是怎么串起来的
3.1 房态查询:一条 SQL 把可用房捞出来
前台最常用的操作是查“今天有哪些空房”。房态字段设计成status,取值0空闲、1已入住、2维修、3预订。查询逻辑放在RoomDao.findAvailable():
public List<Room> findAvailable(String roomType) throws SQLException { String sql = "SELECT room_id, room_no, room_type, price, status " + "FROM room WHERE status = 0"; if (roomType != null && !roomType.isEmpty()) { sql += " AND room_type = ?"; } List<Room> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { if (roomType != null && !roomType.isEmpty()) { ps.setString(1, roomType); // 房型作为可选过滤条件 } try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Room r = new Room(); r.setRoomId(rs.getInt("room_id")); r.setRoomNo(rs.getString("room_no")); r.setRoomType(rs.getString("room_type")); r.setPrice(rs.getBigDecimal("price")); r.setStatus(rs.getInt("status")); list.add(r); } } } return list; }这里用PreparedStatement而不是Statement,一是防 SQL 注入,二是 SQL Server 对预编译语句有执行计划缓存,重复查询更快。try-with-resources保证连接和语句自动关闭,比手动finally干净。房型过滤用字符串拼接 SQL 再setString,注意拼接的是AND room_type = ?这个片段,不是把用户输入直接拼进去。
3.2 开房事务:三张表同时写,失败必须全回滚
开房不是简单插一条订单。它要同时做三件事:往orders表插一条入住记录、把room表对应房间的status改成1、往consume表插一条押金记录。任何一步失败,另外两步都得撤销,否则会出现“房间显示已住但订单不存在”的脏数据。这就是CheckInService存在的意义:
public boolean checkIn(Order order, BigDecimal deposit) throws SQLException { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 第一步:插入订单 String insertOrder = "INSERT INTO orders " + "(guest_id, room_id, check_in_time, deposit, status) " + "VALUES (?, ?, GETDATE(), ?, 1)"; try (PreparedStatement ps = conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, order.getGuestId()); ps.setInt(2, order.getRoomId()); ps.setBigDecimal(3, deposit); ps.executeUpdate(); // 拿到自增的订单号,后面消费记录要用 try (ResultSet keys = ps.getGeneratedKeys()) { if (keys.next()) order.setOrderId(keys.getInt(1)); } } // 第二步:更新房态为已入住 String updateRoom = "UPDATE room SET status = 1 WHERE room_id = ? AND status = 0"; try (PreparedStatement ps = conn.prepareStatement(updateRoom)) { ps.setInt(1, order.getRoomId()); int rows = ps.executeUpdate(); if (rows == 0) { // 房间已被别人抢先入住,抛异常触发回滚 throw new SQLException("房间状态已变更,开房失败"); } } // 第三步:记录押金消费 String insertConsume = "INSERT INTO consume " + "(order_id, item, amount, consume_time) VALUES (?, '押金', ?, GETDATE())"; try (PreparedStatement ps = conn.prepareStatement(insertConsume)) { ps.setInt(1, order.getOrderId()); ps.setBigDecimal(2, deposit); ps.executeUpdate(); } conn.commit(); // 三步都成功才提交 return true; } catch (SQLException e) { if (conn != null) conn.rollback(); // 任何异常都回滚 throw e; } finally { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } }关键点在UPDATE room ... AND status = 0这个条件。它是个乐观锁:只有房间当前确实是空闲的,更新才会影响一行。如果返回 0 行,说明在你查询和开房之间有人抢先操作了,直接抛异常回滚。GETDATE()用数据库时间而不是 Java 的new Date(),避免客户端时区不一致。RETURN_GENERATED_KEYS拿自增主键,SQL Server 需要驱动支持,mssql-jdbc是支持的。
3.3 退房结算:押金抵扣和消费汇总
退房要算总账:把该订单下所有消费记录求和,加上房费,减去押金,多退少补。房费按实际入住天数乘房价,天数用DATEDIFF在 SQL 里算:
public BigDecimal checkOut(int orderId) throws SQLException { String sql = "SELECT o.deposit, r.price, " + "DATEDIFF(DAY, o.check_in_time, GETDATE()) AS days, " + "ISNULL((SELECT SUM(amount) FROM consume WHERE order_id = o.order_id), 0) AS total_consume " + "FROM orders o JOIN room r ON o.room_id = r.room_id " + "WHERE o.order_id = ?"; // ... 执行查询,计算 finalAmount = days * price + total_consume - deposit // 然后在一个事务里:更新订单状态为已退房、房态改回 0、插入结算记录 }DATEDIFF(DAY, ...)算出来的是整数天,如果当天入住当天退,结果是 0 天,实际业务里通常按 1 天收,所以代码里要Math.max(days, 1)。ISNULL处理没有消费记录的情况,否则SUM返回NULL会让整个计算变成NULL。结算同样要包事务,退房和改房态必须一起成功。
4. 避坑与排查:课设里最容易翻车的五个地方
4.1 驱动报“找不到或无法加载主类”
现象是运行时报ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver。原因通常是 jar 包没加到 classpath,或者加的是旧版sqljdbc4.jar而代码里写的驱动类名不匹配。解决:在 IDE 的 Project Structure 里把mssql-jdbc-9.4.1.jre8.jar加到 Libraries,确认 jar 版本和 JDK 版本对应(jre8 对应 JDK 8,jre11 对应 JDK 11+)。命令行运行时用java -cp ".;lib/mssql-jdbc-9.4.1.jre8.jar" com.hotel.ui.MainFrame,Windows 下分隔符是分号。
4.2 中文乱码:数据库存进去变问号
现象是往guest表插中文姓名,查出来全是???。原因是 SQL Server 的排序规则不是中文,或者 JDBC URL 没指定字符集。解决:建库时用COLLATE Chinese_PRC_CI_AS,JDBC URL 加;sendStringParametersAsUnicode=true。另外 Swing 的JTextField取值本身是 Unicode,问题一般出在数据库端,先查SELECT SERVERPROPERTY('Collation')确认排序规则。
4.3 事务没生效:回滚了数据还在
现象是开房失败后,订单表里居然有记录。原因多半是conn.setAutoCommit(false)之后,某条 SQL 用的是另一个连接——比如在service里调了dao的方法,而dao内部自己DBUtil.getConnection()开了新连接。事务只对同一个Connection有效。解决:service层把Connection作为参数传给dao方法,或者用ThreadLocal绑定当前线程的连接。课设里最简单的做法是dao方法统一接收Connection参数。
4.4 Swing 界面卡死:查询大数据量时白屏
现象是点击“查询全部订单”后窗口无响应。原因是查询在事件分发线程(EDT)里同步执行,数据库慢的时候 EDT 被阻塞,界面不重绘。解决:用SwingWorker把查询放到后台线程,done()方法里再更新表格模型。课设数据量小可能感觉不到,但答辩时如果老师让你演示“查所有历史订单”,卡一下就很尴尬。
4.5 主键冲突:自增列手动插值报错
现象是插入订单时报“不能将显式值插入标识列”。原因是orders表的order_id设了IDENTITY(1,1),但 SQL 里写了INSERT INTO orders (order_id, ...) VALUES (?, ...)。解决:自增列不要出现在INSERT的列清单里,让数据库自己生成。如果确实需要指定值(比如数据迁移),先执行SET IDENTITY_INSERT orders ON,插完再OFF。
5. 二次开发与改造:从课设到能演示的完整系统
拿到这套代码后,最值得先改的是界面。Swing 默认的 Metal 主题在投影仪上偏灰,换成 Nimbus 或系统主题只要一行:
try { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); } catch (Exception e) { // 换肤失败不影响功能,忽略即可 }放在main方法最前面,所有窗口都会跟着变。如果想让房态图更直观,可以把RoomPanel里的JTable换成自定义的JPanel网格,每个房间一个带颜色的小方块,绿色空闲、红色已住、灰色维修,点击方块直接弹开房对话框。这个改造大概两百行代码,但演示效果提升明显。
功能上,优先补两个模块。一是预订管理:在orders表加一个order_type字段区分“入住”和“预订”,预订时房态改成3,到店后走“预订转入住”流程,把房态从3改成1。二是简单报表:用JFreeChart画一张“近七天入住率”柱状图,数据从orders表按天GROUP BY查出来。这两个模块加上去,答辩时能讲的东西就从“增删改查”变成“业务闭环”。
改造时有个习惯我一直保持:每加一个功能,先在sql目录下写对应的建表或改表脚本,再动 Java 代码。课设最容易乱的就是数据库结构,今天加个字段明天改个类型,最后hotel_db.sql和实际库对不上,换台机器就建不起来。把脚本当成唯一事实来源,代码跟着脚本走,迁移和复现都省心。希望帮到你。
本文还有配套的精品资源,点击获取