简介:这套基于JavaWeb的酒店预订系统是一份可直接用于毕业设计的完整项目资料,面向计算机相关专业正在筹备毕设的学生及需要项目实战练习的Java学习者。系统采用JSP、Servlet、JDBC技术栈,搭配MySQL数据库,实现用户端注册登录、设施价格浏览、可用房间查询、新增预订、订单明细管理,以及管理员端的订单查询、修改、删除等核心功能,两种角色权限分明,业务流程完整,界面美观且操作简单。压缩包内共3个文件,包含项目说明txt、项目源码zip以及数据库脚本sql,整体仅3.52MB,轻量便携,txt提供使用指引,sql可快速初始化数据库,zip解压即为可部署的工程。目前已有1846人学习下载,项目经过严格调试,可直接运行,适合作为毕设课题、课程设计或JavaWeb综合实训的参考蓝本。
1. 酒店预订系统作为JavaWeb毕设:这套源码加数据库脚本,到底能帮你省下什么
每到毕设季,最折磨人的往往不是写代码本身,而是“既要做一个完整可演示的系统,又不知道从哪儿下手”。这个基于JavaWeb的酒店预订系统,属于典型的全栈练习项目:前端页面、Servlet或框架层控制、后端业务逻辑、数据库脚本一次性都占了。标题里带“项目源码+数据库脚本”,意味着它不是一个只有截图或零散代码片段的半成品,而是拿到后能在本机跑起来、能演示、能截图写论文的完整闭环。适合的人群很明确:正在做JavaWeb方向毕设的在校生,以及想快速复现一套“增删改查+订单状态流转”管理系统练手的人。下面我按照自己动手跑这类项目的顺序,把这套系统从技术选型、环境搭建、数据库脚本到核心代码,一条线讲完。无论你拿到的源码具体是什么版本,这些步骤和坑都通用。
2. 先想清楚再动手:这套系统的技术栈与模块边界
2.1 JavaWeb技术的新旧之争:JSP/Servlet、SSM、Spring Boot该怎么选
很多同学拿到“JavaWeb酒店预订系统”的源码,第一反应是问:这里用的是哪种写法?常见的情况有三种:纯JSP+Servlet+JDBC、SSM(Spring+SpringMVC+MyBatis)、以及Spring Boot改版。这个选择直接影响你能不能在有限时间内把项目跑起来,也影响答辩时老师问“你做了什么”你能答出多少。
纯JSP+Servlet+JDBC是这类毕设最经典的组合。它的优点是依赖少,不需要复杂的Maven依赖管理,也不用理解Spring容器和AOP,适合基础一般、重点在展示“页面+数据库交互”的同学。缺点是代码耦合度高,一个订单列表可能又是JSP又是Java脚本,改起来费劲。
SSM则是把业务逻辑拆成了四层:Controller接收前端请求、Service写业务规则、Mapper操作数据库、Model传递数据。这个结构的价值在于,答辩被问“你的系统怎么做到解耦的”时有话可说。而Spring Boot版本更多是把配置自动化了,内嵌Tomcat把部署门槛降低。但对于纯JSP类的老项目,改成Spring Boot反而工程量大。
我个人的建议是:糊弄型的项目选纯JSP/Servlet,想好好讲原理的多花三天改成SSM。别在一开始幻想“一步到位用最新技术”,JavaWeb毕设的核心是让系统跑通、能截图、能说清楚数据流。
2.2 酒店预订系统的模块边界:预订、房态、订单三条主线的数据流
“酒店预订”这四个字,和普通的学生管理系统(比如图书管理、成绩管理)最大的区别在于:它不只有“增删改查”,还有两套容易出错的业务状态——房间状态的实时变化,以及订单状态的多阶段流转。
先理清权限边界。前台是普通用户:注册登录、浏览房型、按入住日期搜索可用房间、提交订单、查看我的订单、取消订单。后台是管理员:维护房型、管理房间状态(可订/入住/打扫中/停用)、查看所有订单、处理订单退款或标记入住。注意这里有一个经典的建模歧义:房间(Room)和房型(RoomType)是两个表,不要合并。房型存“大床房”“标间”这种价格和描述,房间存具体房号比如“1203”,房间通过外键挂到房型上。很多翻车项目把这两个概念混在一起,导致“不同房间没法单独调价”这种尴尬。
数据流的主线是这样:用户在前台选择入住日期和退房日期,系统按照房型判断哪些房间在日期范围内没有冲突订单,展示可订房列表。用户下单后,生成一条订单记录,初始状态一般是“待支付”或“已预订”。然后订单把状态往后推,管理员或系统触发状态变更。这一套流程,在你的数据库表设计阶段就必须想清楚,不能等到写代码时再补。
2.3 源码包的目录结构:拿到JavaWeb项目先读这几层
无论你从哪里拿到这份JavaWeb项目源码,先别急着启动,先花十分钟看目录结构。一个合格的JavaWeb项目,哪怕是最传统的JSP/Servlet版本,通常也会按下面这个结构组织:
HotelSystem/ ├── src/ │ └── com/example/hotel/ │ ├── controller/ # Servlet类,接收HTTP请求 │ ├── service/ # 业务逻辑接口+实现 │ ├── dao/ # 数据库访问层(JDBC或MyBatis) │ ├── entity/ # 实体类:User,Room,Order │ └── util/ # 数据库连接工具,日期工具 ├── webapp/ # Web根目录 │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置文件,Servlet映射 │ │ └── lib/ # 手动放jar包的位置(没有Maven时) │ ├── css/ js/ images/ │ └── jsp/ # 前台页面:login.jsp,search.jsp └── sql/ └── hotel_db.sql # 数据库脚本,建库建表+初始数据读目录时重点看两个位置。第一是web.xml,这个文件在纯Servlet项目中是“黑匣子”一样的存在:Servlet的URL映射、过滤器(尤其是中文编码过滤器)、欢迎页面,都在这里配置。第二是sql目录下的脚本。脚本通常包含建库语句、建表语句、插入初始数据三个部分。这里要提醒一句:很多项目源码里的sql文件名是hotel.sql或者hotel_system.sql,里面第一行往往是CREATE DATABASE IF NOT EXISTS hotel DEFAULT CHARACTER SET utf8;。这说明建库是脚本自带完成的,你不需要手动先创建数据库,执行时按顺序跑完即可。看到这两处,你的整个源码轮廓就清楚了。
3. 把项目跑起来:IDEA里的环境配置与Tomcat部署
3.1 JDK、Tomcat、IDEA的版本组合怎么选才不折腾
版本组合是JavaWeb项目跑不跑得起来的第一道门槛。这里直接给出一套我实践过几千次的组合:JDK 8或11、Tomcat 8.5或9.0、IDEA 2020到2022之间的任意版本。这三个版本配套使用最稳。
为什么不建议Tomcat 10?因为Tomcat 10把JavaEE命名空间从javax.*改成了jakarta.*,而绝大多数毕设源码里的旧版Servlet和JSP代码写的是javax.servlet.。你在IDEA里配置好Tomcat 10,启动时可能不报错,但一访问页面就报找不到类,重启也没用。这不是你的代码问题,是Tomcat版本不兼容。如果你已经在Tomcat 10上翻车了,最省事的后悔药是换成Tomcat 9,而不是去全局替换javax为jakarta。
JDK的选择也有坑。JDK 17以上的版本会提示“访问限制”或在编译老代码时报警告,更有甚者会出现动态加载代理相关的问题。虽然大多数毕设项目在JDK 11下都能正常编译,但如果你用的是老旧的Eclipse项目格式或手工引入的jar包,JDK 8是最宽容的。
另外,如果源码带Maven依赖(pom.xml),你就还需要一个Maven环境,IDEA自带的Maven或独立安装的Maven 3.6+都可以。不带Maven就简单了,直接确认WEB-INF/lib下有mysql-connector-java.jar和jstl相关的jar包即可。
3.2 源码导入IDEA:从Open到Tomcat Deployment的完整配置
这一步我会把它拆成很具体的操作,因为“idea运行javaweb项目配置”这个搜索词的背后,往往是一个项目半天打不开的崩溃场景。第一次接触JavaWeb项目时,最容易卡住的不是写代码,而是把项目正确挂到Tomcat上。
1. 选择 File → Open,选中项目根目录,IDEA识别为Java项目。 2. 打开 File → Project Structure。 3. Project → SDK 选 JDK版本(1.8或11),Language Level 选 8。 4. Modules → 勾选项目模块,点「+」添加 Web 特性,设置 Web 资源目录为 webapp,路径保留默认。 5. 打开 Run → Edit Configurations → 点「+」选择 Tomcat Server → Local。 6. 在 Deployment 标签页点「+」选择 Artifact → 选名字带 exploded 的那一项。 7. 修改 Application context 为 /hotel,下面URL会显示 http://localhost:8080/hotel/。 8. 启动,IDEA底部会显示 Tomcat log,看到“Server startup in xxx ms”即成功。这是典型的IDEA手动配置JavaWeb项目路径,虽然繁琐,但你能清楚知道每一步在干什么。很多老项目打开后没有Artifact可添加,是因为第4步没做好——IDEA没有把项目识别为Web模块。此时回到Modules标签,给Web模块添加部署描述符web.xml,如果项目里已经有,就指定它的路径;如果没有,点击加号让IDEA自动生成一个。框架选型这步看个人习惯:Maven项目有pom.xml会自动识别,普通项目直接下一步。
参数说明:Application context这个概念很多人不知道是干什么的。它决定了你访问项目的URL前缀。默认可能是/或/out,如果不改成/hotel,那么登录跳转、页面链接里带绝对路径的资源(比如/hotel/login.jsp)会全部404。所以每次新建Run配置,我都习惯先把context设置成项目名,保持和web.xml里的映射一致。
3.3 Tomcat启动失败与端口占用:三个排查命令
如果启动时IDEA直接报Port 8080 was already in use,说明是你本机8080端口被别的东西占用了。解决方案不是换端口,因为换了端口你得连带改所有页面的访问URL。正确方式是找出占用进程并处理。
# Windows下查看8080端口被哪个进程占用 netstat -ano | findstr 8080 # 强制杀掉这个进程(把最后的PID替换成findstr查出来的数字) taskkill /F /PID 12345 # macOS/Linux下查看8080端口 lsof -i :8080Tomcat启动中途报错时,除了看IDEA控制台红色的Exception,还要去WEB-INF/logs或Tomcat安装目录的logs/localhost.YYYY-MM-DD.log里翻细节。很多报错在IDEA控制台里被层层套壳只显示“Error creating bean”,但真正的原因在日志后半段。这是排查JavaWeb老项目时最实用的习惯。启动成功但访问页面是404,先看是不是把Artifact部署成了war而不是exploded。exploded模式允许热部署,也方便你调试JSP页面,毕设阶段一定要用exploded。
4. 数据库脚本落地:从建表到连接串
4.1 先认清核心表的设计:房型、房间、用户、订单
数据库脚本是整套酒店预订系统的“地基”。很多同学拿到脚本就直接执行,结果执行一半报错,又跑回来问为什么。我的建议是先打开脚本文件,看看它建了哪些表。这里给出最常见的四张核心表设计,对照你手里的脚本,重点检查字段是否齐全:
CREATE TABLE t_room_type ( id INT PRIMARY KEY AUTO_INCREMENT, type_name VARCHAR(50) NOT NULL COMMENT '房型名称:大床房/标准间', price DECIMAL(10,2) NOT NULL COMMENT '每晚价格', bed_count INT DEFAULT 1 COMMENT '床位数', breakfast TINYINT DEFAULT 0 COMMENT '是否含早:1含早/0不含早' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '房型表'; CREATE TABLE t_room ( id INT PRIMARY KEY AUTO_INCREMENT, room_num VARCHAR(10) NOT NULL COMMENT '房间号', type_id INT NOT NULL COMMENT '关联房型', status TINYINT DEFAULT 0 COMMENT '0可订/1入住/2打扫/3停用', FOREIGN KEY (type_id) REFERENCES t_room_type(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '房间表'; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT '建议存MD5或BCrypt值', phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户表'; CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT '订单编号', user_id INT NOT NULL, room_id INT NOT NULL, check_in_date DATETIME NOT NULL, check_out_date DATETIME NOT NULL, order_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0已预订/1已入住/2已完成/3已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES t_user(id), FOREIGN KEY (room_id) REFERENCES t_room(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '订单表';这几张表的数据关系就是项目的核心:房型是一等公民,房间挂在房型下面,用户产生订单,订单引用房间,而房间的“能不能订”要看订单时间区间是否重叠。注意status字段的设计:我统一用TINYINT存,加注释说明含义,这种写法在答辩时会被老师视为“考虑到了状态机设计”。你在执行脚本前,重点看时间字段是否是DATETIME类型——如果脚本里用的是VARCHAR存日期,后续做日期比较会非常痛苦。
4.2 用IDEA或Navicat执行脚本的顺序与方法
拿到数据库脚本后,执行顺序和导入工具也很值得讲一下。先说通用结论:先在MySQL客户端用source命令或图形化工具执行整个脚本,不要手工一条条复制语句执行,因为脚本里面通常写了外键和插入语句,单条执行会因为前置数据没插入而失败。Navicat和DataGrip都能直接右键运行.sql文件,IDEA自带数据库工具同样支持。
-- 在Navicat或IDEA中,建议先手动执行下面这句,确保连接库字符集正确 SET NAMES utf8mb4; -- 再导入整个sql文件,正常顺序是: -- 1. 如果脚本有 DROP TABLE IF EXISTS,会自动清掉旧表 -- 2. 先执行建表和插入初始数据 -- 3. 若脚本里有外键,注意表创建顺序:先房型、房间,最后订单 -- 执行完后,用下面这行检查是否成功 SELECT COUNT(*) FROM t_room;这里有一个容易踩坑的细节:字符集不一致。如果你的脚本是GBK编码的,而MySQL连接终端或客户端默认是utf8,那么插入的中文(比如房型名“大床房”)全部变成乱码。解决办法是:在头部加SET NAMES utf8mb4;,并在脚本文件打开时确认IDEA右下角编码显示为UTF-8。如果你是MySQL 5.7,字符集默认是utf8,可以接受;如果脚本里写了utf8mb4而你的MySQL版本过旧,可能报错不支持,那就把脚本里的utf8mb4全局替换为utf8。
另外关于“idea导出数据库脚本”这个需求,它对应的场景是:你改了表结构,需要把数据库导出成脚本发给别人或交作业。在IDEA数据库工具里右键数据库,选择Dump with mysqldump,即可导出包含建表和数据的完整脚本。导出时注意勾选Include Create Database,这样别人拿到脚本后可以直接执行,不必手动建库。
4.3 JDBC连接参数:driver、URL里的时区与字符集
JavaWeb项目连接MySQL,最后一道坎是JDBC配置。这个配置写在哪里?常见位置是src/jdbc.properties,或者Spring的applicationContext.xml里。下面的配置是MySQL 8.0加JDK8的标准写法:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/hotel?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456参数说明:驱动类com.mysql.cj.jdbc.Driver是MySQL 8.0以后的新名字,MySQL 5.7及更早版本用的是com.mysql.jdbc.Driver。如果你项目里驱动类写成旧的,连接MySQL 8会报ClassNotFoundException或Failed to load driver class。serverTimezone=Asia/Shanghai经常被忽略,但它直接解决“连接成功但执行SQL报时区错误”或时间字段差8小时的问题。useSSL=false是为了避免连接时SSL握手告警,不影响功能,只是清理控制台日志。密码位置建议改成自己本机的MySQL密码,这行如果不改,启动时最常见的报错是Access denied for user 'root'@'localhost'——这个不需要换驱动,只需要改密码。
如果项目连接的是MySQL 5.7,那么serverTimezone参数不是必需的,但URL里的characterEncoding=utf8必须有。连接串写好后,可以用一个最简单的Java类测试连接:
public static void main(String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); // 用ConnectionDriverManager代替传统注册方式 java.sql.Connection conn = java.sql.DriverManager.getConnection( "jdbc:mysql://localhost:3306/hotel?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "你的密码"); System.out.println(conn); // 输出了对象就说明连上了 }测试连接这一步建议不要跳过,因为Tomcat启动时即使数据库没连上,Tomcat本身也能启动成功,只有你点击页面上的登录按钮时才报错。提前把连接串测一遍,能分离“数据库问题”和“Web代码问题”,排查起来少一半精力。
5. 核心功能实现:登录、下单、订单列表的代码骨架
5.1 用户登录:Servlet+Service+DAO三层怎么分工
登录功能是所有JavaWeb项目的“标配”,但很多同学的写法是:在Servlet里直接写JDBC代码查询数据库。这样也能跑,但答辩时经不起追问“你的代码怎么维护?”。下面给出三层分工的标准写法,这也是大多数毕设源码采用的模式,只不过类名和包名不同。你需要做的是对照源码,找到类似的对应关系。
// LoginServlet.java - 控制层,只做参数接收、调用service、跳转 @WebServlet("/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserServiceImpl(); protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); User user = userService.login(username, password); if (user != null) { req.getSession().setAttribute("loginUser", user); resp.sendRedirect(req.getContextPath() + "/index.jsp"); } else { req.setAttribute("errorMsg", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } }逻辑说明:Servlet里没有SQL,只负责把请求参数封装好交给Service,登录成功则往Session里写入用户对象,失败则把错误信息塞进request域并转发回登录页。这里的细节是:失败时用的是forward跳转而不是sendRedirect,因为forward能把errorMsg带过去,sendRedirect会丢参数。
Service层和DAO层是另外两个类,职责边界为:Service处理业务规则(比如密码加密后比对),DAO只负责查询数据库。以密码为例,源码里如果存储的是MD5(password),Service先做一次MD5再传给DAO查询,而不是直接拿明文拼SQL。这种写法在答辩讲“防止SQL注入”时就能顺手带出来——JSP/Servlet老项目里,很多人都是用PreparedStatement占位符,不在字符串里拼参数,这是比Statement更安全的做法,评审老师看到这个点通常会给肯定。
5.2 预订下单:事务边界、房间占用与订单状态机
酒店预订系统区别于图书管理系统的核心业务逻辑就在这里:下单时不仅要插入订单记录,还要确保房间没有被其他订单占用,并且房间状态要同步变更,这两个操作必须放在同一个数据库事务里。如果插入订单成功但房间状态更新失败,就会出现“订单显示预订成功,但实际房间状态没变”的数据不一致。
// 核心事务代码,在Service层中完成 public boolean createOrder(Order order) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启手动事务 OrderDao orderDao = new OrderDao(conn); RoomDao roomDao = new RoomDao(conn); // 1. 先查询房间在订单时间段内是否可订 int count = orderDao.checkRoomAvailable( order.getRoomId(), order.getCheckInDate(), order.getCheckOutDate() ); if (count > 0) { conn.rollback(); return false; // 时间段冲突 } // 2. 插入订单 int orderId = orderDao.insert(order); // 3. 更新房间状态为“已预订”或“入住中” roomDao.updateStatus(order.getRoomId(), 1); conn.commit(); // 事务提交 return orderId > 0; } catch (Exception e) { conn.rollback(); // 任何异常都回滚 log.error("createOrder error", e); } finally { DBUtil.close(conn); } return false; }三个关键点说明。第一,checkRoomAvailable的SQL不要想得太简单,不能只看房间当前状态,而是要查订单表里有没有“入住时间区间重叠”的记录,可参考条件:check_in_date < 新退房时间 AND check_out_date > 新入住时间,这条SQL是酒店预订系统的核心查询,几乎每个版本源码都有,只是写法不同。第二,事务是先setAutoCommit(false)再执行多条SQL,最后提交或回滚,这让你在答辩时能清晰说出“我通过事务保证了数据一致性”。第三,房间状态这里有一个常见设计选择:房间表里直接存一个status字段,下单就改成“已预订”。更严谨的做法是订单里直接有房间ID,查询时用关联判断,但毕设场景下直接维护状态字段更直观。
5.3 订单列表与状态回显:JSP里的EL表达式和状态映射
订单列表页是前台用户最常看到的页面,也是JSP里最容易展示“水平”的地方。老项目常用JSTL的c:forEach来遍历订单集合,再用EL表达式输出字段。下面是一个典型的订单列表片段:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table> <tr><th>订单号</th><th>房型</th><th>入住日期</th><th>状态</th><th>金额</th></tr> <c:forEach items="${orderList}" var="order"> <tr> <td>${order.orderNo}</td> <td>${order.roomTypeName}</td> <td>${order.checkInDate}</td> <td> <c:choose> <c:when test="${order.status == 0}">待支付</c:when> <c:when test="${order.status == 1}">已预订</c:when> <c:when test="${order.status == 2}">已入住</c:when> <c:when test="${order.status == 3}">已完成</c:when> <c:otherwise>已取消</c:otherwise> </c:choose> </td> <td>¥${order.orderPrice}</td> </tr> </c:forEach> </table>这段代码的逻辑不复杂,但有几个点要注意。${order.orderNo}能直接取到值,靠的是实体类里必须有getOrderNo()方法,否则EL表达式取到空值页面上直接空白。状态为什么用c:choose而不是直接输出数字?因为用户看不懂0、1、2,必须有一个状态码到文本的映射,这一步放在JSP里最简单。也有源码会直接在控制层把状态码转好放进实体类扩展字段,写法不同,但表达的意思一样。假如你拿到源码后想快速改造成中文提示,搜索c:choose就能定位到所有状态映射的位置。
JSP页面还有一个“价值点”值得你在答辩前搞清楚:所有前台页面都要先判断Session里有没有loginUser,没登录就跳转登录页。这个拦截逻辑如果每个页面都写一遍,容易漏;所以更合理的实现方案是配置Servlet的Filter统一处理,把不需要放行的公共请求(默认登录页和注册页)放行,其余全部重定向到login.jsp。拿到源码后,先在web.xml里找Filter声明,就能知道项目到底有没有做登录拦截。
6. 避坑:JavaWeb酒店的典型故障与排查顺序
6.1 现象:Tomcat能启动,点登录报404或500,控制台却没有任何异常
原因:这是最常发生在“Servlet映射”上的事故。访问URL和web.xml里的<url-pattern>不匹配,比如页面表单提交的是/login,但web.xml里写的是/loginServlet。另一种可能是使用了注解@WebServlet("/login"),但web.xml里同时保留了旧的Servlet映射,两者产生冲突。 解决:先打开web.xml,把里面的<servlet-mapping>都看一遍,确认URL路径与实际请求一致;再检查Servlet类上有没有@WebServlet注解,有注解就去掉web.xml中的重复映射,只保留一种方式。
6.2 现象:页面能开,登录时中文用户名变成问号
原因:请求参数编码没设置,MySQL连接串里也缺字符集参数,两层都出问题。 解决:在Servlet最前面加req.setCharacterEncoding("UTF-8"),或者在web.xml里配置一个全局编码过滤器,把所有请求统一成UTF-8,这样你不需要每个Servlet写一遍。数据库侧在JDBC URL里带上characterEncoding=utf8,并把jdbc.properties里对应的配置改对。改完这两处,重启Tomcat,旧数据可能出现乱码,需要重新插一次数据,把脚本里的中文重新执行一遍。
6.3 现象:MySQL运行sql脚本时报错Cannot add foreign key constraint
原因:外键关联的字段类型或字符集不一致。最常见的是两张表的id字段一个为INT,另一个为BIGINT;或者被参照表(比如t_room_type)根本还没创建,脚本执行顺序不对。 解决:检查脚本中的表创建顺序,保证父表先建。同时确认外键字段都带UNSIGNED属性,字符集统一为utf8mb4。最省事的办法是直接在脚本里去掉部分外键约束,或先建完表再通过ALTER TABLE加外键。毕设场景里,外键不是必须的,逻辑外键靠代码维护也完全可行。
6.4 现象:java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
原因:驱动Jar包没有拷贝到WEB-INF/lib目录,或项目的Tomcat运行环境中没带上这个Jar。 解决:先确认Maven或本地lib有没有mysql-connector-java.jar;如果你的连接串写的是com.mysql.cj.jdbc.Driver,但项目用的是MySQL 5.7驱动包,也会报这个错,需要根据MySQL版本换驱动名。此外,在IDEA里还要检查Artifact列表:右键Artifact → Put into Output Root,确保运行时lib里有这个Jar。这是一个很隐蔽的坑,代码不报错,但每次一部署就缺这个类。
6.5 现象:酒店搜索“可用房间”时,明明没订单却显示已满房
原因:房间状态只在房间表里维护,而订单取消或过期后没有回写状态字段。比如用户下单后取消订单,订单状态改了,但房间表的status没有被置回0。或者订单创建时直接把房间改成了“入住中”,不管实际日期差了多少天。 解决:检查取消订单的代码逻辑,是否同步把房间状态更新回可订;检查下单成功之后,房间状态变更是否在同一个事务里提交。更稳健的做法是把“查可订”从房间表的status字段改成“查订单表日期区间是否冲突”,也就是4.1里写的checkRoomAvailable,这样即使房间表状态乱了,搜索也不会阻塞。
6.6 现象:JSP页面通过${orderList}取到集合,但页面显示空白
原因:控制层没有把orderList放到请求域或会话域,或者JSP页面取的时候大小写不一致。另一个容易被忽略的点是Controller用sendRedirect跳转,跳转后request里的数据全部丢失。 解决:用forward转发到JSP,并把集合放进request.setAttribute("orderList", 集合)。顺带一提,如果你用IDEA自带的高版本Tomcat运行,JSP表达式语言默认可能被禁用,需要在web.xml里确认isELIgnored为false。这些细节单独看不出问题,但组合在一起,就是“纸面上完美,跑起来全空白”的经典翻车现场。
7. 从能跑通到能讲清楚:验收清单与三个进阶方向
7.1 功能验收清单:给自己的项目做一次全面体检
把项目跑起来只是及格线的开始,毕设评审看重的是你能当场演示哪些功能。这里给出一份自检清单,建议对照源码逐项走一遍:“前台注册、登录成功并能记住会话”“按条件搜索后只返回可订房间”“下单生成订单号,状态为待支付/已预订”“后台管理员能查看到所有用户订单”“订单取消后房间恢复可订”“数据库脚本删掉后重新执行,环境能复现”。每一行调试通过后,在答辩时就把对应功能当作一个演示小节,老师在现场看演示时心里是有数的,项目如果在此处出岔子反而更突兀。
7.2 JDK与Tomcat版本升级的稳妥路线:从JSP/Servlet到Spring Boot
这个项目想再进一步,常见的两个方向是:给项目加一个单元测试用例,或换成Spring Boot。直接颠覆底层可能风险大,稳妥路线是先把JDBC换成MyBatis、再换Servlet为SpringMVC,最后套上Spring Boot的自动化配置。每走一步就重启一次项目,确认登录和下单两个主流程没被破坏。
最后一个切身的建议:拿到整套源码后,不要急着直接跑,先按第2章说的方式把表结构画在纸上,然后自己手动把SQL脚本执行一遍、再故意改错一个地方、观察报错的现象。这种“主动破坏再修复”的方式,比任何讲解都更能帮你建立对系统的直觉。我当年做毕设时,就是通过反复删掉数据库脚本重跑、观察Tomcat日志里的一行行异常,才真正把Servlet、Filter、事务这些概念串了起来。希望这个过程也能成为你答辩时最拿得出手的底气,希望帮到你。
本文还有配套的精品资源,点击获取