Java酒店管理系统课设:Swing+JDBC+MySQL从设计到答辩完整指南

发布时间:2026/10/26 7:31:03
Java酒店管理系统课设:Swing+JDBC+MySQL从设计到答辩完整指南

简介:这是一份面向Java课程期末大作业的酒店管理系统完整项目包,适合正在做课设、毕业设计或期末项目的在校生。系统围绕客房预订、入住登记、退房处理、账单结算等核心业务展开,代码中覆盖类与对象、继承多态、异常处理、JDBC/SQL数据库交互、Swing/JavaFX界面及多线程同步等要点,能帮助读者理解一个完整管理系统如何分层设计与实现;各业务模块通过数据访问层与界面层解耦,便于复用和二次开发。压缩包共29个文件、约9.7MB,含11个Java源码文件、1份设计报告doc文档以及jpg/png界面截图;源码构成可运行主体,报告可用于撰写课程设计说明、梳理需求与模块划分,截图便于对照界面和预期效果。已有1641人学习/下载,适合需要借鉴项目结构、数据库表和基础设计模式(如MVC、单例、工厂)的Java学习者。

1. Java 期末大作业的酒店管理系统:它要的是什么,又卡住了谁

期末拿到「酒店管理系统源代码+设计报告」这个题目时,大多数人第一反应是去网上找一份能跑的源码,改个包名就交。这个思路本身没错,但真正卡住人的从来不是代码能不能跑,而是答辩现场老师随口一句「你这个房间状态怎么流转的」「预订 and 入住有什么区别」,你答不上来。这门课的及格线不在代码写得多高级,而在三件事:代码和报告能对上、现场能独立跑通、每一个出现在报告里的类和方法你都能讲明白。本文按我做这类课设的完整路径,把技术选型、数据库设计、核心代码、常见翻车点和报告写作顺序写清楚,适合正在做 Java 期末大作业、需要交付源码加设计报告的同学,也适合想快速搭一个管理类系统模板的初级开发者。

2. 技术选型:为什么期末场景下 Swing + JDBC 仍然是最稳的组合

2.1 三条主流技术路线的取舍

酒店管理系统作为期末大作业,常见做法有三条路线:Swing + JDBC + MySQL 的桌面客户端、JavaFX + MyBatis 的桌面程序、Spring Boot + Vue 的 Web 前后端分离。很多同学一上来就想做 Web,觉得好看、有面子,但忽略了一个事实:期末大作业的验收重点是「代码结构是否清晰、设计报告是否完整、答辩能否讲清楚」,而不是界面多炫。

三条路线的实际差距,集中在这张表里:

对比项Swing + JDBC + MySQLJavaFX + MyBatisSpring Boot + Vue
学习成本低,Swing 控件拖拽即可中,需要理解 FXML 和属性绑定高,前后端都要熟
答辩风险低,代码直观中,涉及线程与生命周期高,随便一个 CORS 或依赖问题就黑匣子
工作量5 张表 + 3 个界面就能交界面更现代但工作量不减需要额外写接口文档
环境要求JDK + MySQL + 一个 jarJDK + JavaFX SDKJDK + Maven + Node
报告匹配度高,三层结构好画图中低,容易写虚

我一般会直接建议选第一条路线。原因很实际:Swing 是 JDK 自带的,不需要额外装运行时;JDBC 是 Java 基础课必讲的内容,老师问到底层你不会露怯;MySQL 在课程设计里是标配。等你真把 Swing 这版做完了,想升级成 Web 版,业务逻辑和 DAO 层是可以平移的,不算白做。

2.2 为什么用 JDBC 而不是 MyBatis

很多热词搜「mybatisplus根据java实体类生成创建表的sql语句」,说明现在培训班和网课都在推 MyBatis Plus。但期末大作业里引入 MyBatis 会带来三个新问题:第一,你需要额外引入 mybatis.jar 和 mybatis-plus.jar,而且版本要和 JDK 匹配;第二,MyBatis 的 mapper XML 和注解对新手是个黑匣子,一个 namespace 写错就整站跑不起来;第三,设计报告里你得花篇幅解释 ORM 的原理,答辩时反而多了一堆可被追问的点。

