Servlet+JSP酒店管理系统课程设计:从环境搭建到答辩避坑全链路
简介这是一套面向计算机专业学生与Java Web初学者的酒店管理系统完整项目源码采用servletjspmysqljquery技术栈适合作为毕业设计、课程设计或Java Web入门练手项目。压缩包共15个文件约186.16MB包含sql数据库脚本、项目源码压缩包、论文与答辩PPT等doc/docx文档、mp4操作演示视频以及项目截图覆盖从数据库搭建到系统运行的完整环节。系统涵盖管理员与住店管理、餐饮相关管理等核心业务模块并附有数据库连接说明与初始账号信息方便快速部署调试。已有1666人学习下载资源还配套项目辅导视频、毕业设计论文、中期检查表、任务书与答辩PPT可帮助读者理清开发思路、撰写论文并准备答辩对需要完整交付成果的毕业设计场景尤为实用。1. 从一份能跑起来的 ServletJSP 酒店管理系统说起很多人第一次接触 JavaWeb 课程设计拿到的题目就是「酒店管理系统」。它不像电商那么庞大也不像博客那么单薄刚好覆盖了登录鉴权、房态管理、订单流转、前后端交互这几块核心能力。用 ServletJSPMySQLjQuery 这套组合去实现最大的好处是链路短、依赖少、调试直观——你不需要先搞懂 Spring 的自动装配也不用纠结前端工程化一个 Tomcat 加一个 MySQL 就能把整个系统跑起来。这套方案适合两类人一是正在做课程设计或毕设的学生需要一份结构清晰、能讲清楚设计思路的源码和配套文档二是刚转 JavaWeb 的开发者想通过一个完整项目把 Servlet 生命周期、JSP 内置对象、JDBC 连接池、jQuery Ajax 这几块串起来。后面我会按「环境搭建 → 数据库设计 → 后端 Servlet 实现 → 前端 JSPjQuery 交互 → 避坑排查 → 论文与答辩准备」的顺序把每个环节的关键代码和参数配置讲透。2. 环境搭建与项目骨架Tomcat、JDK、MySQL 的版本对齐2.1 为什么版本对齐比装最新版更重要ServletJSP 这套技术栈对版本非常敏感。Tomcat 9 对应 Servlet 4.0Tomcat 10 直接跳到了 Jakarta EE 9包名从javax.servlet变成jakarta.servlet如果你照着老教程写import javax.servlet.*在 Tomcat 10 上编译直接报错。我一般建议课程设计统一用 JDK 8 Tomcat 8.5 或 9 MySQL 5.7 或 8.0这套组合资料最多出问题最容易搜到答案。MySQL 8.0 和 5.7 的差异主要在驱动类和连接 URL 上。8.0 必须用com.mysql.cj.jdbc.DriverURL 要带时区和 SSL 参数否则启动就报Unknown system variable query_cache_size或者时区错误。下面是我常用的环境清单组件推荐版本关键注意点JDK1.8环境变量 JAVA_HOME 指向 JDK 根目录Tomcat8.5.x / 9.x不要用 10包名不兼容MySQL5.7 / 8.08.0 需改驱动类和 URLIDEIntelliJ IDEA / EclipseIDEA 需配置 Artifact 和 Tomcat Server前端库jQuery 3.x本地引入别依赖 CDN2.2 在 IDEA 里跑通第一个 Servlet 的最小步骤很多人卡在「代码写完了访问 404」。核心原因是 IDEA 的项目结构没配对。下面是我在 IDEA 里新建 Web 项目的标准流程# 1. 新建 Java Enterprise 项目勾选 Web Application # 2. 在 Project Structure 里确认 # - Modules - Sources 下 web 目录被标记为 Web Resource Directory # - Artifacts 下有一个 xxx:war exploded # 3. Run - Edit Configurations - 添加 Tomcat Local # - Deployment 选项卡添加刚才的 war exploded # - Application context 设为 /hotel配置完成后写一个最简 Servlet 验证链路package com.hotel.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/hello) public class HelloServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置响应类型防止中文乱码 resp.setContentType(text/html;charsetUTF-8); resp.getWriter().write(酒店管理系统启动成功); } }这段代码的关键点有三个WebServlet(/hello)注解代替了 web.xml 配置Servlet 3.0 以后都支持resp.setContentType必须在getWriter()之前调用否则中文会变成问号访问路径是http://localhost:8080/hotel/hello/hotel就是你在 Application context 里设的值。2.3 MySQL 建库与连接测试数据库连接是另一个高频翻车点。先在 MySQL 里建库建表字符集统一用utf8mb4CREATE DATABASE hotel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hotel_db; CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(10) NOT NULL UNIQUE, room_type VARCHAR(20) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1已预订 2入住中 );然后用 JDBC 测试连接注意 MySQL 8.0 的 URL 写法// MySQL 5.7 写法 String url jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8; // MySQL 8.0 写法多了时区和 SSL 参数 String url jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; String user root; String password 你的密码; Class.forName(com.mysql.cj.jdbc.Driver); // 8.0 用 cj5.7 可省略 cj Connection conn DriverManager.getConnection(url, user, password);serverTimezone不设会报The server time zone value ?D1ú±ê×?ê±?? is unrecognizeduseSSLfalse是为了避免控制台一堆 SSL 警告。测试通过后再把连接封装成工具类后面所有 DAO 都复用它。3. 数据库表设计与 JDBC 封装房态、订单、用户三张核心表3.1 酒店管理系统的表结构怎么拆酒店管理系统的业务核心是「房间状态随订单变化」。所以最少需要三张表用户表、房间表、订单表。用户表存账号密码和角色管理员/前台房间表存房号、类型、价格、当前状态订单表存入住人、房间、入住离店时间、订单状态。订单表设计有个容易忽略的点不要只存 room_id最好同时冗余 room_no。因为房间可能被删除或改号历史订单要能追溯。另外订单状态和房间状态要联动退房时既要改订单状态也要把房间状态改回空闲这两个操作必须放在同一个事务里。CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, room_id INT NOT NULL, room_no VARCHAR(10) NOT NULL, check_in DATE NOT NULL, check_out DATE NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待入住 1入住中 2已退房 3已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );3.2 用 DBUtil 统一管理连接避免每次 new直接在 Servlet 里DriverManager.getConnection会导致连接无法回收压测几次就Too many connections。正确做法是抽一个工具类用 ThreadLocal 或者连接池。课程设计用 Druid 或 C3P0 都行但为了减少依赖我一般先写一个基于 ThreadLocal 的简易封装答辩时也容易讲清楚。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PWD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 通用关闭方法顺序不能错ResultSet - Statement - Connection 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) {} } }关闭顺序必须是 ResultSet → Statement → Connection反过来会报错。参数说明URL 里的characterEncodingutf8保证中文写入不乱码serverTimezone解决时间字段差 8 小时的问题。这个工具类虽然简单但已经能支撑整个系统的 CRUD答辩时你可以说「基于 JDBC 原生封装未引入 ORM 框架便于理解底层 SQL 执行流程」。3.3 房态查询与分页 SQL 的写法房间列表页通常要分页MySQL 用LIMIT offset, size。这里有个坑offset是从 0 开始的第 1 页 offset0第 2 页 offsetsize。如果前端传页码 page后端要算offset (page - 1) * size。-- 查询空闲房间按价格升序第 2 页每页 10 条 SELECT id, room_no, room_type, price, status FROM room WHERE status 0 ORDER BY price ASC LIMIT 10, 10;LIMIT 10, 10的意思是跳过前 10 条取 10 条。如果写成LIMIT 10只取前 10 条。分页查询还要配一个 count 语句算总页数否则前端分页控件没法渲染。这两条 SQL 建议放在同一个 DAO 方法里减少数据库往返。4. Servlet 处理请求与 JSP 渲染登录、房态、订单的完整链路4.1 登录鉴权Session 存什么、Filter 怎么拦登录是第一个要跑通的业务。流程是JSP 表单提交到 LoginServlet → Servlet 查库比对密码 → 成功则session.setAttribute(user, user)→ 重定向到主页。这里的关键是密码不要明文存课程设计至少做个 MD5答辩时能加分。WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); // POST 中文参数必须设 String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(MD5Util.encode(password))) { req.getSession().setAttribute(user, user); resp.sendRedirect(req.getContextPath() /room/list); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }req.setCharacterEncoding(UTF-8)只对 POST 生效GET 请求的中文乱码要在 Tomcat 的 server.xml 里配URIEncodingUTF-8。sendRedirect和forward的区别要讲清楚重定向是两次请求地址栏变session 还在转发是一次请求地址栏不变request 域能传值。登录成功用重定向是为了防止刷新重复提交。再配一个登录拦截 Filter防止未登录直接访问后台页面WebFilter(/admin/*) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); // 放行 } }WebFilter(/admin/*)拦截 admin 下所有路径chain.doFilter是放行不调用就直接返回了。注意 Filter 里拿 session 要用request.getSession()不要用getSession(false)否则第一次访问会拿不到。4.2 JSP 页面用 JSTL 渲染房态列表JSP 里写 Java 代码% %是课程设计常见做法但答辩时老师更认可 JSTLEL。用 JSTL 渲染房间列表页面干净逻辑清晰% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle房态列表/title/head body table border1 trth房号/thth类型/thth价格/thth状态/thth操作/th/tr c:forEach items${roomList} varroom tr td${room.roomNo}/td td${room.roomType}/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:otherwise入住中/c:otherwise /c:choose /td tda hrefjavascript:book(${room.id})预订/a/td /tr /c:forEach /table /body /html${roomList}是 Servlet 里req.setAttribute(roomList, list)传过来的。c:choose相当于 switch比在页面里写 if-else 清爽。注意 JSTL 的 jar 包要放到 WEB-INF/lib 下否则报The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved。4.3 jQuery Ajax 提交订单并局部刷新前端用 jQuery 发 Ajax后端返回 JSON页面不刷新就能更新房态。这是这套技术栈里最能体现「前后端分离思想」的部分答辩时值得重点讲。function book(roomId) { $.ajax({ url: ctx /order/create, type: POST, data: { roomId: roomId, checkIn: $(#checkIn).val(), checkOut: $(#checkOut).val() }, dataType: json, success: function(res) { if (res.code 200) { alert(预订成功订单号 res.data.orderNo); location.reload(); // 简单场景直接刷新 } else { alert(res.msg); } }, error: function() { alert(网络异常请稍后重试); } }); }后端 Servlet 返回 JSON 需要引入 fastjson 或 gson手动拼字符串容易出转义问题resp.setContentType(application/json;charsetUTF-8); MapString, Object result new HashMap(); result.put(code, 200); result.put(data, order); resp.getWriter().write(new Gson().toJson(result));dataType: json告诉 jQuery 按 JSON 解析响应ctx是pageContext.request.contextPath在页面顶部定义的全局变量避免路径写死。Ajax 的坑在于跨域和编码同源部署下不会跨域但 POST 中文参数要在 Servlet 里设setCharacterEncoding。5. 避坑与排查这套技术栈最容易翻车的 5 个地方5.1 404路径对了但访问不到现象浏览器访问 Servlet 报 404控制台无报错。原因通常是 IDEA 的 Artifact 没更新或者WebServlet的路径和访问路径不一致。解决先看 Tomcat 启动日志里有没有Deploying web application再检查 Application context 和注解路径拼接后的完整 URL。改完代码要 Rebuild Artifact否则跑的还是旧 class。5.2 中文乱码POST 正常 GET 乱码现象表单提交的中文能正常显示但地址栏传参的中文变成乱码。原因是 GET 请求的参数由 Tomcat 按 ISO-8859-1 解码。解决在 Tomcat 的 conf/server.xml 的 Connector 标签加URIEncodingUTF-8或者后端手动new String(name.getBytes(ISO-8859-1), UTF-8)。前者一劳永逸推荐。5.3 数据库连接泄漏跑一会儿就 Too many connections现象系统用几分钟后所有数据库操作都超时MySQL 报连接数满。原因是 DAO 里Connection没关或者异常路径下没走到 close。解决把关闭逻辑放到 finally 块或者用 try-with-resources。更彻底的做法是引入 Druid 连接池配置maxActive20让池子管理连接生命周期。5.4 jQuery 事件绑定失效动态生成的按钮点不动现象房间列表是 Ajax 渲染的里面的「预订」按钮绑定的 click 事件不生效。原因是事件绑定时元素还不存在。解决用事件委托把事件绑到父元素上$(document).on(click, .book-btn, function() { var roomId $(this).data(id); book(roomId); });$(document).on会监听后续动态插入的元素$(this).data(id)读取>Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 orderDao.updateStatus(conn, orderId, 2); roomDao.updateStatus(conn, roomId, 0); conn.commit(); } catch (Exception e) { if (conn ! null) conn.rollback(); throw new RuntimeException(e); } finally { DBUtil.close(conn, null, null); }关键点是 DAO 方法要接收外部传入的 Connection不能自己 new否则事务管不到。setAutoCommit(false)之后必须显式 commit 或 rollback忘了 commit 数据不会落库。6. 论文与答辩怎么把代码讲成设计把功能讲成方案6.1 论文里最该写厚的三章课程设计论文通常要求 8000 字以上很多人把需求分析抄了一堆技术实现却只有两页。我的经验是需求分析写 1500 字够了重点放在「系统设计」和「系统实现」两章。系统设计里画 E-R 图、系统架构图、模块划分图把三张核心表的字段和关系讲清楚。系统实现里按模块写每个模块配流程图和关键代码代码不要全贴贴核心方法加注释说明。论文里有个加分项写清楚「为什么选 ServletJSP 而不是 SpringBoot」。理由可以是「课程设计旨在理解 JavaWeb 底层请求响应模型Servlet 作为规范实现能直观展示 HTTP 请求到业务处理的完整链路JSP 作为视图层便于服务端渲染减少前后端分离带来的调试成本」。这段话答辩时也能直接用。6.2 答辩演示的脚本化流程答辩最怕现场演示翻车。我一般会提前把演示步骤写成脚本按顺序操作每步都有预期结果步骤操作预期结果备用方案1启动 Tomcat控制台无异常访问首页提前录屏2登录 admin/123456跳转房态列表用测试账号3新增房间列表出现新房号直接改数据库4预订房间房态变已预订生成订单展示订单表5退房房态变空闲订单已完成展示事务日志演示前把数据库重置到初始状态避免脏数据导致结果不对。如果现场网络或环境出问题直接切录屏别硬撑。6.3 老师最常问的 4 个问题及回答思路第一个问题通常是「Session 和 Cookie 的区别」。回答Session 存在服务端Cookie 存在客户端Session 依赖 Cookie 传 JSESSIONIDSession 更安全但有超时Cookie 可持久化但易被篡改。第二个问题是「Servlet 生命周期」。回答实例化 → init → service → doGet/doPost → destroy容器只创建一次实例多线程共享所以成员变量要小心线程安全。第三个问题是「事务的 ACID」。回答原子性、一致性、隔离性、持久性结合退房操作讲两个 update 要么都成功要么都回滚。第四个问题是「为什么用 jQuery 不用 Vue」。回答课程设计聚焦后端jQuery 轻量、DOM 操作直观Ajax 封装简单学习成本低适合快速实现局部刷新。如果老师追问「jQuery 还有必要学吗」可以说「作为理解 DOM 和 Ajax 的入门仍然有价值但新项目会选 Vue 或 React」。6.4 我踩过的一个血泪教训最后说个我自己的翻车经历。第一次做这个系统时我把数据库密码硬编码在每个 DAO 里换台电脑演示就连不上现场改代码改了十分钟。后来我把所有配置抽到db.properties用Properties类加载换环境只改一个文件。这个习惯一直保留到现在——配置和代码分离是项目能不能快速迁移的关键。希望帮到你。本文还有配套的精品资源点击获取