Java Web老架构实战:JSP+Servlet+JDBC实现旅游管理系统全解析
简介基于 Java JSP 的洛阳旅游管理系统毕业设计源码包面向高校计算机相关专业学生适用于课程设计、毕业设计或 Java Web 综合练习。系统采用 JSP jQuery 搭建前台Servlet JDBC 处理后端数据集成景点浏览、资讯发布、留言互动等完整功能模块兼容 JDK 1.8、Tomcat 9 与 MySQL 5.7 常见教学环境。资源共 349 个文件压缩包约 30.18MB49 个 Java 源文件负责业务逻辑与数据访问30 个 JSP 页面、31 个 JS 脚本和 29 个 CSS 样式表实现页面结构与交互效果92 个 PNG 与 53 个 JPG 图片素材支撑界面展示1 个 SQL 数据库脚本可直接初始化核心表数据。核心模块包含网站首页、畅游景点、资讯管理、景点管理、留言中心、管理员管理和系统管理支持景点图片上传、名称模糊搜索、网站基本信息配置等操作目录分层清晰便于按功能模块对照学习。资源内含可直接导入的数据库与完整前后台代码适合参考学习 ServletJDBC 的经典分层写法、后台管理开发思路和 JSP 前台搭建方式目前已有 33 人学习下载。1. 为什么 2025 年还在用 JSPServlet洛阳旅游管理系统这套老架构的真实价值如果你正在找一个能跑、能改、能讲清楚的 java 课程设计案例源码手上这套「基于 javajsp 的洛阳旅游管理系统」看起来确实有点复古Servlet JSP JDBC连框架都没用。但我的判断恰恰相反——正因为它是老架构才更适合做毕业设计和课程设计。Spring Boot 把太多细节藏起来了老师问一句「请求是怎么走到你这段代码的」很多同学当场卡壳。这套系统前端是 JSP jQuery Bootstrap后端是 Servlet JDBC数据库是 MySQL整条链路完全裸露每一条请求路径、每一次数据库查询都能在源码里精确定位。功能上覆盖了景点管理、资讯管理、留言中心、管理员管理和站点设置前台展示和后台管理都有拿来当管理系统的样板完全够格。适合两类人一类是需要一个能答得上来的项目去交毕设的学生另一类是后端开发做久了想回头把 Java Web 地基补扎实的从业者。2. 读懂项目骨架三层结构、五类数据表与一次完整请求的流转2.1 先分清层Servlet 不是写业务的地方拿到源码先别急着启动先在 IDEA 里把目录结构过一遍。这类传统 JSP 项目的包结构高度相似你大概率会看到这样的层级src/com/luoyang/travel/ ├── servlet/ # 控制层接收请求、调用业务、转发页面 ├── service/ # 业务层处理逻辑与事务边界 ├── dao/ # 数据访问层封装 JDBC 操作 ├── entity/ # 实体类对应数据表字段 └── util/ # 工具类数据库连接、字符串处理等注意不同来源的源码包名可能不一样常见的是com.xxxx.servlet、com.xxxx.dao这类写法核心看src目录下的实际结构即可。这套三层结构的核心思想是「Servlet 不写业务JSP 不写 Java 代码」。Servlet 拿到请求后只做三件事取参数、调 Service、转页面。真正的增删改查逻辑在 Service 和 Dao 里。面试或答辩时如果被问到「为什么分层」标准答法就是降低耦合每一层只关心自己的事后面改成 SSM 或 Spring Boot 时页面和数据库不用动只换中间两层。2.2 核心数据表景点、资讯、留言、管理员、系统设置五大模块从项目功能介绍反推数据库里至少要有五类表我用一个比较常见的落库方式列出来供你对照模块表名常见命名核心字段对应功能景点t_scenicid、name、image、address、price、content、create_time前台畅游展示后台增删改查资讯t_newsid、title、image、content、publish_time前台资讯展示后台增删改查留言t_messageid、nickname、content、create_time游客前台留言后台删除管理员t_adminid、username、password后台登录与账号管理系统设置t_configid、site_name、keywords、description、footer网站名称、底部信息的动态配置以景点表为例建表语句差不多长这样CREATE TABLE t_scenic ( id INT(11) NOT NULL AUTO_INCREMENT COMMENT 景点ID, name VARCHAR(120) NOT NULL COMMENT 景点名称, image VARCHAR(255) DEFAULT NULL COMMENT 封面图路径, address VARCHAR(255) DEFAULT NULL COMMENT 景点地址, price DECIMAL(10,2) DEFAULT 0.00 COMMENT 门票价格, content TEXT COMMENT 景点详细介绍, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENT景点信息表;price用DECIMAL(10,2)而不是float是行业惯例避免浮点精度问题content用TEXT是因为景点介绍通常有几百上千字VARCHAR(255)放不下。后面你自己加字段时照着这个风格写就行。2.3 把请求路径理顺前台游客和后台管理员是两条独立的线这个系统的前后台是两套入口。前台从index.jsp网站首页进入游客可以浏览轮播图、热门景点、资讯列表点景点名称会跳转到详情页。后台从admin/login.jsp进入登录后进入管理页面进行景点、资讯、留言、管理员和系统设置的管理。以「管理员修改景点价格」为例一次完整请求的流转是浏览器提交表单 → ScenicServlet?actionupdate → 取参数封装成 Scenic 对象 → 调 ScenicService.update(scenic) → 调 ScenicDao.update(scenic) → PreparedStatement 执行 UPDATE SQL → 请求重定向回列表页 → 页面重新查一次这个链路里有两个细节值得注意。第一新增和修改通常会合并处理actionadd走 INSERTactionupdate走 UPDATE但表单字段几乎一样第二提交成功后用的是sendRedirect而不是forward这样浏览器地址栏会变成列表页地址用户刷新时不会重复提交表单——这个叫 PRG 模式很多老项目没做答辩时能说清楚这一点是加分项。系统里的「关键词搜索」「景点名称模糊搜索」本质上是同一件事WHERE name LIKE %关键词%。你在源码里搜一下LIKE就能找到景点搜索和资讯搜索各自的实现把%拼接的位置换一换就能复用到别的模块。3. 本地复现全流程导入 5.7 数据库、IDEA 部署 Tomcat 9 到后台登录3.1 版本对齐JDK 1.8 配 Tomcat 9 是这套源码的黄金组合项目要求的环境是 JDK 1.8、MySQL 5.7.26、Tomcat 9.0.73、IntelliJ IDEA 2021.3。这套组合是传统 JSP 项目里最稳的配比JDK 1.8 是 Servlet 项目的长期稳定版本Tomcat 9 对应 Servlet 4.0 规范MySQL 5.7 对 JDBC 驱动的兼容性也最好。如果你本机装的是 JDK 17tomcat 大概率也能启动但 JSP 编译时可能遇到UnsupportedClassVersionError所以除非你处理过这类问题否则老老实实装一个 JDK 1.8用 IDEA 的 Project Structure 把项目的 SDK 指过去。有一个前置检查值得做确认%JAVA_HOME%指向 JDK 1.8不是 JRE也不是 JDK 17。可以在命令行跑一下确认java -version # 期望输出java version 1.8.0_xxx echo %JAVA_HOME% # Windows 下确认路径不含 jre如果你同时装了多个 JDKIDEA 里 File → Project Structure → Project SDK 选 1.8同时 Settings → Build Tools 里没有 Maven 依赖问题时一般不会串版本。真正容易翻车的是 Artifacts 配置后面 3.4 小节专门讲。3.2 第一步把 sql 文件导入 MySQL 5.7数据库文件通常是一个.sql文件。不要直接双击打开也不要盲目地整文件复制到 Navicat 的查询窗口按执行——如果文件里有建库语句而当前连接用户没有建库权限就会报错。我最常用的是命令行方式先手动建库再导入mysql -uroot -p # 输入密码后进入 MySQL 命令行 mysql CREATE DATABASE luoyang_travel DEFAULT CHARACTER SET utf8; mysql USE luoyang_travel; mysql SOURCE D:/downloads/luoyang_travel.sql;CREATE DATABASE这里建议加DEFAULT CHARACTER SET utf8避免建出来的库是latin1导致后面中文乱码。SOURCE后面用正斜杠或双反斜杠Windows 路径直接D:\...会被转义。导入完成后执行一下mysql SHOW TABLES;能看到五张以上的表说明导入成功。如果你用的是 Navicat操作路径是右键目标库 → 运行 SQL 文件 → 选中.sql文件 → 开始。注意一定要先选中库不然表会导到默认库里去。3.3 第二步IDEA 导入项目与修改数据库连接配置用 IDEA 打开项目后如果右下角提示框架未识别不用急着点。先找到数据库配置文件——这类项目通常有一个db.properties或jdbc.properties也可能直接写死在JdbcUtil.java里。打开后你会看到类似这样的内容jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/luoyang_travel?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password123456这里的四个值需要改成你本机的实际信息。localhost:3306是 MySQL 端口如果你装的是 3307 之类的自定义端口必须同步改luoyang_travel是刚才建的库名以.sql文件里的实际库名为准username和password用自己的 MySQL 账号。改完之后确认一下lib目录或 Maven 依赖里有没有 MySQL 驱动 jar。如果项目没有 Maven 而是直接把 jar 放在WEB-INF/lib下进入 IDEA 后要手动把lib目录标记为库右键WEB-INF/lib→ Add as Library → OK。这一步漏了运行时会报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver代码本身没问题却启动不起来很冤枉。3.4 第三步配置 Tomcat 9 并启动IDEA 里选择 Add Configuration → 左上角加号 → Tomcat Server → Local。注意如果列表里没有 Tomcat Server 这一项说明没装 Tomcat 插件或者 IDEA 版本差异去 Settings → Plugins 里确认 Tomcat 支持已启用。添加完成后关键一步是 Deployment部署页签。点加号选 Artifact如果下拉框是空的先去 File → Project Structure → Artifacts → 加号 → Web Application: Exploded把项目 Web 根目录指进去。然后回到 Deployment 页把 Application context 填成/luoyang_travel这个值就是你的访问路径前缀。完整访问地址是http://localhost:8080/luoyang_travel/index.jsp端口在 Server 页签里改默认 8080。如果和本机其他服务冲突比如之前跑过项目占用了改成 8081 也可以。启动后如果浏览器打开的是 Tomcat 默认首页而不是你的项目说明 Deployment 里没选对 Artifact或者 Application context 填成了/回到上一步检查。3.5 验证前台首页与后台登录各跑一遍启动完成后先访问前台首页确认轮播图、景点列表、资讯列表能正常渲染。然后访问后台http://localhost:8080/luoyang_travel/admin/login.jsp后台登录账号多数版本默认是admin / admin123如果登录不进去去数据库t_admin表里看一眼已有的账号密码。密码如果是明文就直接抄如果是 MD5 加密可以用 SQL 临时改一条记录UPDATE t_admin SET password MD5(admin123) WHERE username admin;这里用MD5()是因为老项目常见做法是 MD5 存储新版本如果校验逻辑不同以源码里LoginServlet的比对方式为准。能登录进去并且后台列表页能显示数据说明数据库连接、Servlet 映射、JSP 编译这三条链路全通了。4. 核心代码拆解JDBC 工具类、Servlet 分发与 JSP 页面渲染4.1 JDBC 工具类连接、查询、关闭三件事这套源码的数据访问层核心是一个 JDBC 工具类通常叫JdbcUtil或DBUtil。传统 JSP 项目里它的写法大同小异核心是「注册驱动 → 获取连接 → 执行 SQL → 关闭资源」四步package com.luoyang.travel.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class JdbcUtil { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/luoyang_travel ?useUnicodetruecharacterEncodingutf8useSSLfalse; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(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(ResultSet rs, PreparedStatement ps, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps ! null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }静态代码块里的Class.forName是 JDBC 4.0 之前的标准加载方式作用是让驱动类完成自身注册。close()方法按 ResultSet → PreparedStatement → Connection 的顺序逆序关闭数据库连接是稀缺资源用完必须释放。注意关闭时用 try-catch 包住而不是直接抛出否则前面语句出错会导致后面的资源泄露。这套代码在项目里被大量调用你可以搜索JdbcUtil.getConnection()看它被引用了多少次。如果老师问「连接池是什么」答案是这套代码每个请求都新建连接高并发下性能堪忧生产系统会用 Druid 或 C3P0 做连接池但毕设场景不要求。4.2 Servlet 典型写法doGet 和 doPost 做 action 分发传统 JSP 项目里一个 Servlet 对应一个模块是常见约定比如ScenicServlet管景点、NewsServlet管资讯。关键在于同一个 Servlet 内部用 action 参数区分不同操作写法一般是package com.luoyang.travel.servlet; import com.luoyang.travel.entity.Scenic; import com.luoyang.travel.service.ScenicService; 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; import java.util.List; WebServlet(/admin/scenic) public class ScenicServlet extends HttpServlet { private ScenicService scenicService new ScenicService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (list.equals(action)) { ListScenic list scenicService.listAll(); req.setAttribute(list, list); req.getRequestDispatcher(/admin/scenic/list.jsp).forward(req, resp); } else if (edit.equals(action)) { int id Integer.parseInt(req.getParameter(id)); req.setAttribute(scenic, scenicService.getById(id)); req.getRequestDispatcher(/admin/scenic/edit.jsp).forward(req, resp); } else if (delete.equals(action)) { int id Integer.parseInt(req.getParameter(id)); scenicService.delete(id); resp.sendRedirect(scenic?actionlist); } else { resp.sendRedirect(scenic?actionlist); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (add.equals(action)) { // 读取表单字段封装成 Scenic 对象调 service 保存 } else if (update.equals(action)) { // 读取表单字段调 service 更新 } // 处理完成后重定向回列表 resp.sendRedirect(scenic?actionlist); } }分发逻辑用if-else串起来是传统 JSP 项目最常见的写法你不用改成分支模式看懂就够了。doPost里新增和修改共用一套表单读取逻辑只是最终调用的 Service 方法不同。web.xml里也可能用servlet-mapping做同样的映射如果你的源码里没有WebServlet注解就去web.xml里查 URL 对应的servlet-class。有个容易被忽视的点req.getParameter(id)返回的是字符串转int时如果参数为空或非法会抛NumberFormatException。老项目普遍不处理这个你也可以不改但答辩被问到「输入非法参数怎么办」时知道这是个隐患总比完全懵掉强。4.3 JSP 页面渲染JSTL 遍历、Bootstrap 样式与天气接入前台的 JSP 页面用的是 Bootstrap 3 那一套经典样式配合 jQuery 做交互。页面里景点列表的渲染方式一般是div classcontainer div classrow c:forEach varscenic items${scenicList} div classcol-md-4 div classthumbnail img src${scenic.image} alt${scenic.name} div classcaption h3${scenic.name}/h3 p${scenic.address}/p p a hrefscenic?actiondetailid${scenic.id} classbtn btn-primary rolebutton查看详情/a /p /div /div /div /c:forEach /div /divc:forEach是 JSTL 的循环标签items是 Servlet 里setAttribute进去的集合名var是循环变量。${scenic.name}是 EL 表达式等价于调用scenic.getName()。这套组合是 JSP 入门的必修课源码里每个列表页都是这个套路看懂了就相当于看懂了全部页面的渲染逻辑。轮播图用的是 Bootstrap 自带的 Carousel 组件页面底部网站名称、关键词、描述等设置从t_config表里读取由系统管理模块来维护。天气模块在源码里通常有两种实现方式一种是后端 Servlet 转发第三方天气接口的数据另一种是前端 JS 直接调用免费天气 API。无论哪种你都会在代码里找到一个需要填appkey或key的位置这个 key 需要自己去天气服务商注册注册后替换原来的占位符即可。5. 避坑指南这套源码最容易翻车的 5 个地方5.1 数据库导入就报错现象与排查现象在 Navicat 里运行.sql文件弹出Unknown database或者You have an error in your SQL syntax。原因Unknown database是因为脚本里有USE xxx;而你的 MySQL 里还没有这个库语法报错一般是 MySQL 8.0 的sql_mode与 5.7 差异导致或者脚本里用了当前版本不支持的语法。解决先手动建库再导入不要直接整文件运行。用 3.2 小节命令行方式最省事。如果语法报错是sql_mode引起的可以在当前连接执行SET GLOBAL sql_mode ALLOW_INVALID_DATES之类兼容设置但这属于临时手段最稳妥的还是安装 MySQL 5.7。常见做法是导入前先问一下卖家或作者用的 MySQL 版本和版本对齐就完事。5.2 Tomcat 启动后访问 404Artifacts 没部署现象Tomcat 正常启动日志没有报错浏览器访问http://localhost:8080/luoyang_travel/index.jsp返回 404或者跳到了 Tomcat 默认首页。原因IDEA 里创建了 Tomcat 配置但 Deployment 页签是空的项目没有部署到 Tomcat或者 Artifact 类型选错选成了空的 web 应用。解决打开 Run → Edit Configurations → Deployment点加号选择 Artifact确认 Application context 填的是/luoyang_travel。Artifact 列表如果是空的先去 File → Project Structure → Artifacts新建一个 Web Application: Exploded重新构建后再回来选。如果改完还不行手动 Build → Build Artifacts → Rebuild 一次。这个坑几乎是传统 JSP 项目部署的必踩项遇到 404 先看这个别去改代码。5.3 页面中文全部是问号三处编码要一起改现象前台首页和后台页面全是乱码搜出来的景点名称显示为??或方块。原因JSP 页面编码、Tomcat 请求编码、JDBC 连接编码三处没有对齐。最常见的是数据库连接串里没加characterEncodingutf8或者 JSP 页面头部写的是ISO-8859-1。解决按顺序检查三处。第一处是 JSP 页面顶部必须有% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%第二处是db.properties的连接串里要带useUnicodetruecharacterEncodingutf8第三处是 Tomcat 的server.xmlConnector 标签上加URIEncodingUTF-8。改完重启不要只改其中一处。数据库导入之前的DEFAULT CHARACTER SET utf8也要确认否则改三处代码也没用。5.4 图片上传成功但页面访问 404现象后台添加景点时选择图片提示上传成功但前台页面图片位置裂开直接访问图片地址返回 404。原因代码里常见的实现是用getServletContext().getRealPath(/upload)获取上传目录这个路径在 IDEA 部署模式下通常指向 IDEA 的输出目录或者 Tomcat 的临时目录不是项目里的webapp/upload。重启 Tomcat 后上传的文件可能直接被清空或者指向错误。解决这个问题的标准解法是给上传目录做外部映射。在 Tomcat 的conf/server.xml或项目的META-INF/context.xml里加一个虚拟目录配置Context docBaseD:/luoyang_upload path/upload reloadablefalse /这样磁盘上D:/luoyang_upload目录里的文件就能通过http://localhost:8080/upload/xxx.jpg访问。改完后把代码里上传路径改成写入这个外部目录不再依赖getRealPath。如果不想改代码那至少做到每次重启后重新上传图片别让旧图残留影响评审印象。5.5 天气模块一直空白或加载失败现象首页其他内容都正常只有天气区域空白或者控制台里报网络请求 403/401。原因天气数据来自第三方接口免费接口通常要求注册获取应用密钥appkey源码里大概率是一个写死的占位符需要你替换成自己的。还有少数情况是接口域名过期或服务商调整了协议。解决在源码里搜索weather、appkey、apiKey这些关键词找到请求天气接口的代码段。前端方式就找 JS 文件里的 key 配置后端方式就找 Servlet 里的请求地址。注册一个免费天气服务商把 key 填进去重启再看。如果源码里用到的服务商已经不提供免费额度就换一个服务商把请求 URL 和返回字段解析逻辑按照新文档微调一般半小时内能搞定。6. 进阶玩法用 Spring Boot MyBatis 重写后端前端 JSP 直接复用6.1 这套源码最值钱的不是代码是数据结构把整套系统拆开看真正沉淀下来的有价值资产是数据库设计和页面布局。五张表的字段关系、前台首页的信息架构、后台管理页面的交互流程这些是从需求文档一步步推导出来的换一个框架它们依然成立。所以我的建议是不要推倒重来保留数据库和 JSP 页面只把 Servlet JDBC 这层替换成 Spring Boot MyBatis。迁移的正确顺序是先建 Mapper再建 Service 接口最后写 Controller。原ScenicDao里的 SQL 大部分可以直接搬进ScenicMapper.xml比如select idselectByName resultTypecom.luoyang.travel.entity.Scenic SELECT * FROM t_scenic WHERE name LIKE CONCAT(%, #{name}, %) /select然后 Controller 里把原来ScenicServlet的doGet方法拆成细粒度接口actionedit变成GetMapping(/admin/scenic/edit)actiondelete变成GetMapping(/admin/scenic/delete)。JSP 页面里原来指向scenic?actionlist的链接改成/admin/scenic/list页面表单的提交路径同步改一下即可。6.2 迁移后立刻获得三个收益第一连接池不再是黑匣子Spring Boot 默认的 HikariCP 把连接管理接管了不用每个请求新建连接。第二事务控制从原来手写conn.setAutoCommit(false)变成Transactional一个注解解决问题。第三依赖管理清晰了pom.xml里声明mybatis-spring-boot-starter、mysql-connector-java即可不会再出现 jar 包缺失导致的 ClassNotFoundException。6.3 验证标准页面行为完全一致迁移完的验收标准很简单前台首页的轮播图、景点列表、资讯列表、留言提交后台的登录、增删改查、图片上传一个功能一个功能地过一遍。原来页面长什么样新系统就该长什么样。我一般会花半天做完迁移再用半天对照原系统逐条过功能因为表面上一样的操作背后可能藏着sendRedirect重定向和forward转发的细微差别URL 变了但跳转语义要一致。说实话我最早拿到这种传统 JSP 项目源码时第一反应也是「这也太老了」。但拆完数据库设计和页面结构之后我反而觉得它比很多 Spring Boot 项目更有教学价值——没有框架帮你兜底占位符、编码、路径、部署每一个细节都得亲手处理踩过一次的坑后面几十年都记得。从那以后我每次拿这种老项目都会强制走一遍「先读结构、再导数据、再部署、最后改代码」的流程顺序错了就会多花两倍时间。希望这篇拆解能帮你把复现时间压缩到一晚上把精力留给真正值得打磨的答辩材料。本文还有配套的精品资源点击获取