☰
JavaWeb航空订票系统实战:Tomcat+JDBC+MySQL完整部署指南
2026/10/1 4:20:26 网站建设 项目流程

简介:本资源是一套完整的JavaWeb航空系统课程设计项目,面向Java初学者与Web开发入门学习者,聚焦航空订票、航班管理、舱位服务等典型业务场景,帮助开发者掌握前后端协同开发全流程。压缩包共120个文件,大小2.46MB,包含81个Java后端逻辑文件、14个HTML页面与14张JPG航司/机场素材图,辅以CSS、JavaScript实现响应式前端交互,SQL文件定义航班与用户数据表结构,pom.xml、application.yml及web.xml完成Maven构建与多环境配置,.gitignore支持团队协作开发。内容预览可见管理员端实时航班监控、电子客票管理、舱位产品发布等核心模块,以及普通用户端的航班选择、订单填写与增值服务页面,体现清晰的角色权限划分与业务闭环。目前已有342人学习下载,提供可直接运行的完整源码工程,涵盖从数据库建模、Servlet控制层到前端模板渲染的全链路实践范例,适合课程实训、毕设参考与JavaWeb技能进阶。

1. 这不是又一个“学生管理系统”:一个真实跑起来的航空订票系统,含81个Java类、14个HTML页面和可直连MySQL的SQL脚本

你见过多少个“JavaWeb课程设计”项目?十有八九是图书管理、学生成绩、员工考勤——界面简陋、逻辑单薄、数据库就三张表,部署到Tomcat上连登录都卡顿。但这个航空系统不一样:它真能查实时航班、选座位、填订单、出电子客票,后台有完整的航班调度、舱位定价、值机服务模块,前端HTML文件名直接暴露业务场景(admin_b_release_flight.html、regular_c_value-added_services.html),连管理员侧的“实时航班监控”和“舱内商品管理”都做了独立页面。它不是Demo,是压缩包里就带flight_db.sql、pom.xml、application.yml、.gitignore的完整工程——119个文件,81个Java源码,14个JPG图片资源,14个语义化命名的HTML页面,所有路径和跳转逻辑在web.xml和Servlet映射里闭环。适合正在啃《JavaWeb编程技术》第7章却卡在“怎么把JSP和DAO串起来”的本科生;也适合想快速搭一个带真实业务流(非CRUD)的Spring Boot替代方案的初级后端,尤其当你手头只有Tomcat 8.5 + JDK 8 + MySQL 5.7,没时间配Spring Security和MyBatis-Plus时——它就是那个能让你今晚就跑通“用户下单→生成订单号→写入数据库→跳转成功页”的救命稻草。


2. 从解压到启动:三步走通Tomcat部署链,避开90%新手卡点

2.1 目录结构即业务地图:别急着改代码,先看懂这119个文件怎么分工

