JSP+Servlet+MySQL旅游管理系统:JavaWeb全链路实战与避坑指南
简介这是一套基于JSPServletMySQL的JavaWeb旅游管理系统完整项目源码面向正在学习JavaWeb开发、需要课程设计或毕业设计参考的在校学生与初级开发者。系统采用面向对象编程思想覆盖主界面、用户注册、旅游景点、酒店预订与后台管理五大模块可实现景点信息展示与查询、酒店预订流程、塞北人文与故事管理、自驾游路线维护、留言板及账号权限管理等功能适合作为Servlet与JSP综合实践的练手项目。压缩包共738个文件约8.46MB其中111个jsp页面承载各功能视图415个gif与69个jpg构成界面素材另有40个css、23个js负责样式与交互并附带sql脚本、jar依赖及数据库文件导入后即可运行调试。目前已有1111人学习下载读者可借此理清MVC分层结构、掌握前后台数据交互与数据库表设计思路快速搭建可运行的旅游网站原型。1. 从一份 JSP 老项目说起为什么它仍是理解 JavaWeb 全链路的最佳样本打开招聘网站翻 Java 后端岗位十有八九写着 Spring Boot、微服务、分布式。但如果你真去翻很多中小公司的老系统或者接手一个跑了七八年的内部管理平台看到的往往是另一番景象JSP 页面、Servlet 控制器、MySQL 单库、Tomcat 部署。这套技术栈被很多人贴上过时标签可它恰恰是理解 JavaWeb 请求流转最干净的模型——没有自动配置帮你藏起细节没有注解帮你省掉映射浏览器发一个请求容器怎么找到 Servlet、Servlet 怎么拿参数、怎么查库、怎么把结果塞回 JSP每一步都摆在明面上。这份旅游管理系统就是按这个思路搭起来的JSP 负责视图渲染Servlet 承担控制器逻辑MySQL 存业务数据功能覆盖景点展示、酒店预订、用户注册审核、后台管理五大模块。它适合两类人一是正在做课程设计或毕设、需要一套能跑通全流程参考实现的学生二是从 Spring 体系往回补基础、想搞清楚框架到底帮我做了什么的开发者。下面我按实际拆包和部署的顺序把这份资源从结构到跑通再到排错讲一遍。2. 拆开目录看结构JSPServletMySQL 的分层与请求流转2.1 从文件清单反推项目骨架拿到一个 JavaWeb 项目压缩包别急着往 IDE 里导。先看根目录有哪些文件基本就能判断它是 Eclipse 动态 Web 项目还是 Maven 项目。这份资源的根目录出现了.classpath、.project、org.eclipse.wst.common.component、org.eclipse.wst.jsdt.ui.superType.container这几个文件说明它是典型的 Eclipse Dynamic Web Project 结构不是 Maven 工程。.classpath记录编译输出路径和依赖 jar 位置org.eclipse.wst.common.component记录 Web 资源映射关系这两个文件决定了你导入后能不能正确识别WebContent或webapp目录。资源里还出现了多个editor.css、style.css说明前端样式是独立抽出来的没有全部内联在 JSP 里。db.class这个文件值得注意——它大概率是数据库连接工具类的编译产物对应的源码应该是DBUtil.java或DBHelper.java之类封装了 JDBC 连接的获取和释放。一个健康的 JavaWeb 项目数据库连接绝不应该散落在每个 Servlet 里各写一遍必须收敛到一个工具类。标准的分层结构大致是这样层次目录/包职责视图层WebContent/*.jsp页面渲染、表单提交控制层src/.../servlet接收请求、调业务、转发业务层src/.../service业务规则、事务边界数据层src/.../daoSQL 执行、结果集映射模型层src/.../entity与表对应的 JavaBean工具层src/.../util数据库连接、编码过滤如果你导入后发现只有 servlet 和 entity 两层没有独立的 service那说明业务逻辑直接写在 Servlet 里了。这在教学项目里很常见能跑但后期改需求会痛苦。判断标准很简单打开一个 Servlet 的doPost方法如果里面既有request.getParameter又有大段 SQL 拼接那就是三层揉成一层了。2.2 一个请求从浏览器到数据库的完整路径以景点查询为例走一遍完整链路。用户在首页搜索框输入景点名称点击查询浏览器发出GET /travel/SpotServlet?actionsearchname塞北。Tomcat 根据web.xml或WebServlet注解找到对应的 Servlet 类容器调用它的doGet方法。Servlet 从request对象取出name参数调用 DAO 层方法DAO 通过 JDBC 拿连接、执行SELECT * FROM spot WHERE name LIKE ?把ResultSet映射成ListSpot返回。Servlet 把 list 塞进request.setAttribute(spotList, list)然后request.getRequestDispatcher(/spot_list.jsp).forward(request, response)。JSP 页面用 JSTL 或脚本片段遍历 list 渲染成 HTML 表格最终返回浏览器。这条链路里有两个关键点新手容易忽略。第一forward是服务器内部跳转浏览器地址栏不变request域里的数据能带到目标页面redirect是让浏览器重新发请求request域数据丢失。查询结果展示必须用 forward。第二中文参数乱码问题。如果name塞北传到 Servlet 变成乱码不是数据库的问题是请求体编码没设对。Tomcat 8 以后 GET 请求默认用 UTF-8但 POST 请求体默认还是 ISO-8859-1需要在取参数前调用request.setCharacterEncoding(UTF-8)或者配一个 CharacterEncodingFilter 统一处理。2.3 数据库连接工具类的写法与参数db.class对应的源码常见写法是静态加载驱动加DriverManager获取连接。下面这段是我一般会用的结构和资源里的思路一致public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/travel_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD your_password; 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); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { // 按 rs - ps - conn 顺序关闭每个都判空 } }URL 里几个参数必须说清楚。useUnicodetruecharacterEncodingutf8保证存取中文不乱码useSSLfalse关掉 SSL 警告本地开发不需要加密连接serverTimezoneAsia/Shanghai是 MySQL 8 驱动必须带的不带会报时区错误。驱动类名从 MySQL 5 的com.mysql.jdbc.Driver变成了 8 的com.mysql.cj.jdbc.Driver用错版本会直接抛ClassNotFoundException。密码不要硬编码在源码里提交到公开仓库教学项目无所谓但养成习惯用配置文件或环境变量读取。3. 把项目跑起来环境配置、建库导数据与 Tomcat 部署3.1 JDK、Tomcat、MySQL 的版本匹配这套技术栈对版本比较敏感配错了启动就报错。JDK 建议用 8 或 11Tomcat 用 8.5 或 9MySQL 用 5.7 或 8.0。为什么不用 JDK 17 配 Tomcat 10因为 Tomcat 10 把javax.servlet包名改成了jakarta.servlet老项目的 import 语句全部失效除非你手动改包名否则跑不起来。这是新手最容易翻车的地方——下载了最新版 Tomcat导入项目后满屏红色报错以为是代码问题其实是版本不兼容。MySQL 8 和 5.7 的差异主要在驱动类名、时区参数和默认认证插件。8.0 默认用caching_sha2_password认证老版本驱动连不上要么升级驱动 jar 到 8.x要么把用户认证方式改回mysql_native_password。我一般直接统一用 8.0 驱动加 8.0 服务端省得来回折腾。3.2 建库、导表与初始数据项目一般会附带一个.sql文件里面是建表语句和初始数据。导入步骤# 登录 MySQL mysql -u root -p # 创建数据库字符集必须指定 utf8mb4 CREATE DATABASE travel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 切换数据库 USE travel_db; # 导入 sql 文件在 MySQL 命令行外执行 mysql -u root -p travel_db travel_db.sql建库时字符集用utf8mb4而不是utf8因为 MySQL 的utf8其实是阉割版只支持三字节存不了 emoji 和部分生僻字。景点介绍里如果有特殊符号用utf8会插入失败或变成问号。utf8mb4_general_ci排序规则对中文够用如果对排序精度要求高可以换utf8mb4_unicode_ci。导入后检查几张核心表是否存在用户表存注册信息和审核状态、景点表、酒店表、订单表、留言表。用户表里通常有个status字段0 表示待审核1 表示已通过。注册后不能直接登录就是因为这个字段初始为 0管理员在后台改成 1 之后才能登录。这个设计在教学项目里很典型理解它有助于调试注册成功但登录失败的问题。3.3 在 IDE 里配置并部署到 Tomcat以 Eclipse 为例导入步骤是File - Import - Existing Projects into Workspace选中项目根目录。导入后右键项目Properties - Java Build Path确认 JDK 版本和 jar 依赖没有报错。然后Properties - Project Facets确认 Dynamic Web Module 版本和 Java 版本匹配。最后配置 Tomcat 运行时Window - Preferences - Server - Runtime Environments添加 Tomcat 安装目录再把项目添加到服务器实例右键Run As - Run on Server。如果用 IDEA步骤略有不同File - Project Structure - Modules检查 Web 资源目录是否被识别Artifacts里确认有 exploded war 输出然后在 Run Configuration 里添加 Tomcat Local部署 artifact。IDEA 里最常见的坑是 Web 资源目录没被标记导致 JSP 404需要在 Facets 里手动指定 Web Resource Directory。启动成功后浏览器访问http://localhost:8080/项目名/应该能看到首页。如果首页能打开但图片和 CSS 加载不出来检查资源路径是相对路径还是绝对路径以及web.xml里welcome-file配置是否正确。4. 核心模块的实现细节注册审核、景点查询与酒店预订4.1 用户注册与管理员审核的状态流转注册模块的完整流程是用户填表单提交 - Servlet 校验参数 - 查重用户名是否已存在- 插入用户表status设为 0 - 提示等待审核 - 管理员后台看到待审核列表 - 点击通过status更新为 1 - 用户此时才能登录。登录校验的 SQL 必须同时判断用户名、密码和状态String sql SELECT * FROM user WHERE username ? AND password ? AND status 1;如果只查用户名和密码待审核用户也能登录审核机制就形同虚设。密码存储方面教学项目通常明文存但合格的做法是存 MD5 或 BCrypt 哈希。如果资源里是明文你自己改的时候至少加一层 MD5别让数据库里躺着明文密码。注册表单的校验分前端和后端两层。前端用 JavaScript 校验非空、格式、两次密码一致后端必须再校验一遍因为前端校验可以被绕过。后端还要处理用户名重复的情况靠数据库唯一索引兜底插入时捕获SQLIntegrityConstraintViolationException给用户友好提示而不是抛 500 错误页。4.2 景点模糊查询与分页景点查询用LIKE做模糊匹配SELECT * FROM spot WHERE name LIKE CONCAT(%, ?, %) LIMIT ?, ?CONCAT拼接通配符比在 Java 里拼%name%更安全配合PreparedStatement能防 SQL 注入。分页用LIMIT offset, sizeoffset (pageNum - 1) * pageSize。前端传当前页码后端算出 offset同时再查一次COUNT(*)得到总条数算出总页数返回给页面渲染分页条。这里有个性能细节LIKE %关键词%前置通配符会导致索引失效全表扫描。数据量小无所谓数据量上万就要考虑全文索引或搜索引擎。教学项目数据量小不用过度优化但要知道这个边界在哪。景点详情页通常还要展示图片。图片存法有两种存文件路径或者存二进制。教学项目一般存路径图片文件放在WebContent/images/下数据库存相对路径。部署后如果图片不显示先检查路径拼接是否正确再检查 Tomcat 是否有权限读取该目录。4.3 酒店预订的库存与订单一致性酒店预订比景点查询复杂因为它涉及状态变更。用户点预订 - 生成订单记录 - 酒店可订数量减一。这两步必须保证一致性否则会出现订单生成了但库存没减或库存减了但订单没生成。教学项目里常见做法是在一个 Service 方法里顺序执行两条 SQL用 JDBC 事务包起来Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 1. 插入订单 orderDao.insert(conn, order); // 2. 扣减库存 hotelDao.decreaseStock(conn, hotelId); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn, null, null); }关键点是 DAO 方法要接收外部传入的Connection而不是自己内部再获取一个否则两条 SQL 不在同一个事务里回滚无效。这是 JDBC 事务最容易被写错的地方。另外扣库存的 SQL 要加条件WHERE stock 0防止超卖UPDATE hotel SET stock stock - 1 WHERE id ? AND stock 0根据返回的受影响行数判断是否扣减成功如果返回 0 说明库存不足抛异常触发回滚。5. 避坑与排查那些让项目跑不起来的常见问题5.1 启动报 ClassNotFoundException: com.mysql.jdbc.Driver现象Tomcat 启动或第一次访问数据库时报驱动类找不到。原因MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver代码里还写着旧版com.mysql.jdbc.Driver或者WEB-INF/lib下根本没放 mysql-connector jar。解决确认 jar 包已放入WEB-INF/lib并加入 Build Path把驱动类名改成com.mysql.cj.jdbc.Driver。如果用的是 Maven检查 pom 里 mysql-connector-java 的版本和 scope。5.2 中文乱码页面显示问号或方块现象JSP 页面中文正常但提交到后台存进数据库变成???或者从数据库读出来显示乱码。原因三个环节的编码不一致——JSP 页面pageEncoding、请求体编码、数据库连接编码。解决JSP 顶部统一写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 取参数前调request.setCharacterEncoding(UTF-8)JDBC URL 加useUnicodetruecharacterEncodingutf8数据库和表用utf8mb4。四个地方全对上才不会乱。5.3 404JSP 能找到但 Servlet 访问不了现象直接访问 JSP 页面正常但表单提交到 Servlet 报 404。原因web.xml里 servlet 映射路径写错或者WebServlet(/SpotServlet)注解的路径和表单 action 不一致也可能是web.xml的metadata-completetrue导致注解被忽略。解决核对表单 action、注解路径、web.xml 映射三者是否一致如果用了注解确认web.xml没有设metadata-completetrue检查访问 URL 是否带了项目上下文路径。5.4 数据库连接池耗尽或连接未关闭现象访问几个页面后系统卡死报Too many connections。原因DAO 里获取了 Connection 但异常路径下没关闭连接泄漏。解决所有数据库操作放在 try-finally 里finally 中按ResultSet - PreparedStatement - Connection顺序关闭或者引入 Druid、HikariCP 连接池统一管理。教学项目用DriverManager每次新建连接并发一高就崩生产环境必须用池。5.5 修改代码后刷新页面没变化现象改了 JSP 或 Java 代码重启 Tomcat 后页面还是旧的。原因浏览器缓存了旧页面或者 IDE 没有重新编译 class 文件或者 Tomcat 的 work 目录缓存了编译后的 JSP。解决浏览器强制刷新CtrlF5IDE 里Project - Clean重新编译删除 Tomcat 的work/Catalina目录让它重新编译 JSP确认部署的是最新编译输出而不是旧的 war 包。6. 从能跑到好用连接池替换与 SQL 注入自查项目跑通之后如果想让它在课程设计答辩或实际演示中更稳有两件事值得做。第一件是把DriverManager换成连接池。每次请求新建 TCP 连接开销很大连接池复用连接能把响应时间降一个量级。用 Druid 的话加一个druid.properties配置文件改DBUtil从DataSource取连接driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/travel_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot passwordyour_password initialSize5 maxActive20 maxWait3000initialSize是启动时创建的连接数maxActive是最大连接数maxWait是获取连接的超时毫秒数。这三个值按演示并发量调课程设计场景 initialSize 给 5、maxActive 给 20 足够。改完之后DBUtil.getConnection()内部从DruidDataSource拿关闭时调connection.close()实际是归还池子而不是真关代码不用大改。第二件事是 SQL 注入自查。把所有 DAO 方法过一遍确认没有字符串拼接 SQL。反面写法是SELECT * FROM user WHERE name name 正面写法是PreparedStatement加?占位符。登录、查询、删除这几类操作是注入重灾区重点查。自查方法很简单在输入框里填 OR 11如果登录成功了说明有注入漏洞。修完之后再试一次应该提示用户名或密码错误。我自己的习惯是每次接手一个 JDBC 项目先把所有 DAO 里的 SQL 语句 grep 一遍看有没有号拼接变量。这个动作花不了五分钟但能挡掉大部分低级安全问题。从那以后我每次部署这类老项目都强制走一遍连接池替换 注入自查 编码核对三件套比事后救火省心得多。希望帮到你。本文还有配套的精品资源点击获取