资讯详情 SQL Server + Swing 宾馆管理系统:数据库课设完整实现与避坑指南
📅 2026/10/21 7:58:59
简介这份资源是面向高校计算机相关专业学生的数据库课程设计参考方案以SQL Server作为后台数据库、Java Swing构建桌面端界面实现了一套功能完整的宾馆管理系统适合正在准备数据库课设或需要练习桌面应用与数据库联动开发的初学者参考。压缩包共40个文件约2.02MB包含SQL建库脚本与数据库文件、Java源码及编译后的class文件、可运行jar包以及一份使用说明文档覆盖从建库、编码到运行调试的完整链路。目前已有378人学习下载说明该方案在同类课设中具有一定参考价值。读者可借此了解宾馆管理场景下的数据表设计思路、Swing界面与SQL Server的连接方式以及项目目录组织与打包运行方法快速搭建起自己的课设框架并在此基础上按需扩展功能模块。1. 从一份“能跑起来”的课设说起SQL Server Swing 宾馆管理系统到底能解决什么如果你正在做数据库课设大概率会遇到这种局面选题定了“宾馆管理系统”老师要求必须有数据库、有界面、能演示入住退房但真动手时发现——数据库建表容易Swing 画界面也不难难的是把两者串起来还要让业务逻辑说得通。这份基于 SQL Server Swing 的宾馆管理系统解决的正是这个“最后一公里”问题它把客房、客人、入住、退房、账单这几条核心链路完整跑通前端用 Java Swing 做桌面交互后端用 SQL Server 存数据JDBC 做桥接。适合谁适合数据库课设、Java 课程设计、需要交一个“能演示、能讲清、能改”的完整项目的同学。它不是玩具级 Demo客房状态流转和账单计算都有实际逻辑拿来改改就能当自己的课设交上去。2. 环境搭建与数据库设计从零把 SQL Server 和 JDBC 接通2.1 为什么选 SQL Server 而不是 MySQL课设场景下SQL Server 的优势在于和 Windows 环境贴合、SSMS 图形化操作直观、老师验收时不容易挑刺。MySQL 虽然轻量但很多学校机房默认装的是 SQL Server你交上去的项目如果跑不起来答辩现场就是大型翻车现场。所以选型逻辑很简单跟着验收环境走。这份项目用的是 SQL Server 2019 及以上版本JDBC 驱动用mssql-jdbc连接串里注意encryptfalse这个参数否则新版驱动会强制加密导致连接失败。2.2 建库建表五张核心表撑起整个业务宾馆管理系统的数据模型不复杂但表之间的关系要想清楚。核心表就五张客房表、客人表、入住记录表、账单表、用户表。下面是我一般会用的建表脚本字段和约束都按课设验收标准来-- 创建数据库 CREATE DATABASE HotelDB; GO USE HotelDB; GO -- 客房表记录房间号、类型、价格、状态 CREATE TABLE Room ( RoomID INT PRIMARY KEY IDENTITY(1,1), RoomNumber VARCHAR(10) NOT NULL UNIQUE, RoomType VARCHAR(20) NOT NULL, -- 单人间/标准间/豪华间 Price DECIMAL(10,2) NOT NULL, Status VARCHAR(10) DEFAULT 空闲 -- 空闲/已入住/维修中 ); -- 客人表身份证号做唯一标识 CREATE TABLE Guest ( GuestID INT PRIMARY KEY IDENTITY(1,1), Name VARCHAR(50) NOT NULL, IDCard VARCHAR(18) NOT NULL UNIQUE, Phone VARCHAR(15), Gender VARCHAR(4) ); -- 入住记录表关联客人和房间 CREATE TABLE CheckIn ( CheckInID INT PRIMARY KEY IDENTITY(1,1), GuestID INT FOREIGN KEY REFERENCES Guest(GuestID), RoomID INT FOREIGN KEY REFERENCES Room(RoomID), CheckInTime DATETIME DEFAULT GETDATE(), CheckOutTime DATETIME, Deposit DECIMAL(10,2), -- 押金 Status VARCHAR(10) DEFAULT 在住 -- 在住/已退房 ); -- 账单表退房时生成 CREATE TABLE Bill ( BillID INT PRIMARY KEY IDENTITY(1,1), CheckInID INT FOREIGN KEY REFERENCES CheckIn(CheckInID), RoomFee DECIMAL(10,2), OtherFee DECIMAL(10,2) DEFAULT 0, TotalFee DECIMAL(10,2), BillTime DATETIME DEFAULT GETDATE() ); -- 用户表系统登录用 CREATE TABLE SysUser ( UserID INT PRIMARY KEY IDENTITY(1,1), Username VARCHAR(20) NOT NULL UNIQUE, Password VARCHAR(32) NOT NULL, Role VARCHAR(10) DEFAULT admin );这段脚本的逻辑说明Room.Status字段是整个系统的状态机核心入住时改成“已入住”退房时改回“空闲”维修时锁死不可选。CheckIn表用外键关联客人和房间退房时通过CheckInID找到对应记录生成账单。Bill表把房费和其他消费分开存方便答辩时讲“账单明细”。参数上注意DECIMAL(10,2)表示最多 10 位数、2 位小数金额字段别用FLOAT否则算账时会出现0.30000000000000004这种玄学问题。2.3 JDBC 连接把驱动放进项目里建完表之后下一步是让 Java 能连上数据库。我一般会建一个DBUtil工具类把连接串、用户名、密码集中管理import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 注意encryptfalse 和 trustServerCertificatetrue 缺一不可 private static final String URL jdbc:sqlserver://localhost:1433;databaseNameHotelDB;encryptfalse;trustServerCertificatetrue; private static final String USER sa; private static final String PASSWORD 你的密码; static { try { Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }逻辑说明静态代码块里加载驱动保证类加载时驱动就注册好。getConnection()每次返回新连接课设场景下并发量低不用连接池也扛得住。参数上encryptfalse是必须的SQL Server 新版驱动默认开启加密不加这个参数会报SSL error。trustServerCertificatetrue是配套的跳过证书验证。这两个参数是血泪经验很多人卡在这里一整天。提示如果连接报The TCP/IP connection to the host has failed先去 SQL Server 配置管理器里把 TCP/IP 协议启用端口改成 1433然后重启服务。3. Swing 界面与业务逻辑把客房状态流转写成代码3.1 主界面布局用 JFrame JTable 搭出管理台Swing 做桌面端虽然老但课设够用。主界面我一般用JFrame加BorderLayout左边放功能按钮面板中间放JTable显示客房列表底部放状态栏。客房列表用DefaultTableModel绑定数据从数据库查出来直接塞进去public class MainFrame extends JFrame { private JTable roomTable; private DefaultTableModel tableModel; public MainFrame() { setTitle(宾馆管理系统); setSize(900, 600); setDefaultCloseOperation(EXIT_ON_CLOSE); setLocationRelativeTo(null); // 表格模型列名固定数据动态加载 String[] columns {房间号, 类型, 价格, 状态}; tableModel new DefaultTableModel(columns, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 表格只读防止误改 } }; roomTable new JTable(tableModel); JScrollPane scrollPane new JScrollPane(roomTable); add(scrollPane, BorderLayout.CENTER); // 左侧按钮面板 JPanel btnPanel new JPanel(new GridLayout(6, 1, 5, 5)); btnPanel.add(new JButton(刷新客房)); btnPanel.add(new JButton(办理入住)); btnPanel.add(new JButton(办理退房)); btnPanel.add(new JButton(客人查询)); btnPanel.add(new JButton(账单查看)); btnPanel.add(new JButton(退出系统)); add(btnPanel, BorderLayout.WEST); loadRooms(); // 启动时加载数据 } private void loadRooms() { tableModel.setRowCount(0); // 清空旧数据 String sql SELECT RoomNumber, RoomType, Price, Status FROM Room; try (Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { while (rs.next()) { tableModel.addRow(new Object[]{ rs.getString(RoomNumber), rs.getString(RoomType), rs.getBigDecimal(Price), rs.getString(Status) }); } } catch (SQLException e) { JOptionPane.showMessageDialog(this, 加载客房失败 e.getMessage()); } } }逻辑说明DefaultTableModel重写isCellEditable返回false防止用户直接在表格里改数据导致状态不一致。loadRooms()每次先setRowCount(0)清空再重新查保证界面和数据库同步。参数上getBigDecimal(Price)比getDouble更安全金额计算不会丢精度。3.2 入住与退房状态流转和账单生成入住和退房是整个系统最核心的两个业务动作。入住时要做三件事检查房间是否空闲、插入入住记录、更新房间状态。退房时要做三件事计算房费、生成账单、释放房间。下面以入住为例public boolean checkIn(int guestId, int roomId, BigDecimal deposit) { String checkSql SELECT Status FROM Room WHERE RoomID ?; String insertSql INSERT INTO CheckIn (GuestID, RoomID, Deposit) VALUES (?, ?, ?); String updateSql UPDATE Room SET Status 已入住 WHERE RoomID ?; try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 // 1. 检查房间状态 try (PreparedStatement ps conn.prepareStatement(checkSql)) { ps.setInt(1, roomId); ResultSet rs ps.executeQuery(); if (rs.next() !空闲.equals(rs.getString(Status))) { return false; // 房间不是空闲拒绝入住 } } // 2. 插入入住记录 try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, guestId); ps.setInt(2, roomId); ps.setBigDecimal(3, deposit); ps.executeUpdate(); } // 3. 更新房间状态 try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, roomId); ps.executeUpdate(); } conn.commit(); // 三步都成功才提交 return true; } catch (SQLException e) { e.printStackTrace(); return false; } }逻辑说明这里用conn.setAutoCommit(false)开启事务三步操作要么全成功要么全回滚。如果房间状态检查失败直接return false不会执行后面的插入和更新。参数上PreparedStatement用占位符传参防止 SQL 注入这也是答辩时老师常问的点。退房逻辑类似只是把“插入入住记录”换成“更新退房时间”再插入账单记录最后把房间状态改回“空闲”。注意事务里不要混用Statement和PreparedStatement统一用PreparedStatement否则连接管理容易出问题。3.3 账单计算按天计费与押金抵扣账单计算是课设里最容易讲出亮点的部分。房费按实际入住天数算押金抵扣后多退少补。我一般会写一个calculateBill方法public BigDecimal calculateRoomFee(BigDecimal pricePerDay, Date checkIn, Date checkOut) { long diff checkOut.getTime() - checkIn.getTime(); long days diff / (1000 * 60 * 60 * 24); if (days 0) days 1; // 不满一天按一天算 return pricePerDay.multiply(BigDecimal.valueOf(days)); }逻辑说明diff算出毫秒差除以一天的毫秒数得到天数。days 0时强制设为 1这是宾馆行业的常见规则答辩时能体现你考虑过实际业务。参数上BigDecimal的multiply方法保证精度别用double直接乘。4. 避坑与排查课设验收前必须过的五道坎4.1 连接报错TCP/IP 未启用或端口不对现象Java 程序报Connection refused或The TCP/IP connection to the host localhost, port 1433 has failed。原因SQL Server 默认不启用 TCP/IP 协议或者端口不是 1433。解决打开 SQL Server 配置管理器 → SQL Server 网络配置 → 实例的协议 → 启用 TCP/IP → 双击 TCP/IP 查看 IP 地址页签把 IPAll 的 TCP 端口改成 1433 → 重启 SQL Server 服务。4.2 驱动加载失败ClassNotFoundException现象运行时报java.lang.ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver。原因项目里没引入mssql-jdbc的 jar 包或者版本不匹配。解决去微软官网下载mssql-jdbc-9.x.jre8.jar在 IDE 里添加到 Libraries。如果用 Maven加依赖dependency groupIdcom.microsoft.sqlserver/groupId artifactIdmssql-jdbc/artifactId version9.4.1.jre8/version /dependency4.3 中文乱码数据库排序规则不对现象客人姓名存进去变成问号或者界面显示乱码。原因数据库默认排序规则是SQL_Latin1_General_CP1_CI_AS不支持中文。解决建库时指定COLLATE Chinese_PRC_CI_AS或者建表时对VARCHAR字段改用NVARCHAR。我一般直接在建库语句里加CREATE DATABASE HotelDB COLLATE Chinese_PRC_CI_AS;4.4 事务未回滚入住失败但房间状态改了现象入住时如果插入记录失败房间状态却已经变成“已入住”数据不一致。原因没有用事务或者commit和rollback逻辑写错。解决把三步操作包在setAutoCommit(false)和commit()之间catch块里加conn.rollback()。注意rollback要判空避免conn为 null 时二次异常。4.5 界面卡死在 EDT 里做数据库查询现象点击“刷新客房”后界面卡住几秒按钮点不动。原因Swing 的事件分发线程EDT里直接做了数据库查询阻塞了 UI 渲染。解决把耗时操作放到SwingWorker里doInBackground查数据done方法里更新表格。课设数据量小不优化也能过但答辩时老师如果问“界面为什么卡”你能答上来就是加分项。5. 进阶技巧把课设改成“能讲出花”的版本5.1 用存储过程封装退房逻辑如果想让答辩更有深度可以把退房逻辑写成存储过程Java 端只负责调用。这样业务逻辑集中在数据库层老师问“为什么用存储过程”时你可以答“减少网络传输、保证事务一致性”。示例CREATE PROCEDURE CheckOut CheckInID INT, OtherFee DECIMAL(10,2) AS BEGIN SET NOCOUNT ON; DECLARE RoomID INT, CheckInTime DATETIME, Price DECIMAL(10,2); DECLARE Days INT, RoomFee DECIMAL(10,2), TotalFee DECIMAL(10,2); -- 获取入住信息 SELECT RoomID RoomID, CheckInTime CheckInTime FROM CheckIn WHERE CheckInID CheckInID; -- 计算天数 SET Days DATEDIFF(DAY, CheckInTime, GETDATE()); IF Days 0 SET Days 1; -- 获取房价 SELECT Price Price FROM Room WHERE RoomID RoomID; SET RoomFee Price * Days; SET TotalFee RoomFee OtherFee; -- 更新入住记录 UPDATE CheckIn SET CheckOutTime GETDATE(), Status 已退房 WHERE CheckInID CheckInID; -- 插入账单 INSERT INTO Bill (CheckInID, RoomFee, OtherFee, TotalFee) VALUES (CheckInID, RoomFee, OtherFee, TotalFee); -- 释放房间 UPDATE Room SET Status 空闲 WHERE RoomID RoomID; ENDJava 端调用public boolean checkOut(int checkInId, BigDecimal otherFee) { String sql {call CheckOut(?, ?)}; try (Connection conn DBUtil.getConnection(); CallableStatement cs conn.prepareCall(sql)) { cs.setInt(1, checkInId); cs.setBigDecimal(2, otherFee); cs.execute(); return true; } catch (SQLException e) { e.printStackTrace(); return false; } }逻辑说明存储过程把退房的所有步骤封装在数据库端Java 只传CheckInID和其他费用。DATEDIFF(DAY, ...)算天数IF Days 0处理不满一天的情况。参数上CallableStatement用{call 过程名(?, ?)}的语法调用占位符按顺序传。5.2 验证方法用 SQL 脚本做回归测试改完代码后怎么确认没改坏我一般会写一段 SQL 脚本手动跑一遍完整流程-- 1. 插入测试客人 INSERT INTO Guest (Name, IDCard, Phone, Gender) VALUES (测试客人, 110101199001011234, 13800000000, 男); -- 2. 查看空闲房间 SELECT * FROM Room WHERE Status 空闲; -- 3. 模拟入住假设 RoomID1, GuestID1 INSERT INTO CheckIn (GuestID, RoomID, Deposit) VALUES (1, 1, 200); UPDATE Room SET Status 已入住 WHERE RoomID 1; -- 4. 模拟退房 EXEC CheckOut CheckInID 1, OtherFee 50; -- 5. 验证结果 SELECT * FROM Bill WHERE CheckInID 1; SELECT Status FROM Room WHERE RoomID 1; -- 应该是“空闲”这套脚本跑一遍如果账单金额对、房间状态回到空闲说明核心链路没问题。答辩前我习惯把这段脚本存成.sql文件现场演示时直接执行比点界面更稳。5.3 一个让我长记性的细节有次帮人看一个类似的课设退房后房间状态死活改不回来查了半天发现是UPDATE Room SET Status 空闲 WHERE RoomID RoomID里的RoomID在存储过程里没赋值一直是NULL。从那以后我每次写存储过程都在BEGIN之后先把所有变量SELECT一遍确认有值再往下写逻辑。这个习惯帮我省了至少三次通宵排查。希望帮到你。本文还有配套的精品资源点击获取