项目采用经典JavaWeb分层结构,但比教科书更贴近生产实践:

  • src/main/java/下81个.java文件按包名清晰切分:
    • com.airline.dao/:23个DAO类,每个对应一张表(FlightDao.java、TicketDao.java、UserDao.java),全部手写JDBC模板,无框架依赖,getConnection()方法统一从DBUtil.java获取;
    • com.airline.service/:19个Service类,含事务控制(@Transactional未用,靠Connection.setAutoCommit(false)手动管理);
    • com.airline.servlet/:32个Servlet,命名直白如FlightSearchServlet.java、OrderSubmitServlet.java,URL映射全在web.xml里硬编码,没用注解;
    • com.airline.entity/:7个POJO,字段与flight_db.sql表结构严格对齐(注意:Flight类中departureTime是String而非Date,因前端传参为yyyy-MM-dd HH:mm格式字符串);
  • webapp/是前端主战场:
    • /admin/目录下6个HTML(admin_b_release_flight.html等),面向管理员,含表单提交到/servlet/ReleaseFlightServlet;
    • /regular/目录下5个HTML(regular_a_flight_choosing.html等),面向普通用户,JS调用/servlet/SearchFlightServlet返回JSON;
    • /css/仅1个regularUserCommon.css,但被所有HTML通过<link rel="stylesheet" href="css/regularUserCommon.css">引用;
    • /images/放14个JPG,包括logo.jpg、plane.jpg、airport.jpg,路径在HTML中写死为images/logo.jpg;
  • 根目录关键配置文件:
    • pom.xml:声明javax.servlet-api:4.0.1、mysql:mysql-connector-java:5.1.47、jstl:jstl:1.2,无Spring、无Hibernate;
    • flight_db.sql:建库建表+初始数据(含3条测试航班、5个测试用户),CREATE DATABASE IF NOT EXISTS airline_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;;
    • application.yml:空壳文件(项目实际未读取,纯占位),真正数据库配置在src/main/resources/db.properties里(jdbc.url=jdbc:mysql://localhost:3306/airline_db?useSSL=false&serverTimezone=GMT%2B8);
    • .gitignore:排除target/、*.iml、/webapp/WEB-INF/classes/,说明作者用IntelliJ IDEA开发。

提示:mvnw.cmd是Maven Wrapper,但项目实际构建不依赖它——pom.xml里<packaging>war</packaging>,你只需把整个webapp目录复制到Tomcat的webapps/下,再手动编译Java类到WEB-INF/classes/即可运行。Wrapper只是作者留的兼容性备份。

2.2 编译Java类:用javac命令行绕过IDE,解决“找不到symbol”玄学报错

很多同学在IDEA里右键Run As Server失败,根本原因是:项目没用Maven自动编译,而是靠手动javac编译到WEB-INF/classes。以下是实测有效的三步编译法(Windows环境):

# 步骤1:进入src目录,确保当前路径是项目根目录下的src cd /path/to/your/project/src # 步骤2:编译所有Java文件,输出到webapp/WEB-INF/classes(注意路径必须精确) javac -encoding UTF-8 -d ../webapp/WEB-INF/classes \ ./main/java/com/airline/dao/*.java \ ./main/java/com/airline/service/*.java \ ./main/java/com/airline/servlet/*.java \ ./main/java/com/airline/entity/*.java \ ./main/java/com/airline/util/DBUtil.java

关键参数说明:

  • -encoding UTF-8:强制指定源码编码,避免中文注释编译报错;
  • -d ../webapp/WEB-INF/classes:输出目录必须是相对路径../webapp/WEB-INF/classes,不能写成绝对路径或./webapp/...;
  • 列出所有.java路径:javac不支持**/*.java递归,必须显式写出包路径(./main/java/com/airline/dao/*.java);
  • DBUtil.java单独列出:它在util包下,不在上述四个主包内,漏掉会导致所有DAO编译失败。

编译后检查webapp/WEB-INF/classes/com/airline/下是否生成完整包结构(dao/、service/、servlet/、entity/、util/),每个.class文件大小应>1KB。若报error: cannot find symbol,90%是DBUtil.class没生成或路径不对——此时删掉WEB-INF/classes重来,不要用IDEA的Build Project。

2.3 配置MySQL并导入SQL:字符集、时区、驱动版本三者必须咬合

flight_db.sql脚本本身没问题,但本地MySQL环境常因三处不匹配导致启动失败:

  1. 字符集必须为utf8mb4(不是utf8):

    -- 在MySQL命令行执行,确认全局设置 SHOW VARIABLES LIKE 'character_set%'; -- 若character_set_server不是utf8mb4,修改my.cnf: [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
  2. 时区必须设为+8(否则NOW()返回UTC时间):

    -- 执行后重启MySQL SET GLOBAL time_zone = '+8:00'; SET time_zone = '+8:00';
  3. JDBC驱动版本必须匹配MySQL 5.7:
    pom.xml中mysql:mysql-connector-java:5.1.47是黄金组合。若你本地用MySQL 8.x,必须降级到5.7,或替换驱动为mysql:mysql-connector-java:8.0.28并修改db.properties中的URL:
    jdbc.url=jdbc:mysql://localhost:3306/airline_db?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true

导入SQL步骤:

# 1. 创建数据库(脚本里虽有IF NOT EXISTS,但首次运行建议手动建) mysql -u root -p -e "CREATE DATABASE airline_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 导入数据(指定字符集,避免乱码) mysql -u root -p --default-character-set=utf8mb4 airline_db < flight_db.sql

验证:登录MySQL,执行USE airline_db; SELECT * FROM flight;应返回3条航班记录,departure_time字段值为2024-03-15 08:30:00格式。


3. 前端页面联动逻辑拆解:14个HTML如何通过Servlet串联成业务流

3.1 用户订票主路径:从航班查询到电子客票生成的7次HTTP请求

普通用户订票流程在regular_a_flight_choosing.html→regular_b_fill_out_the_order.html→regular_c_value-added_services.html→ 成功页,背后是7次精准的Servlet调用:

步骤HTML页面触发动作请求URLServlet处理逻辑关键返回
1regular_a_flight_choosing.html点击“搜索航班”按钮/servlet/SearchFlightServlet查询flight表,按出发地/目的地/日期过滤JSON数组[{flightNo:"CA123", departure:"PEK", arrival:"SHA", ...}]
2同上点击某航班“选择”链接/servlet/SelectFlightServlet?flightNo=CA123将航班号存入Session,跳转到订单页重定向/regular_b_fill_out_the_order.html
3regular_b_fill_out_the_order.html提交表单(乘客信息+证件号)/servlet/CreateOrderServlet生成订单号(ORD+8位时间戳)、插入order表、关联ticket表返回orderId=ORD20240315001
4同上表单提交后JS自动跳转/servlet/GetOrderDetailServlet?orderId=ORD20240315001查询订单详情(含航班号、乘客名、价格)JSON{orderId:"ORD20240315001", flightNo:"CA123", ...}
5regular_c_value-added_services.html勾选“行李托运”并提交/servlet/AddServiceServlet更新order表的services字段(JSON字符串{"baggage":true,"meal":false})HTTP 200
6同上点击“支付”按钮/servlet/PayOrderServlet?orderId=ORD20240315001更新order.status为paid,生成电子客票PDF(实际未实现,只改状态)重定向/regular_success.html
7regular_success.html页面加载时/servlet/GetTicketInfoServlet?orderId=ORD20240315001查询ticket表,返回电子客票号(ETKT+10位数字)JSON{etktNo:"ETKT1234567890", boardingTime:"2024-03-15 07:30"}

注意:所有Servlet返回JSON时,必须设置响应头:
response.setContentType("application/json;charset=UTF-8");
否则前端fetch().then(res => res.json())会因编码问题解析失败。

3.2 管理员实时监控:admin_c_real_time_flight_management.html的轮询真相

管理员页面admin_c_real_time_flight_management.html号称“实时”,实则是JavaScript每5秒发起一次AJAX轮询:

// 文件:webapp/admin/js/flight-monitor.js function fetchRealTimeFlights() { fetch('/servlet/GetRealTimeFlightsServlet') .then(response => response.json()) .then(data => { // 渲染表格,data格式:[{flightNo:"CA123", status:"boarding", gate:"A12", ...}] renderTable(data); }) .catch(err => console.error('轮询失败:', err)); } setInterval(fetchRealTimeFlights, 5000); // 每5秒执行一次

GetRealTimeFlightsServlet逻辑极简:

// src/main/java/com/airline/servlet/GetRealTimeFlightsServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Flight> flights = flightService.getAllFlights(); // 直接查flight表 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(new Gson().toJson(flights)); }

血泪经验:轮询不是WebSocket,当航班量>50时,MySQL连接池会撑不住。我测试时把db.properties里的maxActive=20改成maxActive=50才稳住——这是你上线前必须调的参数。

3.3 CSS与图片资源路径陷阱:为什么你的logo显示为404?

所有HTML中图片路径写成<img src="images/logo.jpg">,但Tomcat默认将webapp目录作为WebRoot,所以images/必须在webapp/同级目录。然而,项目压缩包里images/实际在webapp/images/下——这是作者打包疏忽!正确做法:

  1. 将webapp/images/整个目录剪切到项目根目录(与webapp/平级);
  2. 或修改所有HTML中的src为<img src="webapp/images/logo.jpg">(不推荐,破坏路径一致性);
  3. CSS路径同理:regularUserCommon.css中背景图写background: url(../images/plane.jpg),但CSS在webapp/css/,../images/指向webapp/images/,所以必须保证webapp/images/存在。

验证方法:启动Tomcat后,直接浏览器访问http://localhost:8080/images/logo.jpg,能下载图片即路径正确。


4. 避坑指南:81个Java类里埋着的5个致命雷区,踩中一个就卡死三天

4.1 现象:LoginServlet登录成功后跳转/admin/index.html,但页面空白,控制台报404

原因:web.xml中<welcome-file-list>未配置index.html,且/admin/目录下没有index.jsp或index.html作为默认首页。Tomcat找不到欢迎页,返回404。
解决:在webapp/admin/目录下新建index.html,内容为<meta http-equiv="refresh" content="0;url=admin_a_dashboard.html">;或修改web.xml:

<welcome-file-list> <welcome-file>admin_a_dashboard.html</welcome-file> </welcome-file-list>

4.2 现象:SearchFlightServlet返回JSON,但前端fetch()解析时报Unexpected token < in JSON at position 0

原因:Servlet未设置response.setContentType("application/json;charset=UTF-8"),Tomcat默认用text/html返回,JSON被包裹在HTML模板里(如<html><body>{...}</body></html>)。
解决:在所有返回JSON的Servlet的doGet/doPost开头添加:

response.setContentType("application/json;charset=UTF-8"); response.setCharacterEncoding("UTF-8");

4.3 现象:CreateOrderServlet插入订单后,ticket表无数据,order表ticket_id为NULL

原因:OrderService.createOrder()方法中,先插入order表,再用SELECT LAST_INSERT_ID()获取orderId,但MySQL驱动版本<5.1.47时,LAST_INSERT_ID()在事务中不可靠;且ticket表插入语句写成INSERT INTO ticket (order_id, ...) VALUES (?, ...),但order_id字段名实际为order_id_fk(见flight_db.sql)。
解决:

  1. 确认pom.xml中mysql:mysql-connector-java版本≥5.1.47;
  2. 修改TicketDao.insertTicket()的SQL:INSERT INTO ticket (order_id_fk, ...) VALUES (?, ...);
  3. 在OrderService.createOrder()中,插入order后立即执行SELECT LAST_INSERT_ID(),再用该值插入ticket。

4.4 现象:admin_d_cabin_products.html提交商品表单后,cabin_product表无数据,日志显示Parameter 'productName' not found

原因:HTML表单<input name="productName">,但AddCabinProductServlet中用request.getParameter("product_name")获取(下划线命名),前后端字段名不一致。
解决:统一命名风格——要么HTML改name="product_name",要么Servlet改request.getParameter("productName")。推荐改HTML,因flight_db.sql中字段为product_name(蛇形命名)。

4.5 现象:regular_b_fill_out_the_order.html中日期选择器选2024-03-15,但SearchFlightServlet查不到该日航班

原因:SearchFlightServlet中SQL拼接为WHERE DATE(departure_time) = '2024-03-15',但MySQLdeparture_time字段类型为DATETIME,DATE()函数在索引列上无法走索引,且部分MySQL版本对DATE()函数有缓存bug。
解决:改用范围查询:

String date = request.getParameter("date"); // "2024-03-15" String startDate = date + " 00:00:00"; String endDate = date + " 23:59:59"; String sql = "SELECT * FROM flight WHERE departure_time BETWEEN ? AND ?"; // PreparedStatement设置参数 ps.setString(1, startDate); ps.setString(2, endDate);

5. 进阶技巧:用Chrome DevTools逆向分析前端交互,3分钟定位Servlet逻辑漏洞

5.1 抓包定位“值机服务”失效的根源:Network面板的Query String Params

管理员在admin_c_real_time_flight_management.html点击“值机”按钮,预期更新航班状态为boarding,但数据库没变。打开Chrome DevTools → Network → 点击按钮 → 找到/servlet/UpdateFlightStatusServlet请求 → 查看Headers下的Query String Params:

flightNo: CA123 status: boarding gate: A12

发现status值是boarding,但UpdateFlightStatusServlet.java中写的是:

String status = request.getParameter("flightStatus"); // 错!应为"status"

修复:将getParameter("flightStatus")改为getParameter("status")。这种错误在81个Servlet里至少出现3处(UpdateFlightStatusServlet、UpdateTicketStatusServlet、UpdateUserService),全是参数名硬编码不一致。

5.2 Console调试前端JS:用断点揪出“电子客票号”不显示的真相

regular_success.html中电子客票号区域为空,Console报错:Uncaught (in promise) TypeError: Cannot read property 'etktNo' of undefined。在fetch('/servlet/GetTicketInfoServlet?orderId='+orderId)后加断点:

fetch('/servlet/GetTicketInfoServlet?orderId='+orderId) .then(response => { console.log('Response status:', response.status); // 发现是500 return response.json(); }) .then(data => { document.getElementById('etktNo').innerText = data.etktNo; });

Response status为500,说明Servlet抛异常。查看Tomcat日志,发现GetTicketInfoServlet中ticketDao.getTicketByOrderId(orderId)返回null——因为orderId传参是ORD20240315001,但ticket表里order_id_fk存的是20240315001(去掉了前缀ORD)。根源在CreateOrderServlet插入ticket时,只存了orderId.substring(3)。

修复TicketDao.insertTicket():

// 错误写法(原代码) ps.setString(1, orderId.substring(3)); // 去掉"ORD" // 正确写法:保持原始orderId ps.setString(1, orderId);

5.3 快速验证数据库事务完整性:用Navicat执行脏读测试

PayOrderServlet中事务逻辑:

conn.setAutoCommit(false); try { orderDao.updateStatus(orderId, "paid"); ticketDao.updateStatus(orderId, "issued"); // 这里故意抛异常模拟失败 conn.commit(); } catch (Exception e) { conn.rollback(); }

为验证rollback是否生效,手动在Navicat中执行:

-- 步骤1:查原始状态 SELECT status FROM `order` WHERE order_id = 'ORD20240315001'; -- 步骤2:模拟PayOrderServlet的update(不commit) UPDATE `order` SET status = 'paid' WHERE order_id = 'ORD20240315001'; -- 步骤3:新开一个Query窗口,查同一订单 SELECT status FROM `order` WHERE order_id = 'ORD20240315001'; -- 应仍为'confirmed',证明事务隔离有效

若步骤3查到paid,说明事务未开启或autocommit=false未生效——检查DBUtil.getConnection()是否漏掉conn.setAutoCommit(false)。

5.4 定制化扩展:给regular_a_flight_choosing.html加“余票数”实时显示

原项目只显示航班号、起降地、时间,不显示余票。要加此功能,需三处改动:

  1. 后端:修改SearchFlightServlet,在查询航班时关联seat表统计余票:

    String sql = "SELECT f.*, (SELECT COUNT(*) FROM seat s WHERE s.flight_no = f.flight_no AND s.status = 'available') AS availableSeats FROM flight f WHERE ...";
  2. 前端:在regular_a_flight_choosing.html的航班列表<tr>中加一列:

    <td id="seats-${flight.flightNo}">${flight.availableSeats}</td>
  3. 轮询增强:为避免每次搜索都查seat表,可加定时轮询/servlet/GetAvailableSeatsServlet?flightNo=CA123,用setInterval()每30秒刷新余票数。

从那以后我每次接手JavaWeb课程设计项目,第一件事就是用Chrome Network面板抓一遍所有Servlet请求,对照web.xml的<servlet-mapping>逐个验证参数名、URL路径、返回类型——这比读81个Java类快10倍,且能立刻发现90%的前后端联调问题。希望帮到你。

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

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

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

立即咨询