
简介基于 MySQL、IDEA、Tomcat、JSP 与 Servlet 开发的宾馆管理系统面向 JavaWeb 期末作业与课程设计场景覆盖客房管理、预订入住、账单结算等模块功能完整代码附注释适合有 Java 基础的学生直接部署和二次扩展。资源包共 1370 个文件、约 22.34MB包含 JSP 页面、Java/Servlet 类、CSS/JS 前端脚本、SQL 数据库脚本、XML 配置及 MD 说明文档目录按前后端与文档分层便于定位修改。目前已有 375 人学习下载常被用于期末大作业或课程设计参考。配套文档说明部署与使用要点新手简单配置即可运行项目清晰展现 JSP 与 Servlet 的协作流程可继续扩展订房、退房、统计等功能。1. 为什么期末作业选它一套能讲清楚「请求从哪来到哪去」的宾馆管理系统期末前两周老师丢下一句「基于JavaWeb做一套管理系统前后端交互必须完整」不少同学的第一反应是打开百度找现成源码。搜到「javaweb期末作业基于MySQLIDEATomcatJSPServlet宾馆管理系统源码文档说明」这类标题时先别急着下载——这套技术栈恰好覆盖了JavaWeb课程里最核心的一条链路浏览器发请求Tomcat接住Servlet处理逻辑JSP渲染页面MySQL存数据。它不是什么炫酷的新框架但它能让你在答辩时把每个环节讲得明明白白。这篇笔记就是围绕这个标题把「拿到源码之后怎么做、做完怎么讲、跑不通怎么查」一次讲透。适合两类人一是正在赶课程设计、需要独立完成并讲清原理的同学二是工作后想用最朴素的ServletJSP快速搭内部小工具的开发者。别嫌它老这套组合的调试过程本身就是最好的复习资料。2. 先把架构和数据表定下来从E-R图到MySQL建库脚本2.1 为什么这套组合是期末现场的「安全牌」常见做法是直接选ServletJSP而不是SpringBoot。原因很现实SpringBoot帮你把Tomcat内嵌了、把依赖管理了、把配置自动了答辩时老师一问「你的请求是怎么被处理的」你很难从框架的封装里翻出底层链路。而ServletJSP这套组合每一个环节都暴露在你面前——web.xml里配了什么映射Filter拦了哪些路径Session存了什么键JDBC连接是谁打开的。这些恰恰是期末考核最容易追问的点。再说技术选型MySQL负责持久化IDEA负责写代码和调试Tomcat负责跑Servlet和JSPJSP负责展示Servlet负责控制。这五个词串起来正好是一条完整的请求处理链。有人会问那Nginx呢Nginx本身不解析JSP它通常只做静态资源转发或反向代理真正执行JSP/Servlet的还是背后的Tomcat。课程设计阶段直接把Tomcat当作Web服务器就够了不用把Nginx拉进来增加变量。2.2 宾馆管理系统的模块划分别一上来就追求「大而全」拿到项目标题后第一件事不是写代码而是把业务边界划清楚。一个能自圆其说的宾馆管理系统最少需要四件事管理员能登录能维护客房信息能办理预订和入住能退房结算。在此基础上可以加客户管理、经营统计但别贪多——期末作业的核心是可运行、可演示、可讲清楚不是功能数量。我一般会按这样的模块拆管理员管理登录、退出、密码修改、客房管理房间类型、房号、状态、客户管理登记姓名、证件号、联系方式、预订管理预订时间、入住时间、退房时间、状态、入住退房实际入住登记、退房结算。对应到数据库里就是五张核心表。模块画好了数据库表结构自然就出来了。2.3 建库建表脚本五张表的核心字段与设计意图这是整套系统的基础表字段设计得合理后面写Servlet和JSP会省一半事。下面是适合课程设计体量的建表脚本CREATE DATABASE IF NOT EXISTS hotel_db DEFAULT CHARACTER SET utf8mb4; USE hotel_db; -- 管理员表 CREATE TABLE t_admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(30), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 客房类型表 CREATE TABLE t_room_type ( id INT PRIMARY KEY AUTO_INCREMENT, type_name VARCHAR(20) NOT NULL, price DECIMAL(10,2) NOT NULL, -- 每晚价格 bed_count INT NOT NULL DEFAULT 1, -- 床位数量 remark VARCHAR(200) ); -- 客房表 CREATE TABLE t_room ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(10) NOT NULL UNIQUE, -- 房间号如 301 type_id INT NOT NULL, status TINYINT NOT NULL DEFAULT 0, -- 0空闲 1已预订 2已入住 3维修 floor_num TINYINT DEFAULT 1, FOREIGN KEY (type_id) REFERENCES t_room_type(id) ); -- 客户表 CREATE TABLE t_customer ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, id_card VARCHAR(18) NOT NULL, -- 证件号码 phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单表一个订单对应一次预订或入住 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, -- 订单编号 customer_id INT NOT NULL, room_id INT NOT NULL, order_type TINYINT NOT NULL DEFAULT 1, -- 1预订 2入住 status TINYINT NOT NULL DEFAULT 0, -- 0进行中 1已完成 2已取消 check_in_date DATE, check_out_date DATE, total_amount DECIMAL(10,2) DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (customer_id) REFERENCES t_customer(id), FOREIGN KEY (room_id) REFERENCES t_room(id) );这里有几个设计意图需要说明。第一房间状态用 TINYINT 数字而不是字符串——「0空闲 1已预订 2已入住 3维修」这一套约定写在代码注释里比在数据库里存中文更规范也更容易做条件查询。第二订单表里同时承载预订和入住两种类型通过 order_type 区分这样退房结算时可以直接复用这条记录不需要额外的入住表。第三价格放在房间类型表而不是房间表里因为同类型的房间价格相同改价格只改一处。2.4 JDBC连接参数为什么你的连接总是报错表建好了接下来要接数据库。课程设计里最常见的翻车点就是JDBC连接串写错。下面是一个标准的JDBC工具类模板package com.hotel.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/hotel_db ?useSSLfalseserverTimezoneAsia/Shanghai characterEncodingutf8allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }连接串里这几个参数是踩过坑才加上的。useSSLfalse 是为了避免MySQL 8.x默认开启SSL后控制台刷出一大串警告serverTimezoneAsia/Shanghai 解决日期时间差八小时的经典问题characterEncodingutf8 保证中文写入不乱码allowPublicKeyRetrievaltrue 是因为MySQL 8.x使用 caching_sha2_password 认证时首次连接需要通过公钥交换不加这个参数会报 Public Key Retrieval is not allowed。驱动类名用 com.mysql.cj.jdbc.Driver 对应MySQL 5.1.8之后的版本旧版的 com.mysql.jdbc.Driver 在MySQL 8.x下也能用但会有提示。连接参数落实到项目里就是把这段代码放进 com.hotel.util 包下之后所有Servlet通过 DBUtil.getConnection() 拿连接用完记得在 finally 里关掉。3. 让项目在IDEA里跑起来Tomcat配置、依赖与最小启动3.1 拿到源码包后先看清目录结构别急着点运行网上下载的「源码文档说明」压缩包解压后通常包含这么几块源码目录src/main/java、web或src/main/webapp、数据库脚本.sql文件、文档说明需求文档、设计文档、运行说明。先把目录结构摸清楚对照标题里的五个关键词逐个确认MySQL部分有没有给你建表脚本IDEA部分有没有附带 .idea 配置Tomcat部分有没有说明版本要求JSP/Servlet部分代码是否完整。没有 .idea 目录很正常到你自己电脑上重新配置就行。一个标准Maven结构的JavaWeb项目关键目录是这样组织的hotel-system ├── pom.xml // Maven依赖声明 ├── src │ ├── main │ │ ├── java │ │ │ └── com.hotel │ │ │ ├── servlet // 所有Servlet类 │ │ │ ├── util // DBUtil等工具类 │ │ │ └── model // 实体类 │ │ └── webapp │ │ ├── index.jsp // 登录页/首页 │ │ ├── WEB-INF │ │ │ ├── web.xml // Servlet映射配置 │ │ │ └── lib // 依赖jar包非Maven项目 │ │ ├── css │ │ └── js └── sql └── hotel_db.sql // 建库建表脚本不是Maven项目的话src目录会叫 src/com/hotel 这种结构依赖的jar包放在 WEB-INF/lib 下MySQL驱动jar是必须检查的一项。打开IDEA后选择 File - Open选中项目根目录如果是Maven项目IDEA会自动识别 pom.xml 并下载依赖这个过程需要联网耐心等右下角进度条结束。3.2 IDEA里配置Tomcat核心是把Artifact部署对这是整个IDEA运行JavaWeb项目配置里最容易出问题的环节。步骤按下面顺序做不容易漏第一步安装Tomcat。到Tomcat官网下载对应版本Windows选zip包解压到纯英文路径比如 D:\tomcat。注意不要带空格和中文。配置环境变量 CATALINA_HOME 指向解压目录。第二步IDEA里配置Tomcat Server。打开 Run - Edit Configurations点左上角 选 Tomcat Server - Local。在 Application server 区域点 Configure选中Tomcat解压目录IDEA会自动识别版本号。此时先别急着点OK还要配置 Deployment 标签页。第三步配置Deployment。这是新手最容易忽略的步骤。点 Deployment 页签点 选 Artifact弹窗里会列出项目打包后的名字一般是 hotel-system:war exploded选它。下方的 Application context 就是访问路径改成 /hotel 或者干脆 /取决于你题目里要求的访问路径。这一步没配好启动Tomcat时会提示 Error during artifact deployment或者页面404。第四步回到 Server 页签把 On frame deactivation 设为 Update classes and resources这样你改完JSP或静态资源后切换到浏览器就能生效不用反复重启Tomcat。这个设置在调试阶段能省大量时间。配置完成后项目里需要有一个 Lombok 或其他依赖的话检查 pom.xml 里是否声明。下面是一个最小Maven依赖清单dependencies !-- Servlet API -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL 标签库 -- dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency /dependenciesServlet和JSP的scope设为provided是因为Tomcat里自带这两个API不打进war包JSTL和MySQL驱动需要随项目打包。版本号要在 pom.xml 的 properties 里统一管理。Tomcat和JDK的搭配有个经验值JDK 8 搭配 Tomcat 8.5 或 9.x 最稳妥JDK 11 以上就选 Tomcat 9.x。如果你在linux上部署记得给Tomcat的 bin 目录下的 startup.sh 加执行权限想开机自启就在 /etc/rc.d/rc.local 里追加一行启动命令——这些都是后话期末阶段先在IDEA里跑通最重要。3.3 从启动到看到页面最小化验证清单配置好之后点绿色三角启动Tomcat。Tomcat窗口下方会滚动日志看到一行类似Deployment of web application directory [...] has finished的日志说明部署成功。然后再手动做三轮验证每轮都要有稳定预期第一轮确认Tomcat本身活着。浏览器访问http://localhost:8080/看到Tomcat默认首页说明Servlet容器正常。如果这一步就失败问题出在Tomcat安装或端口配置和项目无关。端口被占用就改 conf/server.xml 里的Connector port8080改成8081或8082都行改完重启Tomcat。第二轮确认项目上下文能访问。访问http://localhost:8080/项目名/正常会跳转到登录页 index.jsp。如果看到404先检查IDEA里Deployment的 Application context 和实际路径是否一致再检查 web.xml 中 welcome-file 是否配置了 index.jsp。第三轮确认数据库链路通。在登录页输入用户名密码点登录——前提是你已经把第二章的建表脚本在MySQL里执行过并在 DBUtil 里改成了自己的数据库账号密码。如果登录页面跳转但提示数据库错误回去看JDBC连接串和驱动jar。# 或者用命令行快速验证MySQL是否正常 mysql -uroot -p123456 -e use hotel_db; select count(*) from t_admin;这条命令能直接确认账号密码、数据库名、表是否都在。很多时候Tomcat和IDEA配置没问题就是卡在MySQL这一步。4. 把核心流程写出来ServletJSP的登录、房间与预订实现4.1 登录逻辑Session的存取时机决定你答辩时怎么讲登录功能是整个系统里最经典的ServletJSP交互示例。前端表单提交用户名密码Servlet接收参数、查数据库、校验、写Session、跳转页面。下面是一个完整的LoginServlet写法package com.hotel.servlet; import com.hotel.util.DBUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.sql.*; WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(utf-8); String username request.getParameter(username); String password request.getParameter(password); // 用PreparedStatement防止SQL注入不是拼接字符串 String sql SELECT id, real_name FROM t_admin WHERE username? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { HttpSession session request.getSession(); session.setAttribute(userId, rs.getInt(id)); session.setAttribute(realName, rs.getString(real_name)); response.sendRedirect(index.jsp); // 重定向防止表单重复提交 } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } } catch (SQLException e) { e.printStackTrace(); request.setAttribute(errorMsg, 系统异常请联系管理员); request.getRequestDispatcher(login.jsp).forward(request, response); } } }这段代码里有三个面试官爱问的点。第一为什么用 try-with-resources因为Connection、PreparedStatement、ResultSet都实现了AutoCloseabletry块结束后自动关闭不用手动写finally里那一堆null判空。第二为什么登录成功用 sendRedirect 而不是 forward因为转发地址栏不变刷新页面会再次提交表单造成重复登录重定向是两次请求刷新只刷新目标页面。第三为什么密码校验在SQL里做而不是先查出记录再在Java里比因为让数据库做这件事更高效但前提是SQL里不能拼接字符串——永远用问号占位符这是血泪经验。4.2 客房列表与条件查询JSP里怎么展示数据最规范登录后进主页通常要按状态筛选房间。Servlet从数据库查出房间列表放进request域转发给JSPJSP用JSTL标签遍历。看下面这条链路// RoomListServlet.java 的关键逻辑 WebServlet(/room/list) public class RoomListServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String status request.getParameter(status); // 前端下拉框传来的筛选条件 // 动态拼接SQL时只在无用户输入干扰的地方使用字符串拼接 String sql SELECT r.id, r.room_no, r.status, t.type_name, t.price FROM t_room r JOIN t_room_type t ON r.type_idt.id ; if (status ! null !status.isEmpty()) { sql WHERE r.status? ; } sql ORDER BY r.floor_num, r.room_no; ListRoom roomList new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { if (status ! null !status.isEmpty()) { ps.setInt(1, Integer.parseInt(status)); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Room room new Room(); room.setId(rs.getInt(id)); room.setRoomNo(rs.getString(room_no)); room.setStatus(rs.getInt(status)); room.setTypeName(rs.getString(type_name)); room.setPrice(rs.getBigDecimal(price)); roomList.add(room); } } } catch (SQLException e) { e.printStackTrace(); } request.setAttribute(roomList, roomList); request.getRequestDispatcher(/room_list.jsp).forward(request, response); } }%-- room_list.jsp 中遍历展示的片段 --% % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 cellpadding8 tr th房间号/thth类型/thth价格/thth状态/thth操作/th /tr c:forEach items${roomList} varroom tr td${room.roomNo}/td td${room.typeName}/td td${room.price} 元/晚/td td c:choose c:when test${room.status 0}空闲/c:when c:when test${room.status 1}已预订/c:when c:when test${room.status 2}已入住/c:when c:otherwise维修/c:otherwise /c:choose /td td a hrefroom/book?roomId${room.id}预订/a a hrefroom/checkin?roomId${room.id}入住/a /td /tr /c:forEach /table这套写法的好处是Servlet里看不到HTMLJSP里看不到Java的脚本片段没有那些% %到处飞的写法数据和展示彻底分离。JSP页面里的EL表达式${room.roomNo}对应Room实体类的getRoomNo()方法JSTL标签帮你管好循环和分支。如果期末答辩老师要求你现场给页面加一列「入住天数」你只需要在Room实体类加一个 transient 字段或者直接在SQL里用 DATEDIFF 算出天数再在JSP里加一个td标签即可。4.3 预订房间的事务处理两张表的状态联动别做丢预订动作涉及两张表的更新t_room 表把房间status从0改成1t_order 表插入一条预订记录。这两个操作要么同时成功要么同时失败否则会出现在订单里查得到预订但房间状态没变的情况。用Connection的事务控制WebServlet(/room/book) public class BookRoomServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(utf-8); int roomId Integer.parseInt(request.getParameter(roomId)); String customerName request.getParameter(customerName); String idCard request.getParameter(idCard); String phone request.getParameter(phone); Date checkInDate Date.valueOf(request.getParameter(checkInDate)); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 第一步写入客户信息这里简化为直接插入也可先按证件号查重 String sqlCustomer INSERT INTO t_customer(name, id_card, phone) VALUES(?,?,?); PreparedStatement psCustomer conn.prepareStatement(sqlCustomer, Statement.RETURN_GENERATED_KEYS); psCustomer.setString(1, customerName); psCustomer.setString(2, idCard); psCustomer.setString(3, phone); psCustomer.executeUpdate(); ResultSet keys psCustomer.getGeneratedKeys(); int customerId keys.next()? keys.getInt(1) : 0; // 第二步生成订单号并插入订单 String orderNo BK System.currentTimeMillis(); String sqlOrder INSERT INTO t_order(order_no, customer_id, room_id, order_type, status, check_in_date) VALUES(?,?,?,1,0,?); PreparedStatement psOrder conn.prepareStatement(sqlOrder); psOrder.setString(1, orderNo); psOrder.setInt(2, customerId); psOrder.setInt(3, roomId); psOrder.setDate(4, checkInDate); psOrder.executeUpdate(); // 第三步更新房间状态为已预订 String sqlRoom UPDATE t_room SET status1 WHERE id? AND status0; PreparedStatement psRoom conn.prepareStatement(sqlRoom); psRoom.setInt(1, roomId); int rows psRoom.executeUpdate(); if (rows 0) { throw new SQLException(房间状态已变化请刷新后重试); } conn.commit(); // 三张表操作全部成功才提交 response.sendRedirect(room/list?status1); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); request.setAttribute(errorMsg, 预订失败 e.getMessage()); request.getRequestDispatcher(/book_room.jsp).forward(request, response); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }这段代码的示范价值在于事务边界的把握。conn.setAutoCommit(false)之后后续所有SQL都在同一个事务里任意一步抛异常就回滚不会出现脏数据。UPDATE语句里特意加了AND status0这是乐观锁的思路——如果两个管理员同时对同一个房间操作后执行的那个人受影响行数为0会走进异常分支提示刷新重试而不是静默覆盖。答辩时把这个细节讲出来老师会知道你理解并发问题。4.4 编码与登录拦截用Filter解决两个隐蔽问题中文乱码和未登录访问后台是JavaWeb作业里被问烂的两个点。用Filter一次性解决package com.hotel.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(urlPatterns /*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(utf-8); // 处理POST请求体中的中文 response.setCharacterEncoding(utf-8); // 处理响应中的中文 chain.doFilter(request, response); } } WebFilter(urlPatterns {/room/*, /order/*}) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(userId) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }编码Filter配/*所有请求都过登录拦截Filter只拦需要登录后才能访问的URL前缀。这里有个细节request.getSession(false)加了false参数表示取不到Session就返回null而不是新建——如果在这里用了getSession()未登录用户访问后台页面时也会被创建一个空Session拦截逻辑就失效了。另外GET请求的参数编码问题在Tomcat 8.5之后已经默认处理了UTF-8你主要关注POST即可。5. 跑不通的常见坑Tomcat、MySQL与编码排查记录5.1 Tomcat启动闪退窗口一闪就没了日志在哪儿看现象双击IDEA里的运行按钮控制台没有输出或者Tomcat启动后立刻退出页面完全访问不了。原因Tomcat启动依赖JAVA_HOME环境变量IDEA里虽然配置了JDK但Tomcat本身是通过启动脚本找JAVA_HOME的。环境变量没配或者指向了不存在路径启动脚本直接退出。另一个常见原因是8080端口被占用Oray、微信开发者工具、其他Java进程都可能占住这个端口。解决Windows右键此电脑 - 属性 - 高级系统设置 - 环境变量新建 JAVA_HOME 指向JDK安装目录比如 D:\Java\jdk1.8.0_202在Path里加%JAVA_HOME%\bin。改完重启IDEA再试。端口冲突就打开 Tomcat 的 conf/server.xml改Connector port8080为其他端口。排查日志别只看控制台——到 Tomcat 的 logs 目录看 catalina.out 或 localhost.yyyy-MM-dd.log里面有完整堆栈。5.2 数据库连接失败Cant connect 与 Access denied 要分开看现象Tomcat正常启动页面也能打开登录点完按钮报错——要么是Communications link failure或者Cant connect to MySQL server through socket /tmp/mysql.sock要么是Access denied for user rootlocalhost。原因前者是MySQL服务没启动或者连接串里 host/port 错了后者是账号密码错误或该账号没有远程访问权限。注意那个/tmp/mysql.sock的报错常见于Linux环境是MySQL客户端尝试通过Unix套接字连接路径不对通常要改成-h 127.0.0.1强制走TCP。解决Windows下先在服务管理器确认MySQL服务是「正在运行」状态Linux下执行systemctl status mysqld或systemctl status mysql查看。MySQL没启动就启动它并确认配置了开机自启。账号问题就在MySQL命令行里执行ALTER USER rootlocalhost IDENTIFIED BY 你的密码;。如果是MySQL 8.x还要注意密码加密插件——DBUtil连接串里的 allowPublicKeyRetrievaltrue 就是为它准备的。最后再确认代码里的URL和账号密码跟本机实际一致别把网上抄来的localhost用户名都照搬。5.3 中文全变问号三个编码位置必须同时一致现象页面上显示的中文正常但往数据库里插入的中文存进去全是??或者反过来页面显示乱码。原因编码链路断在某一环。浏览器用UTF-8提交表单Servlet里没有 setCharacterEncodingJSP页面本身是GBK数据库表又是latin1——任意一环不一致中文必乱。解决统一成UTF-8。第一MySQL建库时指定DEFAULT CHARACTER SET utf8mb4已经建好的库用ALTER DATABASE hotel_db CHARACTER SET utf8mb4;第二每个JSP页面顶部写% page contentTypetext/html;charsetUTF-8 languagejava %第三在Filter里对所有请求和响应设置UTF-8第四连接串加characterEncodingutf8。四个位置都检查一遍之后乱码还没解决大概率是MySQL连接的字符集被SQL语句覆盖了在连接串末尾加useUnicodetrue把参数再强调一次。5.4 404与「源服务器未能找到目标资源」Deployment和web.xml双重排查现象浏览器访问http://localhost:8080/hotel/login报404Tomcat日志里没有明显异常。原因这个报错消息是Tomcat返回的标准404响应文案本质是请求的URL没有匹配到任何Servlet。两种常见情况IDEA的Deployment没把项目部署到正确上下文或者Servlet的注解映射路径和前端表单的action路径不一致。解决先在IDEA的 Run 配置里看 Deployment 标签确认 Artifact 已添加、Application context 写的是/hotel。再检查前端JSP里的提交地址——如果表单写在 login.jsp 里form actionlogin methodpost而LoginServlet注解是WebServlet(/login)那么必须保证action和注解路径完全一致。基于注解的Servlet不需要在web.xml里重复声明混用两种方式容易出问题。项目根路径用%request.getContextPath()%拼接避免上下文变化后链接全失效。5.5 ClassNotFoundException驱动jar没进Artifact现象Tomcat启动时日志报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因若你的项目不是Maven结构MySQL驱动jar必须手动放到 WEB-INF/lib 目录下IDEA不一定帮你自动把外部jar加入部署包。若是Maven项目则检查依赖是否成功下载有时IDEA的本地仓库里没有这个依赖版本。解决非Maven项目就把 mysql-connector-java-8.0.30.jar 复制到src/main/webapp/WEB-INF/lib/非Maven是web/WEB-INF/lib/然后在IDEA里右键jar - Add as Library。Maven项目先执行mvn clean再重新加载或者手动从本地仓库删掉对应版本目录让IDEA重新下载。检查一下Artifact的Output Layout里是否包含了lib下的jar。6. 验收前的自测清单把项目从「能跑」打磨到「能讲」临近提交我给你一份自测路径照着走完一遍答辩时基本不会卡壳。核心思路是不要只测「能点通」而是顺着一条完整业务链路把它走穿。先理一条主流程登录 → 查看空闲房间 → 预订一间房 → 在订单列表看到该订单且房间状态变为已预订 → 执行入住 → 退房结算。每一步都要确认数据库对应表的数据变化符合预期。我给你一张速查表自测项操作路径预期结果登录校验输入错误密码提示错误信息不跳转登录成功输入正确账号跳转首页Session生效房间状态列表页筛选空闲房间只显示status0的房间预订联动预订一间空闲房t_room.status变1t_order新增记录重复预订对同一房间再点预订提示失败且不产生脏数据退房结算对入住订单执行退房计算金额正确房间状态回0未登录访问后台直接访问 /room/list自动跳回登录页第二件事把「讲」准备好。老师让你演示时不要只点按钮边点边说这个请求从前端表单发出Tomcat接收到之后根据 web.xml 或注解找到了对应的ServletServlet里调用了DBUtil拿连接执行SQL数据拿到之后放在request域里转发给JSP渲染成表格——这条链路说清楚比你演示十个功能都管用。我在帮人看期末项目时最怕的不是功能少而是项目明明跑得通学生却说不出一句请求是怎么流转的。第三件可以做的加分之举给房间列表的价格字段后面标注「含税」或者「不含税」在数据库里加一个字段页面里加一个判断这演示的是你对业务细节有感知。另一个好做又提分的方向是把密码存储改成哈希后再入库不用加盐只要说明为什么不能明文存——这能展示你的工程安全意识比多写一张表更打动人。这套MySQLIDEATomcatJSPServlet的方案是我见过期末项目里性价比最高的组合。它没有SpringBoot那种一行依赖搞定的爽快感但正是这种处处需要亲手搭的环境让你在调试过程中把JavaWeb的地基夯实了。我也是从「启动Tomcat看一堆红色报错不知道从哪里下手」一路过来的那时候连IDEA控制台里的日志都不会看。现在你至少知道报错先看Tomcat日志连接问题先验证MySQL链路404先检查映射和部署上下文。按这套思路做完这个项目就真正是你的了。希望帮到你。本文还有配套的精品资源点击获取