JDBC 的问题在于代码啰嗦,但它的好处是「每一条数据怎么来的、每一行代码在干什么」都是透明的。期末作业的场景下,透明比简洁更重要。一个 Connection、三个 Statement 方法、两个 ResultSet 读取,这些本身就是考点,你写在报告里,老师只会觉得你基础扎实。数据库连接参数集中放在 db.properties 里,后续上线给 MySQL 换端口也只需要改一行。

2.3 三层结构怎么落包

期末大作业最常见的做法是三层结构:view 层放 Swing 界面,dao 层放数据库操作,entity 层放实体类,另外加一个 util 包放 DBUtil 这样的通用工具。包结构按这个习惯来:

src/ entity/ Admin.java Room.java Customer.java Reservation.java dao/ AdminDao.java RoomDao.java ReservationDao.java util/ DBUtil.java view/ LoginFrame.java MainFrame.java resources/ db.properties

为什么这么分?因为设计报告里通常要求你画「包图」和「类图」,你分包分得越清晰,图越好画。我见过不少同学把所有类堆在 default 包下面,报告里那张包图画出来自己都嫌丢人。另外,三层结构天然对应报告里的「表现层—业务逻辑层—数据访问层」描述,写概要设计那一章时几乎可以直接复用。

3. 数据库设计:从 ER 设计到建表 SQL,五张表结构与房间状态机

3.1 实体关系与字段约定

酒店管理系统的核心业务链是「管理员登录 → 客户登记 → 预订客房 → 入住 → 退房结账」。围绕这条链,我一般会设计五张表:管理员表 admin、客户表 customer、客房类型表 room_type、客房表 room、预订表 reservation。为什么不把客户和预订合并?因为一个客户可能多次入住,预订记录又有自己的入住和退房时间,拆开以后写 SQL 会更顺,报告里的 E-R 图也更好看。

字段设计有几个约定要提前定好:主键统一用自增 id;时间字段用 datetime 而不是 varchar;金额字段用 decimal(10,2) 而不是 double,因为 double 在涉及金额累加时会有浮点误差,虽然期末作业数据量小看不出来,但老师说出去会显得你很业余。密码字段用 varchar(32) 存 MD5 的十六进制结果,具体加密逻辑放后面代码章。房间状态我习惯用整数 0/1/2/3 表示「空闲/已预订/已入住/脏房」,这样一条 update 就能完成状态流转,比字符串状态写得快,也比布尔字段扩展性强。

3.2 建表 SQL:基于 MySQL 8 的可复现脚本

下面是这套系统的核心建表脚本,MySQL 5.7 和 8.0 都能直接跑。注意设置了 utf8mb4 字符集,否则后面存中文姓名和备注必乱码。

