简介:这是一份基于Java开发的月亮湾酒店管理系统完整源码与设计文档,面向需要完成Java课程设计、毕业设计或学习桌面端信息管理系统的读者。系统采用Java图形界面技术,配合SQL Server 2008 R2数据库,覆盖预订、团队、个人、查询等酒店管理核心功能模块。压缩包共350个文件,以java源码、class编译文件、gif界面截图、jar依赖包和SQL脚本为主,附带doc格式的概要设计、数据库设计、需求说明、详细设计与用户手册,另含数据库文件,总大小约9.74MB,结构完整。目前已有101人学习。读者可借助这些文档快速理解项目架构与业务逻辑,直接参考数据库表设计,并利用源码与界面图进行功能演示或二次开发,是一份适合入门到进阶的Java实战参考资料。
1. 月亮湾酒店管理系统:一份能直接复现的 Java + SQL Server 课程设计源码
月亮湾酒店管理系统是我最近拆过的最能代表 Java 课程设计形态的一份资源:整套代码是纯 Java 桌面程序,类名直接对应散客、团队、预订、查询这些业务模块;数据库用的是 SQL Server 2008 R2 的脚本,SQL Server 2008 以后的版本都执行得了。比那些只有代码没有文档的源码包实在的是,它还带着需求说明书、概要设计、详细设计、数据库设计和用户手册,做软件工程课程设计答辩时,文档这块基本不用重写。适合正在做酒店管理系统课程设计的学生,也适合想用最快速度把“Java + Swing + JDBC + SQL Server”这条链路跑通的从业者。下面直接进入复现流程。
2. 环境准备与数据库还原:先让月亮湾在 SQL Server 上把根扎住
2.1 运行环境选型
这类老式 Java 项目最怕在环境上较劲。不要一上来就装最新版 JDK 17 去跑一个十年前风格的 Swing 项目,模块化之后 Class.forName 和反射调用很容易出兼容问题。我的做法是照着资源本身的年代感配环境:JDK 1.8 足够,Eclipse 或 MyEclipse 打开工程最顺手,数据库按摘要说明的 SQL Server 2008 R2 来装,2008 以上的版本也可以,但别拿最新发布版去自找麻烦——排序规则和旧脚本的兼容性会多出不少变量。
| 组件 | 推荐选型 | 理由 |
|---|---|---|
| JDK | 1.8(64位) | 兼容老 Swing 代码,Class.forName 加载驱动稳定 |
| IDE | Eclipse / MyEclipse | 老工程导入方便,导出可运行 jar 省事 |
| 数据库 | SQL Server 2008 R2 或 2012 | 脚本基于 2008R2,验证成本最低 |
| JDBC 驱动 | sqljdbc4.jar | 匹配 JDK 1.8,连接字符串写法通用 |
| 操作系统 | Windows 7/10/11 | 工具齐全,兼容性最好 |
选 JDK 1.8 而不是 1.6 的原因是:JDK 1.6 在 64 位 Windows 上部署麻烦,很多函数和行为接口差异会让老代码在事件监听上出怪问题;JDK 1.8 还保留了对 Swing 和 AWT 的完整支持,同时又没有 JDK 9 以后的模块化限制。数据库方面,如果你的机器已经装了高版本 SQL Server,也不是不能跑,但执行脚本前要先确认数据库的兼容级别,通常在 SSMS 里右键数据库,属性→选项→兼容级别改成 SQL Server 2008 (100) 就行。
另外注意,SQL Server 2008 R2 的安装包网上容易找,但安装时不要勾选不必要的功能,尤其是 Reporting Services 这种用不到的服务,装了只是多占资源。SQL Server Management Studio 可以直接用 18.x 或 19.x 版本连接 2008 实例,不必非得装老版本 SSMS,省去不少兼容性折腾。
2.2 还原数据库的两种方式
拿到资源后,第一步不是急着打开源码,而是先把数据库建好。摘要里明确写了这份资源和 SQL Server 数据库强绑定,里面的 SQL 脚本包含建库、建表和初始数据。用 SQL Server Management Studio(SSMS)操作最直观:新建一个数据库,名字可以和脚本里的库名保持一致;然后选中这个库,打开“月亮湾酒店管理系统数据库sql”脚本,执行整个脚本,脚本会把表结构和基础数据一次性灌进去。如果脚本里本身就有 CREATE DATABASE,那就直接执行整个脚本,让 SQL Server 自己建库。
命令行方式适合你打算把环境自动化的情况。打开一个管理员 PowerShell,先确认 sqlcmd 在 PATH 里,然后执行:
sqlcmd -S localhost -U sa -P 你的密码 -i "月亮湾酒店管理系统数据库sql.sql"sqlcmd 的 -S 参数指定服务器和实例,默认实例直接写 localhost;-U 和 -P 是登录账号和密码;-i 后面跟脚本文件路径。执行完以后,如果你在 SSMS 里能看到建好的表,就说明脚本执行成功。这一步做完,后面所有代码的连接测试才有对象。
执行脚本时经常遇到一种情况:脚本中途报错停在某一行,建了一半个结构。这不是脚本坏了,通常是脚本里用了高版本语法或者排序规则跟当前实例不一致。你只要把报错信息记下来,把那一行附近的操作拆出来单独执行就好,不必反复整包重跑。脚本执行完之后,顺手查一下核心表里有没有数据,比如客人信息表、房态表、预订表,用 SELECT TOP 10 * FROM 表名 看一眼。有数据才代表资源里的演示账号能登录,没有数据就得回到脚本里找初始化 INSERT 语句是不是被注释掉了。
2.3 JDBC 连接配置怎么看、怎么改
代码跑起来前,系统总要拿到数据库连接。这类老项目常用两种方式存连接信息:要么写在一个 DB.java、DBHelper.java 这种公共类里,要么直接散落在各个窗体类的构造方法里。月亮湾这套从类名看,Setup.class 大概率承担了初始化连接的职责,你在 Eclipse 里搜索 jdbc:sqlserver 或 getConnection,就能把连接配置位置找出来。搜不到的话,试试在工程里搜 .properties 配置文件,有些老项目会把数据库配置放在 config.properties 里用 Properties 类读。
找到以后,连接字符串的格式通常是这样的:
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); String url = "jdbc:sqlserver://localhost:1433;DatabaseName=MoonBayHotel"; String user = "sa"; String password = "123456"; Connection conn = DriverManager.getConnection(url, user, password);Class.forName 负责把 SQL Server 的 JDBC 驱动类加载进 JVM;url 里的 localhost 是服务器地址,1433 是 SQL Server 默认 TCP 端口,DatabaseName 对应你要连接的数据库名。用户名和密码按你自己环境里 sa 的真实密码改。这里有个关键点:如果 SQL Server 装的默认实例,服务器名写成 localhost 就行;如果是命名实例,要写成 localhost\实例名,端口也可能不是 1433。
为了确认连接配置真的没问题,我建议你顺手写一个几行的测试类,把连接建立起来:
public class TestConn { public static void main(String[] args) throws Exception { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); Connection conn = DriverManager.getConnection( "jdbc:sqlserver://localhost:1433;DatabaseName=MoonBayHotel", "sa", "123456"); System.out.println("连接成功: " + !conn.isClosed()); conn.close(); } }isClosed 返回 false 表示连接是通的;如果抛异常,异常信息会直接告诉你是驱动问题、账号问题还是网络问题,前期的排查目标一下子就清晰了。把这一行改对,再运行不同业务类,基本就能看到数据库交互的迹象。接下来再去反向读源码,就容易多了。
3. 从 class 到业务模块:把散客、团队、预订和查询拆开读
3.1 从类名反推模块设计
这份资源不仅给了源码,还能看到编译后的 class 文件和 off.bmp 这类资源,等于把系统的骨架直接摆在那里。类名本身就是最好的模块地图。Setup.class 是环境初始化入口,HotelFrame.class 是主窗体,Individual.class 和 Eindividual.class 对应散客业务,Team.class 对应团队业务,Engage.class 和 EngageInfo.class 对应预订主记录和预订明细,Query.class 是查询模块。off.bmp 是界面资源图,通常是某个按钮的背景或关闭图标,资源缺失时会引发界面显示问题,后面会专门讲。
| 类名 | 推断职责 | 对应业务 |
|---|---|---|
| Setup | 系统初始化、数据库连接配置 | 启动准备 |
| HotelFrame | 主窗口、菜单导航 | 界面框架 |
| Individual / Eindividual | 散客登记、散客信息扩展 | 散客入住 |
| Team | 团队登记、团队成员管理 | 团队入住 |
| Engage / EngageInfo | 预订主表和预订明细 | 客房预订 |
| Query | 多条件查询 | 信息检索 |
读这种老代码不要一行行抠,先把类之间的调用关系理出来。HotelFrame 作为主窗口,会实例化 Individual、Team、Engage 这些业务面板;每个业务面板自己持有 JDBC 连接,提交时直接对数据库做增删改查。这种结构虽然不利于大型项目维护,但作为教学演示,它的好处是你随便点开一个类都能看懂当下在干什么,没有一长串继承链条和工厂方法挡住视线。
3.2 散客入住:从表到界面的完整链路
散客入住是这个系统里最有代表性的模块。我按最常见的表名拆给你看,具体以你拿到的数据库脚本为准。客人来了,界面录入姓名、身份证、房号、入住日期,然后系统扣掉房态并把客人信息写进数据库。整个链路落到代码上就是三个动作:读取表单值、拼 SQL、执行更新。
String sql = "INSERT INTO guest (name, id_card, room_no, checkin_date) VALUES (?, ?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, nameField.getText()); ps.setString(2, idCardField.getText()); ps.setString(3, roomNoField.getText()); ps.setDate(4, new java.sql.Date(System.currentTimeMillis())); ps.executeUpdate();这里用了 PreparedStatement 而不是 Statement,第一个原因是可以防 SQL 注入,第二个原因是参数占位符让代码可读性好得多。nameField.getText() 这类调用就是从 Swing 文本框里取用户输入。setDate 那行是把当前时间转成 java.sql.Date 写进数据库,数据库里的 checkin_date 字段得是 date 或者 datetime 类型才能对上。
如果你在源码里看到的是直接字符串拼接 SQL 的写法,也别奇怪,老项目里这种写法很常见。你复现的时候可以顺手改成 PreparedStatement,这不会影响原有功能,答辩时还能多说一个安全意识。另外值得指出的是,一个完整的入住登记应该是一个事务:先改房态,再插客人记录。如果插入客人记录失败,房态不应该被占用。老项目里经常是两段独立执行,这就存在数据不一致的隐患。你可以在原代码基础上给 conn.setAutoCommit(false),然后连续执行两个 SQL,最后 conn.commit(),异常时 conn.rollback(),这个改进能进答辩加分项。
3.3 预订与查询:参数化带来的隐藏收益
预订模块走的是 Engage 和 EngageInfo 两张表配合的路子。主表记录预订人基本信息,明细表记录预订了哪几间房、什么时间段,这是标准的一对多设计。查询模块则是用综合条件在多个表之间做连接,比如按姓名查散客、按团名查团队、按日期查入住记录。
String sql = "SELECT g.name, g.room_no, r.room_type " + "FROM guest g JOIN room r ON g.room_no = r.room_no " + "WHERE g.checkin_date BETWEEN ? AND ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setDate(1, startDate); ps.setDate(2, endDate); ResultSet rs = ps.executeQuery(); while (rs.next()) { String name = rs.getString("name"); String roomNo = rs.getString("room_no"); String type = rs.getString("room_type"); // 把结果加到界面表格模型里 }这段查询演示了日期范围检索和表连接。BETWEEN ? AND ? 让时间范围由参数控制,startDate 和 endDate 来自界面的日期选择器;JOIN 把客人和房间表连到一张结果集里。rs.getString 按字段名取值,写字段名时注意和数据库列大小写保持一致,否则在有的排序规则下会报列名无效。把这段放到 Query 类里,再用 JTable 展示,就是完整的查询功能。如果你的查询条件允许为空,常见做法是动态拼 SQL,但拼的时候记得用 if 判断逐个追加,并且每个条件继续用 ? 占位,不要因为动态就退回到字符串拼接。
3.4 团队与预订模块的差异
团队模块和散客模块的差别在于,Team 类往往会额外维护一张团队主表,团队成员信息单独存,主表存团名、领队、人数这种整体维度,成员表存每个客人的明细。Eindividual 这类扩展类,一般是在散客基础上加了补充字段,比如押金、备注、同行人数。Engage 和 EngageInfo 的差别是一对多:Engage 存预订主记录,比如预订人、入住时间、离店时间;EngageInfo 存预订的房号清单。读代码时优先看外键关系,明细表里通常会带一个预订主表 ID 的外键字段。
理解了这个结构,你就能明白为什么查询模块常常要做多表连接:因为每个人关心的维度不同,前台想看实时房态,财务想看入住流水,管理者想看团队占比,这些都得靠表和表之间的关联字段拼出结果。读到这里你已经能看出这个系统的边界:没有事务管理,没有连接池,业务逻辑和界面耦合在同一个类里。这些都是缺点,但也正是因为简单,才适合拿来当第一份完整源码拆。
4. 避坑与常见问题排查:跑不起来,多半栽在这五个地方
老项目的坑往往不在业务逻辑上,而在环境、驱动、资源路径这些不起眼的地方。我把复现月亮湾这套系统时最容易踩的五个问题按现象、原因、解决写出来,你照着顺序排查,基本一个下午能跑通。
4.1 sa 账号登录失败、拒绝连接
现象:运行后提示“用户 sa 登录失败”或者 Login failed for user 'sa'。原因:SQL Server 装的时候选了 Windows 身份验证模式,或者 sa 密码是空、密码策略不允许空密码;连接字符串里写的用户名密码和数据库真实账号对不上。解决:打开 SSMS,右键服务器选属性,在“安全性”里改成“SQL Server 和 Windows 身份验证模式”;然后在“安全性→登录名”里找到 sa,右键设置密码,并启用登录。改完以后重启 SQL Server 服务,让配置生效。
更省事的办法是新建一个专用登录名,只给这个系统用的权限。比如创建一个 moonbay 账号,映射到 MoonBayHotel 库,给它 db_owner 权限,然后连接字符串里的账号密码用 moonbay。这样不用动 sa,也更安全。我一般拿到这种老项目都会顺手建专用账号,避免自己把 sa 密码折腾忘了。如果改完仍然报错,检查是否选中了“强制实施密码策略”,很多老机器默认关闭,一旦开启,空密码或短密码会直接被拒。
4.2 JDBC 驱动加载不出来
现象:控制台报 ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver,或者在 Class.forName 那一行直接抛异常。原因:工程里没有放 sqljdbc4.jar,或者放了但没被加载到类路径。Eclipse 老工程经常出现 jar 在磁盘上、但 Build Path 里没有的情况。解决:确认 jar 文件存在,然后在工程上右键 Build Path → Configure Build Path → Add JARs,把 sqljdbc4.jar 加进去。如果你用的是命令行编译,把 jar 写进 classpath,例如 javac -cp ".;sqljdbc4.jar" 你的类.java。
驱动版本也要对齐 JDK。JDK 1.8 用 sqljdbc4.jar 或 sqljdbc42.jar 都行;如果强行拿老版本 sqljdbc.jar 跑,好一点是报链接错误,常见是不声不响地找不到驱动类。建议先看下自己机器 JDK 是 32 位还是 64 位,sqljdbc 驱动本身跨平台,但连接 SQL Server 时会依赖一些本地校验,整体讲 64 位 JDK 更省心。另外,驱动 jar 不要和工程里其他旧 jar 混在同一个 lib 目录下不管,有些老 IDE 会默认把 lib 下所有 jar 加进 classpath,冲突时会优先加载旧版本的驱动类。
4.3 连接超时、1433 端口不通
现象:报 Connection timed out 或者 The TCP/IP connection to the host has failed,日志里能看到 java.net.SocketException: Connection reset。原因:SQL Server 默认没有开启 TCP/IP 协议,或者端口被改过;Windows 防火墙把 1433 端口拦了。解决:打开 SQL Server 配置管理器,在“SQL Server 网络配置”里把实例的 TCP/IP 协议状态改为“已启用”,右键 TCP/IP 选属性,确认 IP 地址配置里 TCP 端口是 1433;然后重启 SQL Server 服务。防火墙方面,在 Windows 防火墙高级设置里放行 1433 端口,或者允许 sqlservr.exe 通过。
这一步是最常见的玄学问题,十次里有五次其实是 SQL Server 服务根本没起来。所以排查时先看 Windows 服务列表里 SQL Server 是否正在运行,再去看协议和防火墙,顺序别反。还有一个细节容易被忽略:如果机器上同时装了多个 SQL Server 实例,1433 端口可能被默认实例占着,命名实例的端口是动态的,连接字符串就得改成 jdbc:sqlserver://localhost\实例名,并配合非默认端口。
4.4 中文乱码或者数据写入变问号
现象:界面上输入中文,存到数据库后变成 ??,或者反过来查询结果在界面上乱码。原因:脚本文件编码和数据库排序规则不一致,或者连接字符串里没有指定字符集。SQL Server 的默认排序规则如果是 Latin1_General_CI_AS,对中文字段的处理就可能出问题。解决:执行 SQL 脚本时,用 SSMS 打开文件前先确认脚本被识别成了正确编码(UTF-8 with BOM 或 GB2312);数据库的排序规则可以在建库语句里指定,常见做法是 Chinese_PRC_CI_AS。如果不想重建库,可以在列级别用 ALTER TABLE 修改排序规则,但那样工程量会大一些。
JDBC 连接串那边,SQL Server 的驱动一般走 Unicode 传输,乱码概率比 MySQL 低;出现乱码时重点看数据库字段类型是不是 nvarchar/nchar,而不是 varchar。老脚本里如果大量使用 varchar 存中文,换成 nvarchar 是治本的办法。改完后记得重启应用,因为连接可能已经在旧的排序规则下建立,新排序规则要等新连接才会生效。
4.5 off.bmp 等图片资源加载失败
现象:程序能跑,但窗口上的图标、背景图、按钮贴图空白,控制台报 FileNotFound 指向 off.bmp 或类似的图片。原因:图片用了相对路径,实际运行时当前工作目录不是图片所在目录。老 Swing 项目里直接写 getImage("off.bmp") 很常见,依赖的是从启动目录找文件。解决:先从项目路径确认 off.bmp 确实存在;然后把图片放到编译输出目录(bin 或者 classes)下,或者改用类路径方式加载,即 getResource("/off.bmp")。
用 getResource 要保证图片在 src 根目录下,编译后自然进了 classpath,这样不管从哪儿启动程序都不会找不到资源。这个改动很小,但对复现体验影响很大,我第一次跑这套系统时就在这上面卡了近十分钟,从起初怀疑环境问题,到后来发现只是图片找不到了。如果你用的是 Eclipse 导出 Runnable JAR 文件,还要记得在导出配置里把资源文件一并打包进 jar,否则换台机器跑打包后的程序,同样的资源问题还会再犯一次。
5. 进阶改一手:把连接收口,再把密码从明文改成 MD5
资源到手以后,不要满足于把它跑起来。我建议你做两件小改造,第一件是把散落在各个类里的连接代码收成一个 DBHelper,第二件是把登录密码从明文存量改成哈希校验。这两件事都不伤原有功能,但能让这份代码从“能跑”变成“能拿得出手”。
public class DBHelper { private static final String URL = "jdbc:sqlserver://localhost:1433;DatabaseName=MoonBayHotel"; private static final String USER = "moonbay"; private static final String PASSWORD = "moonbay123"; static { try { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); } catch (ClassNotFoundException e) { throw new RuntimeException("JDBC驱动未加载", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这个 DBHelper 把所有连接参数集中到了一处,后续改密码、改库名只动一个文件。要验证改造是否成功,写一个几行的 main 方法,调用 getConnection,再执行 SELECT 1,如果返回正常,说明原有业务类替换成 DBHelper.getConnection() 之后不会出问题。替换的时候注意把原来每个类里自建的 Connection 和 PreparedStatement 都关掉,改成统一从 DBHelper 获取,否则会留下不少半开的连接。
密码这块,找到登录校验的方法,把明文比较改成 MD5 摘要比较。MD5 不算强加密,但比明文进步明显,答辩时可以顺带提一句后续可以换 SHA-256 或加盐。改造完成后,用旧密码明文登录,能看到登录失败;把数据库中对应密码字段更新成新密文,才能登录成功,这就是验证逻辑。
我自己的教训是:年轻的时候拿到这种老项目先急着跑,跑通就以为完事了,结果答辩现场老师打开源码问“连接串为什么写死在这里”“密码为什么是明文”,当场卡壳。从那以后,我每次拿到课程设计或者老系统,都强制走一遍第 4 章的五个检查点,再顺手把连接收口和密码加密改掉。这份月亮湾源码和配套文档都在资源包里,照着第 2 章的步骤还原数据库,再用第 3 章的类对照表去读代码,复现过程会顺畅很多。希望帮到你。
本文还有配套的精品资源,点击获取