☰
JSP火车查询系统实战指南:从技术选型到部署避坑
2026/9/29 23:58:04 网站建设 项目流程

简介:基于JSP与MySQL开发的火车查询系统,是一套面向高校Java Web课程设计及毕业设计的完整项目源码。系统采用浏览器/服务器模式,实现了车次信息、站台管理、用户注册与公告发布等核心功能,业务逻辑与数据库访问通过Java类分层完成,有助于初学者理解前后台交互和持久化设计思路。压缩包内共有一百四十一个文件,其中包含四十余个JSP动态页面、十个Java业务类及对应编译后的class文件,配套SQL数据库脚本和若干配置文件,另有大量GIF图片用于页面展示,整包大小仅七百零七KB,结构清晰,可直接导入开发工具运行。默认管理员账号为admin,密码为admin,数据库配置集中在DBO.java文件中,方便快速搭建调试环境。目前已有三百二十二人学习参考,适合需要对照完整代码完成课程设计、复习Java Web开发细节或扩展火车票务管理功能的人群。

1. 为什么“JSP火车查询系统”至今还是 JavaWeb 课程设计里最稳的练手项目

每年到课程设计季,“基于 JSP 的毕设选题”“javaweb 头歌实训答案”这类检索词就会被刷一轮。火车查询系统恰好是 JSP + Servlet + MySQL 三类技术最典型的交汇点:它有明确的实体关系、有带参查询、有表单提交后的页面跳转,又没有电商项目那些订单状态机、支付回调之类的复杂事务。对刚走完 Java 基础、想搞清楚 JSP 到底怎么和数据库联动的人来说,这个选题能把“请求—处理—响应”整条链路完整跑通,中间还能顺带掌握 JDBC 封装、连接池配置、war 包部署这些工作里真正会用到的东西。

但网上能找到的同类源码质量参差不齐,很多版本还是十几年前的教学写法,Servlet 里堆满 System.out.println,JSP 页面里直接写 JDBC 连接。这类代码在 IDEA 里新建 JSP 项目后跑通很容易,一旦面对 Tomcat 版本差异、MySQL 8 的驱动加载方式变化,翻车概率直线上升。这篇笔记就从零开始,把“JSP火车查询系统”拆成可复现的六步:先立住技术选型,再给数据库和页面的完整实现,然后是 Servlet 查询链路的代码与参数说明,最后集中排查五个高频踩坑点,并在结尾把部署和验证的进阶做法一次讲透。

2. 技术选型先立住:JSP + Servlet + JDBC 为什么不选框架

2.1 这个选题的场景边界:查询为主、写入简单,正好卡在框架的“不需要区”

火车查询系统的核心操作是余票查询、车次信息展示、票价查询,后台顶多加一个车次维护。这类需求天然是“读多写少”的形态,最有价值的部分在于查询条件和结果集的映射关系。用 Spring Boot + MyBatis 当然能做,但对准备课程设计答辩或者刚学完 JavaWeb 的人来说,框架会掩盖掉一层关键认知:HTTP 请求怎样被容器接收、参数怎样从 request 里取出、连接怎样从连接池借出又归还。把这层链路看清楚,以后去理解过滤器、拦截器、AOP 才有着力点。

如果你要把这个系统作为毕设选题,还得考虑另一个现实因素:答辩时老师几乎一定会问“你的项目用了哪些技术,为什么这么选”。用 Servlet + JSP + JDBC 的答案是“为了更清晰地展示请求处理流程和 SQL 执行逻辑”,这个理由站得住;如果直接用框架,被追问“Spring 的 IoC 在这里解决了什么问题”反而容易答不上来。不是说框架不好,而是这个题目选轻量方案更诚实、更容易展示自己真正动手的部分。

提示:JSP 本质上是 Servlet 的模板化封装,容器会把 .jsp 文件编译成 Servlet 类再执行。你写的 JSP 页面最终都会变成 Java 类,所以调试时看到 Tomcat 的 work 目录里出现“org/apache/jsp/xxx_jsp.java”不是异常,是正常编译产物。

2.2 运行环境与版本选型:JDK、Tomcat、MySQL 的匹配关系

版本匹配是新手最先踩的坑。这个项目我一般用 JDK 8 + Tomcat 9 + MySQL 5.7 或 MySQL 8.0。JDK 8 至今仍是很多教材和课程设计的基础版本,Tomcat 9 对应 Servlet 4.0 规范,支持 JSP 2.3,足够用且对老代码兼容好;MySQL 用 5.7 最省心,如果机器上已经装了 MySQL 8,记得驱动要换成com.mysql.cj.jdbc.Driver,而且连接 URL 里必须带serverTimezone=Asia/Shanghai,否则会报时区错误。