CREATE DATABASE IF NOT EXISTS hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hotel_db; CREATE TABLE admin ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password CHAR(32) NOT NULL COMMENT 'MD5之后的值' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE customer ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, id_card VARCHAR(18) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE room_type ( id INT AUTO_INCREMENT PRIMARY KEY, type_name VARCHAR(30) NOT NULL, price DECIMAL(10, 2) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE room ( id INT AUTO_INCREMENT PRIMARY KEY, room_no VARCHAR(10) NOT NULL UNIQUE, type_id INT NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0空闲 1已预订 2已入住 3脏房', CONSTRAINT fk_room_type FOREIGN KEY (type_id) REFERENCES room_type(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE reservation ( id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, room_id INT NOT NULL, check_in_date DATE NOT NULL, check_out_date DATE NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT NOT NULL DEFAULT 0 COMMENT '0生效 1已完成 2已取消', CONSTRAINT fk_res_customer FOREIGN KEY (customer_id) REFERENCES customer(id), CONSTRAINT fk_res_room FOREIGN KEY (room_id) REFERENCES room(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段脚本的关键点有三个:一是 room 表用 type_id 外键关联 room_type,这样标准间、大床房、行政房的价格只维护一份,后期涨价只需 update 一张表;二是 reservation 表同时关联 customer 和 room,E-R 图上能画出两个一对多关系,报告内容更饱满;三是所有表都加上了外键约束,老师在检查数据库时看到「关系」是真实存在的,而不是只在文档里画着好看。

3.3 初始数据:让系统第一次启动不是空的

建完表之后要给几间房和一条管理员账号,不然程序启动后查不到数据,你还以为是代码坏了。管理员账号我一般手工插入一条密码为 MD5 值的数据,MD5 的生成后面代码章的 Login 模块会处理,这里先用一个已知值。

INSERT INTO admin (username, password) VALUES ('admin', '21232f297a57a5a743894a0e4a801fc3'); INSERT INTO room_type (type_name, price) VALUES ('标准间', 168.00), ('大床房', 218.00), ('行政套房', 388.00); INSERT INTO room (room_no, type_id, status) VALUES ('101', 1, 0), ('102', 1, 0), ('103', 1, 0), ('201', 2, 0), ('202', 2, 0), ('301', 3, 0), ('302', 3, 0);

这套初始数据的妙处在房间编号和房型 id 直接对应:1 楼是标准间,2 楼是大床房,3 楼是行政房。演示的时候老师说「给我开一间 2 楼的房间」,你在界面上按房号就能定位,这种细节在答辩时非常加分。

4. 核心源代码逐段拆:JDBC 连接、DAO 查询与登录事件怎么串起来

4.1 配置文件与数据库连接入口

先建 db.properties 放在 resources 目录下,所有连接的参数集中在这里。用文件而不是在代码里硬编码,这是我踩过坑后的习惯:有一年我直接写在 DBUtil 里,换一台电脑演示,MySQL 密码不一样,不得不现场改代码重新编译,场面极其尴尬。

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/hotel_db?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=你的数据库密码

这里的每一项都有讲究。driver 用com.mysql.cj.jdbc.Driver是 MySQL 8.x 对应的驱动类名,如果你用的是 mysql-connector-java 5.1.x,类名是com.mysql.jdbc.Driver,新版还保留了这个类名做兼容,但会打印 deprecation 告警,别慌,不是错误。URL 里useSSL=false是必须的,否则 MySQL 8 默认尝试 SSL 握手,可能报 SSL 异常;characterEncoding=utf8保证中文不会变成问号;serverTimezone=Asia/Shanghai解决驱动和 MySQL 时区不一致导致的时间报错;allowPublicKeyRetrieval=true是 MySQL 8 在非 SSL 连接下会遇到的坑,不加会在首次连接时报「Public Key Retrieval is not allowed」,这一段就是传说中的期末大作业玄学重灾区。

接下来是最常用的 DBUtil。类只有一个静态方法 getConnection,读取 properties 文件并返回连接,关闭连接的 close 重载我一般写三个,分别处理 Connection、Statement、ResultSet。

package util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class DBUtil { private static String url; private static String username; private static String password; static { try (InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties")) { Properties props = new Properties(); props.load(in); Class.forName(props.getProperty("jdbc.driver")); url = props.getProperty("jdbc.url"); username = props.getProperty("jdbc.username"); password = props.getProperty("jdbc.password"); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, 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) {} } }

逻辑说明:静态代码块在类第一次被加载时执行,读取配置文件并注册驱动,实际连接在 getConnection 里通过 DriverManager 创建。这样整个项目里所有 DAO 不需要关心驱动注册,只需要调用DBUtil.getConnection()。参数说明:getResourceAsStream 的路径是相对于 classpath 的,db.properties 必须放在源码目录的 resources 根下,否则返回 null,加载空文件后连接参数全是空,运行时报 NullPointerException。

4.2 RoomDao:查询空房与预订时的状态流转

酒店管理系统最核心的一张表是 room,最核心的 DAO 方法就两个:查可用房、在预订时扣房。这里直接查状态为 0 的空房,并按房号排序。

package dao; import entity.Room; import util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class RoomDao { public List<Room> findAvailableRooms() { List<Room> list = new ArrayList<>(); String sql = "SELECT r.id, r.room_no, t.type_name, t.price " + "FROM room r JOIN room_type t ON r.type_id = t.id " + "WHERE r.status = 0 ORDER BY r.room_no"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Room room = new Room(); room.setId(rs.getInt("id")); room.setRoomNo(rs.getString("room_no")); room.setTypeName(rs.getString("type_name")); room.setPrice(rs.getBigDecimal("price")); list.add(room); } } catch (SQLException e) { e.printStackTrace(); } return list; } }

这段代码用了 try-with-resources,连接、语句、结果集在 try 结束后自动关闭,不用手动调 close。JOIN 查询比先查 room 再查 room_type 少一次数据库往返,数据组装也顺手把房型名和价格带出来了,界面显示时不用再查字典表。返回值是 List 而不是 ResultSet,这是 DAO 层的基本要求:ResultSet 依赖连接,连接关了就没了,转成实体类列表后界面层才能安全使用。

预订时的状态更新一般放在一个事务里。先检查房间当前状态,如果已被占就抛异常,否则把状态从 0 改成 1,同时往 reservation 表插入记录。熟练的话可以直接在一个 DAO 里完成,但期末作业我建议拆开:RoomDao.updateStatus 负责改状态,ReservationDao.insert 负责插记录,由业务层 Service 类控制事务边界。这样设计报告的类图能多画一个 Service 类,时序图也更完整。

4.3 登录逻辑:MD5 校验与 Session 模拟

登录是每个管理系统的门面。前端的输入框只是一个文本框,核心在 AdminDao 里。密码不能明文比对,因为数据库里存的是 MD5。MD5 虽然不安全,但作为课程设计,老师看到你做了散列存储已经算及格了。

package dao; import java.security.MessageDigest; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class AdminDao { public static String md5(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(input.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException("MD5加密失败", e); } } public boolean login(String username, String password) { String md5Pwd = md5(password); String sql = "SELECT id FROM admin WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Pwd); try (ResultSet rs = ps.executeQuery()) { return rs.next(); } } catch (Exception e) { e.printStackTrace(); return false; } } }

参数说明:String.format("%02x", b)是把每个字节转成两位十六进制,这样无论输入是什么,出来的 MD5 一定是 32 位小写字符串,和建表时手动插入的21232f297a57a5a743894a0e4a801fc3能对上。登录判断用 rs.next() 返回是否存在记录,不要用 SELECT COUNT(*) 再取数,少一层取值逻辑。密码输入框建议用 JPasswordField 而不是 JTextField,后者会把密码明文显示在界面上;用new String(passwordField.getPassword())取内容时,拿到的是 char 数组,转成 String 时注意判空。

Swing 登录按钮的事件监听器写法如下,这也是期末作业里最常见的「复用模板」代码:

btnLogin.addActionListener(e -> { String username = usernameField.getText().trim(); String password = new String(passwordField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(LoginFrame.this, "用户名和密码不能为空"); return; } AdminDao dao = new AdminDao(); if (dao.login(username, password)) { JOptionPane.showMessageDialog(LoginFrame.this, "登录成功"); new MainFrame().setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(LoginFrame.this, "用户名或密码错误"); } });

这里的dispose()是关键小细节:登录成功后关闭当前登录窗口,否则演示时两个窗口叠在屏幕上,老师会觉得你连窗口生命周期都没理清。界面的布局直接用 Swing 默认的 FlowLayout 就能交差,不需要引入任何第三方美化库,重点是事件监听器里的业务逻辑要完整。

5. 期末作业避坑:五个让现场演示翻车的坑与处理记录

5.1 驱动类找不到:ClassNotFoundException 与连接超时

现象:运行程序,点击登录按钮后控制台抛java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,或者代码在 IDE 里编译通过但一执行就报错。

原因:代码能编译是因为 IDE 的 classpath 里有 jar,但运行时没有。最常见的情况是把 mysql-connector-java 的 jar 放到了某个文件夹,却没添加到项目构建路径;或者换了机器之后,jar 还在原路径,新机器上根本找不到。

解决:确认 jar 确实在项目里。Eclipse 里右键项目 → Build Path → Add External Archives;IDEA 里 File → Project Structure → Modules → Dependencies,添加 jar 后勾选Compile和Runtime两个范围。注意检查 jar 是否被版本控制忽略,有些同学的工程同步到网盘或 git 时把 jar 漏了,换电脑 clone 下来才发现缺。

5.2 MySQL 8 的 SSL 与公钥校验:Public Key Retrieval is not allowed

现象:连接字符串没带额外参数,首次执行查询时报Public Key Retrieval is not allowed,或者报Communications link failure、SSL connection error。

原因:MySQL 8.x 默认使用 caching_sha2_password 认证插件,在非 SSL 连接下首次认证需要获取服务器公钥。驱动为了安全不主动公钥检索,就需要在 URL 里显式放行。

解决:在 db.properties 的 url 里补上allowPublicKeyRetrieval=true&useSSL=false,这是接 MySQL 8 最省事的两行参数。如果是 5.7 的库,加了也不会报错,只是多余。

5.3 中文乱码:界面和数据库都是问号

现象:管理员在界面上输入「张三」,点保存,再查出来变成「???」,或者界面上直接显示乱码。

原因:乱码有两条链:一是数据库端,建库时没指定 utf8mb4,用了默认的 latin1;二是 JDBC 连接端,URL 没带 characterEncoding=utf8,驱动用客户端系统默认编码发送中文,两边编码不一致。两个问题只要中了一个就会乱。

解决:先把已在用的库改成 utf8mb4,按第 3 章的建库脚本重建最干净;然后确认连接 URL 带着characterEncoding=utf8。如果界面本身还是乱码,检查源码文件编码是不是 UTF-8,Windows 下 IDEA 默认是 UTF-8,但老版本 Eclipse 可能是 GBK,在 Run Configuration 的 VM options 里加-Dfile.encoding=UTF-8能救回大部分场景。

5.4 导出可运行 jar 后跑不起来:配置文件没打进去

现象:IDE 里一切正常,导出 Runnable JAR 后双击或命令行运行,报Exception in thread "main" java.lang.NullPointerException,定位到 DBUtil 的 props.load(in)。

原因:Eclipse/IDEA 导出 jar 时默认不会把 resources 目录打进去,或者打了但路径不对,getResourceAsStream 返回 null。

解决:导出时在打包选项中把 resources 目录勾进来;更稳的做法是把 db.properties 放到 src 根目录而不是单独的 resources 目录,这样导出时一定在 classpath 里。另外发布演示时,MySQL 服务和数据库要在同一台机器上并处于启动状态,我见过太多人演示到一半才发现 MySQL 服务没开,然后当众等启动。

5.5 设计报告和代码对不上:字段名、类名、功能全不一致

现象:报告里的类图画的 AdminService 在代码里根本不存在;数据库表字段明明是 create_time,报告表格里写成了 reg_time;报告说做了「会员折扣」,点进程序根本没有这个功能。

原因:报告用别人的模板改了改,代码也是东拼西凑,两份材料互相独立。这在查重环节和答辩时是致命的——老师随便抽一个类问「这个是干嘛的」,你翻代码找不到,现场就会陷入沉默。

解决:写完代码之后再写报告,以代码为准。报告里每个类、每个方法都要在代码里真实存在;数据库表结构直接从数据库里导出来贴进报告,而不是凭记忆写。自己先跑一遍页面,确认哪些功能做完、哪些没有,报告里只写做出来的部分,宁少勿假。

6. 设计报告与答辩:怎么把报告写得像自己写的

报告的结构我一般按软件工程的标准模板走,但每一章都压缩到能对着代码讲出来的程度:一、需求分析,画一个用例图,写 5 条功能性需求加 2 条非功能性需求,别贪多;二、概要设计,放系统架构图加 E-R 图,E-R 图直接用数据库里的表倒推;三、详细设计,类图和时序图,类图里的每个类都要能在 src 里找到;四、数据库设计,直接贴第 3 章那五张表的建表 SQL,加一张字段说明表;五、测试,写一张测试用例表,记录「输入正确的密码能登录、输入错误密码会提示」这类真实跑过的手工测试;六、总结,写你遇到了哪个坑、怎么解决的。

画图工具用 draw.io 或 ProcessOn 都行,重要的是图和你代码一致。时序图画「预订房间」这一个场景就够:客户点预订 → MainFrame 调 ReservationService → Service 调 RoomDao.updateStatus + ReservationDao.insert,画完你就知道答辩时该讲什么。

答辩时老师高频问的问题就那么几个:为什么选 Swing 不选 Web,回答「期末项目周期短,Swing 部署轻量,不需要浏览器兼容」即可;密码为什么是 MD5,回答「防止数据库泄露后明文密码直接暴露」;Room 的状态字段为什么用 int 不用 String,回答「int 比较和状态流转写起来更简洁,也方便后续扩展」。这些问题基本等于 java 八股文里最基础的那几条,提前准备,每个问题准备两句话,不要在台上长篇大论。

最后一个血泪经验:演示前把数据库重启一遍、程序清掉缓存重新跑一遍,不要用 IDE 里开着几小时的旧运行实例,那不算你的程序能跑。我数不清多少次是「刚才还能跑,答辩就挂了」——大多是旧连接、旧缓存、MySQL 没启动,这三个里至少挂一个。希望帮到你。

本文还有配套的精品资源,点击获取