IDEA 里新建 JSP 项目时,不要选 Maven 骨架里的 webapp 模板再手动补依赖,直接新建普通 Java 项目,右键添加 Web 支持更干净。项目结构保持最朴素的布局:src 下放 Java 源码,web 目录下放 JSP 页面,WEB-INF/lib 放驱动 jar 包。Maven 可以引入,但对这个体量的项目,手动把mysql-connector-java的 jar 包丢进 lib 目录反而更直观——你能清楚地看见项目依赖了什么,而不是在 pom.xml 里猜坐标。

2.3 数据访问层的取舍:JDBC 直连还是配一个简单的工具类

常见做法是写一个 BaseDao 或 DBUtil 类,把连接获取和关闭收敛到一个方法里。这样业务代码里不出现DriverManager.getConnection的重复调用,后续换连接池时只改一处。网上很多源码直接在 JSP 里写Class.forName("com.mysql.jdbc.Driver"),这种写法在“把 JDBC 写进 JSP”的演示里能跑,但真实项目不这么做——JSP 的职责是展示,数据库访问应该隔离出去。

数据层我会拆成两层:一个DBUtil负责拿连接,一个TrainDao负责拼 SQL 和执行查询。Servlet 只调用 DAO 的方法,拿到List<Train>后转发给 JSP。这样每个类的职责只有一条,答辩时讲代码结构也从容。别为了省事把 DAO 写成静态方法满天飞,静态方法意味着所有线程共享同一个 DAO 实例的状态,查询类项目问题不大,但会养坏习惯。

3. 数据库设计与 JSP 页面:先有表结构,再有查询页

3.1 火车查询系统的核心表:列车、停靠站、余票三张表的设计思路

火车查询系统的数据模型比学生管理系统多一层“站”的维度。一张车次表只有车次号、始发站、终点站、发车时间、到达时间,无法表达“途经站”,所以需要独立的停靠站表。余票也不是一个死字段,不同区间余票不同,用票额表更合理。下面是完整建表语句:

CREATE DATABASE train_db DEFAULT CHARACTER SET utf8mb4; USE train_db; CREATE TABLE train ( train_no VARCHAR(10) PRIMARY KEY COMMENT '车次号,如 G102', start_station VARCHAR(50) NOT NULL COMMENT '始发站', end_station VARCHAR(50) NOT NULL COMMENT '终点站', start_time TIME NOT NULL COMMENT '发车时间', end_time TIME NOT NULL COMMENT '到达时间', seat_price DECIMAL(8,2) NOT NULL COMMENT '二等座票价', sleeper_price DECIMAL(8,2) NOT NULL COMMENT '卧铺票价' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE station ( id INT PRIMARY KEY AUTO_INCREMENT, train_no VARCHAR(10) NOT NULL, station_name VARCHAR(50) NOT NULL COMMENT '停靠站名称', arrive_time TIME NULL COMMENT '到达本站时间', leave_time TIME NULL COMMENT '离开本站时间', stop_order INT NOT NULL COMMENT '停靠顺序,从1开始', FOREIGN KEY (train_no) REFERENCES train(train_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE ticket ( id INT PRIMARY KEY AUTO_INCREMENT, train_no VARCHAR(10) NOT NULL, start_station VARCHAR(50) NOT NULL COMMENT '上车区间', end_station VARCHAR(50) NOT NULL COMMENT '下车区间', remain_count INT NOT NULL DEFAULT 0 COMMENT '余票数量', FOREIGN KEY (train_no) REFERENCES train(train_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字符集用 utf8mb4 而不是 utf8,是因为 MySQL 的 utf8 实际只支持部分 Unicode 字符,遇到生僻站名或少见全角符号会报“Incorrect string value”错误。把start_stime、end_time设成 TIME 类型而不是 VARCHAR,是让数据库帮你保证数据格式合法性,也方便后续做“发车时间晚于 X 点”这类范围筛选。站表用stop_order而不是靠arrive_time排序,因为同一站可能有多趟车,时间上不一定严格递增,手工维护一个顺序字段更可控。

提示:外键在这个项目里建议保留。虽然很多互联网业务为了分库分表会去掉外键,但你做的是课设级别的系统,保留外键能在插入数据时自动校验车次号是否存在,避免出现“站点指向了一个不存在的车次”这种脏数据。

3.2 查询页面的 JSP 写法:表单、下拉框与结果展示的分离

查询页我分成两个 JSP:query.jsp是查询表单,result.jsp是结果列表。表单页只负责收集条件,不做任何业务判断;结果页只负责展示 Servlet 传过来的 List。这样职责清晰,也方便后续在 result.jsp 上加分页而不动表单。下面是一个带始发站、终点站、出发日期三个条件的表单页:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>火车余票查询</title> </head> <body> <h2>火车余票查询</h2> <form action="${pageContext.request.contextPath}/searchTrain" method="get"> <label>出发站:</label> <input type="text" name="startStation" placeholder="请输入出发站" required /> <label>到达站:</label> <input type="text" name="endStation" placeholder="请输入到达站" required /> <label>出发日期:</label> <input type="date" name="travelDate" /> <button type="submit">查询</button> </form> </body> </html>

form 的 action 指向 Servlet 的 URL 映射而不是 JSP 文件本身,这是关键设计:页面不直接访问数据库,而是交给 Servlet 处理后再转发回来。${pageContext.request.contextPath}是 EL 表达式取应用上下文路径,也就是部署后在 Tomcat 里的访问前缀。比如你这个应用名叫train_web,那么这台 Tomcat 上所有页面的链接前都要加/train_web,硬编码会让项目在换部署名时全部失效,用 EL 表达式可以规避。

travelDate用type="date"而不是文本框,是为了让浏览器弹出原生日期选择器,Servlet 端收到的是yyyy-MM-dd格式字符串,正好可以直接传给 JDBC 的setString。注意这里的日期只是演示查询条件,如果后面要做真实的票价浮动或节假日余票预测,日期就要落到单独的表里。

3.3 JSP 的页面指令与编码:一行乱码问题的根源

JSP 文件第一行<%@ page contentType="text/html;charset=UTF-8" language="java" %>不是摆设。contentType控制响应给浏览器的内容类型和编码,pageEncoding(如果没写就默认取 charset)控制 JSP 文件本身在编译期的读取编码。这两者不一致就会出现“页面显示正常但表单提交后乱码”或者反过来。IDEA 新建 JSP 默认可能是 ISO-8859-1,改 UTF-8 的常见做法是 Editor 里的 File Encodings 全部设为 UTF-8,同时在 JSP 头显式声明。

这里特别提醒一点:<%@ page ... %>指令必须写在 JSP 文件最顶部,前面不能有任何空行、空格或 BOM 头。Tomcat 编译 JSP 时对 BOM 的兼容性不稳定,如果文件被编辑器加了 UTF-8 BOM,运行起来可能报“org.apache.jasper.JasperException: PWC6033”这类难排查的错误。用 IDEA 的“Remove BOM”功能或者在保存时选 UTF-8 without BOM,可以规避。

4. Servlet + JDBC 查询链路:从表单参数到 List 的完整走通

4.1 DBUtil 与 TrainDao:连接管理、重复代码收敛、SQL 参数化

先写 DBUtil。这里采用 Properties 文件配置连接参数,而不是在 Java 代码里写死连接串,这样以后从底层 MySQL 的测试环境切换到生产库,不需要重新编译 Java 类。连接参数放在src/db.properties里:

package com.train.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static String url; private static String username; private static String password; static { try (InputStream is = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties")) { Properties props = new Properties(); props.load(is); Class.forName(props.getProperty("driver")); url = props.getProperty("url"); username = props.getProperty("username"); password = props.getProperty("password"); } catch (Exception e) { throw new ExceptionInInitializerError("数据库配置加载失败"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }

静态代码块在类第一次被加载时执行,作用是完成驱动注册和配置读取。getResourceAsStream从 classpath 读取 properties 文件,所以db.properties必须放在 src 源码目录下,编译后才会进入 classes。Class.forName在 JDBC 4.0 以后其实可以省略——新版驱动 jar 包里有META-INF/services/java.sql.Driver文件,DriverManager 会自动加载。但保留这句兼容性更好,尤其在 MySQL 8 驱动上,显式加载能更快暴露驱动缺失问题而不是等连接超时。

TrainDao 里的查询必须用PreparedStatement而不是Statement,核心原因是防 SQL 注入。用户输入的车站名直接拼进 SQL 字符串,比如输入' or '1'='1,查询条件就变成恒真,会把整张表拉回来。PreparedStatement 用setString绑定参数,JDBC 驱动的 escape 机制会处理单引号,从根上堵住注入。这是 Django、ORM 之外的 Java 原生开发里少见的“安全是语法层面的”时刻,答辩时讲这个点很加分。代码示意如下:

public List<Train> searchByStation(String start, String end) { String sql = "SELECT * FROM train WHERE start_station = ? AND end_station = ?"; List<Train> result = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, start); ps.setString(2, end); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Train t = new Train(); t.setTrainNo(rs.getString("train_no")); t.setStartStation(rs.getString("start_station")); t.setEndStation(rs.getString("end_station")); t.setStartTime(rs.getTime("start_time")); t.setEndTime(rs.getTime("end_time")); t.setSeatPrice(rs.getBigDecimal("seat_price")); t.setSleeperPrice(rs.getBigDecimal("sleeper_price")); result.add(t); } } } catch (SQLException e) { e.printStackTrace(); return new ArrayList<>(); } return result; }

Java 7 引入的 try-with-resources 会自动关闭Connection、PreparedStatement、ResultSet三个资源,不用再写 finally 里层层判空。关闭顺序是逆序的:先关 ResultSet,再关 Statement,最后关 Connection。如果你手动管理,写错顺序会导致“连接已归还但结果集还在被引用”的问题。这里e.printStackTrace()是学习阶段的正常做法,但生产上应该记录到日志文件,避免错误信息淹没在 Tomcat 控制台。

4.2 Servlet 的 doGet 写法:request 参数处理、转发与重定向的边界

Servlet 接收表单 GET 请求的 doGet 方法里,参数处理有三个维度:取值、去空白、默认值。上面表单里两个输入框都标记了required,但浏览器端的约束可以被绕过,所以 Servlet 端仍要做判空。从 request 拿到的值如果为 null(参数不存在)或空字符串(表单提交了空内容),都要统一当成“条件不完整”处理,返回错误提示而不是去数据库执行一次注定查不到结果的查询:

@WebServlet("/searchTrain") public class SearchTrainServlet extends HttpServlet { private TrainDao trainDao = new TrainDao(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String start = request.getParameter("startStation"); String end = request.getParameter("endStation"); if (start == null || end == null || start.trim().isEmpty() || end.trim().isEmpty()) { request.setAttribute("errorMsg", "出发站和到达站不能为空"); request.getRequestDispatcher("/query.jsp").forward(request, response); return; } start = start.trim(); end = end.trim(); List<Train> list = trainDao.searchByStation(start, end); request.setAttribute("trainList", list); request.getRequestDispatcher("/result.jsp").forward(request, response); } }

@WebServlet("/searchTrain")是 Servlet 3.0 起的注解式映射,省去了 web.xml 里繁琐的 servlet-mapping 配置。逻辑上这里做的是“转发”而非常见的sendRedirect——forward由服务器内部完成请求传递,浏览器地址栏不会变,request 里的属性可以继续用;redirect会让浏览器发起第二次请求,request 对象会重建。查询参数在两次请求间需要保留时得手动拼 URL,非常容易漏。所以查询类结果返回一律用 forward。

request.setCharacterEncoding("UTF-8")放在最前面是针对 POST 请求的正文编码;GET 请求的参数编码由 Tomcat 的URIEncoding决定,默认是 UTF-8,所以这里写这行对 GET 请求本身不直接生效。很多教材让所有人把这一行写在 doGet 开头,严格说是种防御式写法,保留它无妨,但要知道它的真实作用域。

4.3 result.jsp 的展示逻辑:JSTL 还是纯 Scriptlet,边界在哪

结果页展示 List,最佳实践是用 JSTL 的c:forEach遍历,而不是在 JSP 里写<% for (...)Scriptlet。JSTL 让页面更像模板,而且 EL 表达式会自动处理空集合,避免list.get(i)越界异常。下面是一个完整的 result.jsp 片段:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <body> <h2>查询结果</h2> <c:if test="${not empty trainList}"> <table border="1" cellspacing="0" cellpadding="6"> <tr> <th>车次</th><th>始发站</th><th>终点站</th><th>发车时间</th><th>到达时间</th> <th>二等座</th><th>卧铺</th> </tr> <c:forEach items="${trainList}" var="t"> <tr> <td>${t.trainNo}</td> <td>${t.startStation}</td> <td>${t.endStation}</td> <td>${t.startTime}</td> <td>${t.endTime}</td> <td>${t.seatPrice}</td> <td>${t.sleeperPrice}</td> </tr> </c:forEach> </table> </c:if> <c:if test="${empty trainList}"> <p>未查询到符合条件的车次,请更换条件后再试。</p> </c:if> </body> </html>

使用 JSTL 需要两个 jar 包:jstl.jar和standard.jar,或者用 Tomcat 10 时代的新坐标jakarta.servlet.jsp.jstl。如果页面报“The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved”,99% 是 lib 目录缺第二个 jar 包。另一种选择是把查询结果在 Servlet 端转成 JSON 输出再配合前端渲染,但那个路线就直接走向前后端分离了,对课设来说多引入 ajax 和 JSON 解析库反而增加了不稳定性。

result.jsp 里Time类型的字段 EL 直接输出是HH:mm:ss,比如 08:30:00。如果想显示成 08:30,简单做法是在 Train 实体里加一个startTimeStr,在 DAO 的getTime后格式化。常见格式化写法是new SimpleDateFormat("HH:mm").format(rs.getTime("start_time")),不要写在 JSP 页面里,实体类的方法更适合。

5. 避坑手册:JSP 火车查询系统最常见的五个故障及排查路径

5.1 现象:运行 Tomcat 后访问页面报 404,但项目明明部署了

原因往上查通常是两个:一是应用部署名和访问 URL 不匹配,IDEA 里 Artifact 的部署名是train_web,浏览器却访问了http://localhost:8080/query.jsp,而实际路径是http://localhost:8080/train_web/query.jsp。二是 Servlet 的@WebServlet路径写成了/searchTrain/,带了尾部斜杠,Tomcat 匹配不到。我处理这类问题先把 IDEA 的 Deployment 页截图核对 Application context,如果显示/train_web,访问前缀就敲http://localhost:8080/train_web/query.jsp;然后打开 Tomcat 的web.xml找不到映射时,再检查注解是否写全。

解决:统一 application context 与请求路径前缀。确认 IDEA 中 Artifact 的 “Deployment” 选项卡里 Application context 的值,以及浏览器 URL 是否完整带上项目名;去掉@WebServlet路径末尾的斜杠,点击 Tomcat 控制台的“Redeploy”而非“Restart”,有时 Tomcat 缓存了旧的 class,Redeploy 才重新编译。

5.2 现象:MySQL 8 下启动项目报ClassNotFoundException: com.mysql.jdbc.Driver

原因:MySQL 8 的驱动类名从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver,而且连接 URL 必须加时区参数。老的 5.x 驱动包如果继续使用,即使类名写对也可能因为协议版本不兼容报Public Key Retrieval is not allowed。

解决:驱动包换成mysql-connector-java8.x(版本号自己标注入座),db.properties 里 driver 写成com.mysql.cj.jdbc.Driver,url 末尾追加?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true。useSSL=false只是因为本地开发没有证书,连远程库时按需开。这个组合我在 Windows 安装 MySQL 8 后没有再遇到连接层面的怪问题。

5.3 现象:表单提交中文参数到 Servlet 变成乱码

原因要分请求方向。浏览器到 Tomcat 这一段:GET 请求的编码由 Tomcat 的URIEncoding控制,POST 由request.setCharacterEncoding控制。Tomcat 8 之后 URIEncoding 默认是 UTF-8,所以 GET 乱码少见;POST 乱码则多是因为 JSP 页面本身的 pageEncoding 是 ISO-8859-1,提交出去的字节就是错误的。这类问题有个经典玄学:本地 IDEA 跑不乱码,打成 war 包部署到 Linux 上的 Tomcat 就乱,因为 Linux 系统默认 locale 是 POSIX,Tomcat 读 URI 的默认编码行为不同。

解决:JSP 头统一pageEncoding="UTF-8",Servlet 里 doPost 第一行写request.setCharacterEncoding("UTF-8"),Tomcat 的server.xml里<Connector>标签加URIEncoding="UTF-8"。三层全部统一后乱码概率趋近于零。还有一个血泪经验:IDEA 里 Database 工具窗口连接的库字符集和项目连的库字符集不一致,可能导致你看到的数据是好的,但程序读出来乱码——用命令行客户端查一次最可靠。

5.4 现象:Tomcat 启动时报Address already in use: JVM_Bind

原因:启动过第二个 Tomcat 实例,或者 IDEA 之前异常退出,8080 端口还被僵尸进程占用。MySQL 的error 2002 (HY000): can't connect to local mysql server through socket '/tmp/mysql.sock'是另一个类似问题——客户端找 socket 文件失败,服务端没起来或者路径不对。两个问题的排查思路其实一样:先看端口/套接字有没有进程在监听。

解决:Windows 下执行netstat -ano | findstr 8080,拿到占用 PID,到任务管理器结束进程,或者改 Tomcat 的 port 为 8081。MySQL 的 socket 问题则检查mysqld进程是否存在,以及my.cnf里socket路径和客户端配置是否一致。注意 Linux 上如果你用 rpm 方式安装的 MySQL,socket 默认在/var/lib/mysql/mysql.sock,客户端默认找/tmp/mysql.sock,不一致就会遇到上面那条错误,业内通常用建软链来解决。

5.5 现象:查询结果页显示“未查询到车次”,但数据库里明明有数据

原因有两个层次。第一次遇到时先别怀疑 SQL——先用 Navicat 或命令行执行同样的查询语句,确认条件能查到。查不到再怀疑代码层,最常见的其实是用户输入了“上海”而表里存的是“上海虹桥”,模糊匹配没做。另一层原因是request.getParameter取到的值带不可见空白,trim()方法在 Servlet 里已经做了,但如果参数从 URL 直接拼接过来,浏览器地址栏里的%20也可能没有被解码。

解决:查询条件改成 LIKE 模糊匹配而不是等值匹配,SQL 写成WHERE start_station LIKE ?后参数设置为%+ start +%。代价是索引失效风险,但数据量小影响可忽略。更稳妥的体验方式是页面表单里用<datalist>列出所有车站,用户必须从备选列表里选,既减少输入不一致又降低查不到的概率。另外可以在 Servlet 里打日志输出最终执行的 SQL 参数,确认 Service 层拿到的值到底是什么,这条排查手段能解决一半的“数据存在但查不到”。

6. 从课设到可演示作品:war 包部署、连接池替换与答辩加分改造

项目在 IDEA 里跑通不算完,关键一步是打成 war 包丢到独立 Tomcat 下验证。IDEA 菜单 Build > Build Artifacts > 选择xxx:war exploded之外的那个xxx:war,点击 Build,产物在out/artifacts/下。把 war 包复制到 Tomcat 的 webapps 目录,启动后自动解压,访问http://localhost:8080/项目名/query.jsp。这一步的目的是暴露 IDEA 内嵌 Tomcat 隐藏的问题——最常见的坑是db.properties没有进 war 包,如果出现ExceptionInInitializerError或找不到配置文件的报错,检查 Artifact 的 Output Layout 里有没有把 resources 目录打进去。

再进一步,把 DBUtil 的直连换成连接池。这里推荐 DBCP 或 HikariCP,注意 HikariCP 需要slf4j-api,别漏依赖。改造后的 DBUtil 核心是HikariDataSource初始化一次后反复调用getConnection,这比 DriverManager 每次新建物理连接要快一个数量级,也是 MySQL 连接被耗尽问题的根治方案。连接池的核心参数就三个:maximumPoolSize默认 10,minimumIdle默认与 maximum 相等,connectionTimeout默认 30 秒。课设系统并发低,maximumPoolSize 设 5 就够,设太大会浪费数据库连接资源。

如果还有余力做答辩加分,我建议加一个“按发车时间排序”的 SQL 排序条件,或者在 DAO 里写一个searchByStationAndDate的重载——把 travelDate 参数加进查询条件,哪怕只是演示“当日有票”的判断逻辑。对 JSP Java 方向想深入的同学,这套东西能不能扩展成 Spring Boot 版不是重点,重点是能说清楚“JSP 页面为什么在 Spring Boot 里变成了模板引擎的天下”。面试被问 Java 八股文里的数据一致性、连接池、SQL 注入时,用这个项目的代码举例比背概念生动得多。

我把这个项目做了三遍以后才意识到:火车查询系统真正的价值不在“查询”本身,而在让你把 Servlet 生命周期、JDBC 资源管理、Tomcat 部署机制这三块黑匣子逐个打开过一次。以后不管用什么框架,遇到 404、乱码、连接超时,第一反应都是沿着这条链路去定位,而不是换个工具再试一次。希望帮到你。

提示:打成 war 包部署后,每次改 Java 代码都要重新 Build Artifact,改 JSP 则不需要重启 Tomcat——JSP 文件会被容器检测到修改并自动重新编译。这个特性调试页面样式时能省大量时间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询