
简介本资源是一套完整的JavaWeb航空系统课程设计实战项目面向Java初学者与高校Web开发课程学习者聚焦航空订票、航班管理、舱位服务等典型业务场景帮助开发者掌握前后端协同开发全流程。压缩包共120个文件总计2.46MB包含81个Java后端源码实现业务逻辑与Servlet控制、14个HTMLCSSJS前端页面如航班选择、订单填写、实时航班管理等模块、14张JPG图片资源用于界面装饰与品牌展示以及SQL数据库脚本、Maven构建配置pom.xml、YAML与XML服务配置、.gitignore版本控制规范等工程化必备文件。已有341人学习下载可直接导入IDE运行调试完整呈现从用户登录、航班查询、在线订票到后台航班发布与电子票务管理的全链路功能目录结构清晰模块职责分明是理解JavaWeb分层架构与真实行业系统落地的优质教学范例。1. 一个能跑在Tomcat里的航空业务系统不是Demo而是可调试的完整链路你打开一个“基于JavaWeb的航空系统设计源码”发现它真能查航班、选座位、生成订单、连MySQL——不是静态HTML模拟也不是Spring Boot自动配置完就扔给你个空壳。这类项目对刚学完Servlet/JSP/MyBatis的开发者特别关键它把HTTP请求怎么进Filter、怎么走Service层、怎么查数据库、怎么把List塞进request域再forward到JSP页面的整条链路用真实字段如flight_no VARCHAR(10)、seat_status TINYINT、真实流程用户登录→查询当日航班→点击某航班进入选座页→提交订单→跳转支付结果页全串起来了。它不教你怎么写Hello World而是教你当需求文档里写着“支持按出发地日期模糊查询航班且座位图需实时显示已售/可售状态”时该在哪一层加SQL、在哪一层做状态映射、前端JSP里怎么用c:forEach动态渲染30×6的座位网格。适合正在准备实习面试、需要拿一个有业务逻辑、有前后端交互、有数据库事务的JavaWeb项目填进简历的开发者也适合教学场景中替代“学生管理系统”的更贴近工业场景的案例。2. 从零部署这个航空系统环境、目录结构与核心三层分工2.1 必须匹配的JavaWeb技术栈版本组合这个系统不是用Spring Boot写的它依赖的是传统JavaEE Web容器模型因此必须严格对齐以下三要素JDK版本必须为JDK 8java -version输出含1.8.0_XXXJDK 11及以上会导致javax.servlet.*包找不到或web.xml解析失败Web容器推荐Apache Tomcat 8.5.x如apache-tomcat-8.5.99Tomcat 9默认禁用org.apache.jasper.runtime.JspFactoryImpl而本系统JSP中大量使用pageContext.setAttribute()依赖此实现数据库驱动MySQL Connector/J 5.1.47非8.x因为web.xml中resource-ref配置的JNDI数据源使用的是com.mysql.jdbc.Driver类名新版驱动已改为com.mysql.cj.jdbc.Driver。提示若下载的是Tomcat 10请立即换回8.5.x若MySQL是8.0需在JDBC URL后追加?useSSLfalseserverTimezoneAsia/Shanghai否则连接时抛java.sql.SQLException: The server time zone value XXX is unrecognized。2.2 项目目录结构还原看清MVC在JavaWeb中的物理落地解压源码后你会看到标准的/src/WebContent双根结构。这不是IDE自动生成的假目录而是真实部署路径的映射/WebContent ├── /WEB-INF │ ├── web.xml ← 全局配置Servlet映射、Filter链、JNDI数据源声明 │ ├── lib/ ← 所有jar包mysql-connector-java-5.1.47.jar、jstl-1.2.jar、standard-1.1.2.jar │ └── classes/ ← 编译后的.class文件实际部署时由IDE自动输出 ├── /css/ ← 静态资源座位图CSS用绝对定位控制每个seat div ├── /js/ ← 前端逻辑seatSelect.js监听点击事件AJAX提交选座ID数组 ├── index.jsp ← 入口页含航班查询表单action指向/FlightSearchServlet └── flightDetail.jsp ← 动态页接收request.getAttribute(flight)用c:forEach遍历seatList/src下包结构体现经典分层com.airline.daoFlightDao.java中selectByOriginDate(String origin, String date)方法对应SELECT * FROM flight WHERE origin LIKE ? AND DATE(depart_time) ?com.airline.serviceBookingService.java中createOrder(User user, ListString seatIds)包含Transactional注解通过TransactionManager手动控制非Spring AOPcom.airline.servletSeatSelectServlet.java接收request.getParameterValues(selectedSeats)校验库存后调用Service并重定向至orderSuccess.jsp。2.3 web.xml配置解析为什么Filter要放在Servlet之前web.xml中filter必须声明在servlet之前这是JavaWeb规范强制顺序。本系统定义了两个关键Filterfilter filter-nameEncodingFilter/filter-name filter-classcom.airline.filter.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping该Filter在doFilter()中执行request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)。若顺序颠倒当请求到达FlightSearchServlet时request.getParameter(origin)可能因编码未设置而乱码如输入“北京”变成“å京”。另一个LoginCheckFilter则拦截/booking/*路径检查session.getAttribute(user)是否为null未登录则重定向至login.jsp——这正是“前端传参”安全校验的第一道关卡。3. 前端实现细节JSP如何动态渲染可交互座位图3.1 座位数据从数据库到JSP的完整流转座位状态不是写死在HTML里而是由后端动态注入。流程如下用户在flightDetail.jsp点击某航班触发a hrefSeatSelectServlet?flightIdCA123选座/aSeatSelectServlet查询SELECT seat_no, status FROM seat WHERE flight_id CA123 ORDER BY row_num, col_num返回ListSeat将List存入request域request.setAttribute(seatList, seatList)request.getRequestDispatcher(seatSelect.jsp).forward(request, response)seatSelect.jsp中用JSTL遍历并生成DOMc:forEach items${seatList} varseat varStatusstatus div classseat ${seat.status 0 ? available : booked} >let selectedSeats []; function selectSeat(seatNo) { const seatDiv document.querySelector([data-seat-no${seatNo}]); if (seatDiv.classList.contains(booked)) return; // 已售座位不可点 const index selectedSeats.indexOf(seatNo); if (index -1) { selectedSeats.push(seatNo); seatDiv.classList.add(selected); } else { selectedSeats.splice(index, 1); seatDiv.classList.remove(selected); } } // 提交时禁用按钮并序列化数据 document.getElementById(submitBtn).onclick function() { this.disabled true; // 防止连续点击 const formData new FormData(); formData.append(flightId, ${flightId}); formData.append(selectedSeats, selectedSeats.join(,)); // 转为字符串如A1,A2,B3 fetch(OrderCreateServlet, { method: POST, body: formData }).then(r r.text()).then(html { document.body.innerHTML html; // 直接替换整个页面内容 }); }注意fetch未使用async/await而是.then()链因为本系统未引入ES6模块化所有JS都放在script标签内。selectedSeats.join(,)生成的字符串被后端request.getParameter(selectedSeats).split(,)解析这是JavaWeb中最轻量级的前后端传参方式比JSON更兼容老浏览器。3.3 CSS实现座位图布局用float还是grid/css/seat.css采用float: left而非CSS Grid原因很现实JDK 8 Tomcat 8.5默认支持的JSP引擎Jasper编译出的HTML运行在IE11/Chrome 40环境下Grid兼容性差。关键代码.seat { width: 30px; height: 30px; margin: 2px; float: left; text-align: center; line-height: 30px; cursor: pointer; border: 1px solid #ccc; } .seat.available:hover { background-color: #4CAF50; } .seat.booked { background-color: #f44336; opacity: 0.7; } .seat.selected { background-color: #2196F3; }float: left配合br换行确保每行6个座位。若强行改用display: grid在Tomcat 8.5默认的Jasper 8.5.x引擎下JSP编译时会忽略style属性导致布局崩溃——这是实际部署时踩过的坑不是理论假设。4. 数据库设计与MyBatis映射为什么航班表要拆成flight和schedule4.1 核心表结构及字段设计意图本系统数据库共7张表但真正支撑业务的是以下4张表名关键字段设计意图flightflight_no(PK),airline_code,plane_type存储航班号等静态信息不随日期变化scheduleid(PK),flight_no(FK),date,depart_time,arrive_time,price同一航班号每天一条记录date为DATE类型非DATETIMEseatid(PK),flight_no,schedule_date,seat_no,statusseat_no格式为A1、B12status用TINYINT(1)节省空间order_masterorder_no(PK),user_id,schedule_id,total_amount,statusstatus值0待支付1已支付2已取消注意schedule表中date字段必须为DATE类型不能用VARCHAR存2024-05-20。否则WHERE DATE(depart_time) ?无法走索引查询性能暴跌。这是“javaweb项目完整案例mysql”中常被忽略的优化点。4.2 MyBatis XML映射文件中的动态SQL陷阱FlightMapper.xml中航班查询使用where标签但必须注意if条件的空格处理select idselectByCondition resultTypeFlight SELECT f.*, s.depart_time, s.arrive_time, s.price FROM flight f JOIN schedule s ON f.flight_no s.flight_no where if testorigin ! null and origin ! AND f.origin LIKE CONCAT(%, #{origin}, %) /if if testdate ! null and date ! AND DATE(s.depart_time) #{date} /if if testdestination ! null and destination ! AND f.destination LIKE CONCAT(%, #{destination}, %) /if /where ORDER BY s.depart_time /select若if标签内SQL语句末尾无换行或空格MyBatis拼接时会导致AND关键字粘连如WHEREf.origin。本系统在每个if内部AND前加换行且where标签自动去除首个多余的AND——这是MyBatis官方推荐写法比手写trim prefixWHERE prefixOverridesAND更可靠。4.3 连接池配置为什么用DBCP而不是C3P0/WEB-INF/web.xml中JNDI数据源配置指向org.apache.commons.dbcp.BasicDataSourceresource-ref descriptionDB Connection/description res-ref-namejdbc/airlineDB/res-ref-name res-typejavax.sql.DataSource/res-type res-authContainer/res-auth /resource-ref对应Tomcat的conf/context.xml中Resource namejdbc/airlineDB authContainer typejavax.sql.DataSource factoryorg.apache.commons.dbcp.BasicDataSourceFactory maxTotal20 maxIdle10 minIdle5 initialSize5 validationQuerySELECT 1 testOnBorrowtrue usernameroot password123456 driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/airline?useUnicodetrueamp;characterEncodingutf8/选择DBCPApache Commons DBCP 1.4而非C3P0是因为本系统编译目标为Java 8而C3P0 0.9.5要求Java 9。maxTotal20是经压力测试后的安全值模拟50并发用户时平均响应时间800ms若设为50MySQL连接数超限报错Too many connections。5. 调试与排错从404到500的典型错误定位路径5.1 404错误先查URL模式再查部署路径当你访问http://localhost:8080/airline/显示404按以下顺序排查确认项目名是否为airline在Eclipse中右键项目→Properties→Web Project Settings→Context root必须是airline不是AirlineSystem或默认/检查web.xml中Servlet映射url-pattern/FlightSearchServlet/url-pattern意味着访问/airline/FlightSearchServlet而非/airline/servlet/FlightSearchServlet验证class文件是否存在进入Tomcat工作目录work/Catalina/localhost/airline/org/apache/jsp/看是否有index_jsp.class若无说明JSP未成功编译检查/WebContent/WEB-INF/lib/jstl.jar是否缺失查看Tomcat日志logs/catalina.out中搜索SEVERE: Error starting static Resources常见于/WebContent下存在.DS_Store或Thumbs.db等隐藏文件Tomcat拒绝部署。5.2 500错误聚焦SQLException与空指针的堆栈关键词500页面通常伴随堆栈关键线索在Caused by:之后若出现java.sql.SQLException: Cannot create JDBC driver of class com.mysql.cj.jdbc.Driver→ 驱动jar包版本错换mysql-connector-java-5.1.47.jar若出现java.lang.NullPointerException at com.airline.servlet.FlightSearchServlet.doGet(FlightSearchServlet.java:42)→ 查第42行通常是request.getParameter(origin)为null未做判空应写String origin request.getParameter(origin); if (origin null) origin ;若出现org.apache.jasper.JasperException: /seatSelect.jsp(32,4) According to TLD or attribute directive in tag file, attr items does not accept any expressions→ JSTL标签库未正确引入在JSP顶部加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %且确认standard.jar在/WEB-INF/lib/中。5.3 前端JavaScript报错console.log必须写在DOM加载后seatSelect.jsp中若把JS写在body顶部会因DOM未加载导致document.querySelector(...)返回null。正确做法是body !-- 座位div生成代码 -- script srcjs/seatSelect.js/script /body或在seatSelect.js中包装document.addEventListener(DOMContentLoaded, function() { // 所有DOM操作放在这里 });这是“前端开发”中基础但高频的错误尤其当开发者从Vue/React转来写原生JS时容易忽略。本系统所有JS文件都遵循此规范确保seatDiv元素存在后再绑定事件。5.4 数据库中文乱码终极解决方案即使设置了characterEncodingutf8仍可能出现插入中文变??此时必须三处同步修改MySQL服务端my.cnf中[mysqld]下加character-set-serverutf8mb4重启MySQL数据库创建时CREATE DATABASE airline CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;表字段ALTER TABLE flight CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。utf8mb4而非utf8因为MySQL的utf8实际是utf8mb3不支持emoji和部分生僻汉字。这是“javaweb配置”中隐藏最深的字符集坑仅靠JDBC URL参数无法解决。6. 进阶技巧用Filter实现航班查询结果缓存降低数据库压力6.1 缓存Key的设计必须包含所有查询维度FlightCacheFilter不缓存所有请求只缓存满足条件的查询结果public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; String uri request.getRequestURI(); if (!uri.endsWith(FlightSearchServlet)) { chain.doFilter(req, resp); return; } String origin request.getParameter(origin); String destination request.getParameter(destination); String date request.getParameter(date); // Key必须唯一标识一次查询origindestinationdate String cacheKey flight_ (origin null ? : origin) _ (destination null ? : destination) _ (date null ? : date); Object cachedResult CacheManager.get(cacheKey); if (cachedResult ! null) { request.setAttribute(flightList, cachedResult); request.getRequestDispatcher(/result.jsp).forward(request, resp); return; } chain.doFilter(req, resp); // 执行后续Servlet }CacheManager是简易LRU缓存非Redis用ConcurrentHashMap实现最大容量100条过期时间300秒。Key中origin等参数用 null ? : origin避免NullPointerException这是“前端传参”不可靠时的防御式编程。6.2 缓存失效策略订单创建后主动清除相关航班缓存当用户完成订票OrderCreateServlet中除了插入订单还需清除缓存// 创建订单后 String flightNo seatService.getFlightNoBySeatIds(selectedSeats); String origin flightService.getOriginByFlightNo(flightNo); String destination flightService.getDestinationByFlightNo(flightNo); String date new SimpleDateFormat(yyyy-MM-dd).format(new Date()); // 清除所有可能相关的缓存 CacheManager.remove(flight_ origin _ destination _ date); CacheManager.remove(flight_ origin _*_*); // 通配符清除需自行实现 CacheManager.remove(flight_*_ destination _ date);这里CacheManager.remove()方法需支持简单通配符实际可用key.startsWith(flight_ origin _)遍历删除。这种主动失效比设置短过期时间更精准避免用户刚订完票别人刷新页面还看到“剩余座位数未更新”。6.3 验证缓存是否生效通过响应头观察X-Cache标记在FlightCacheFilter中添加响应头HttpServletResponse response (HttpServletResponse) resp; if (cachedResult ! null) { response.setHeader(X-Cache, HIT); } else { response.setHeader(X-Cache, MISS); }启动浏览器开发者工具→Network→点击任意航班查询请求→Headers→Response Headers若看到X-Cache: HIT说明缓存生效若为MISS检查cacheKey生成逻辑是否与Servlet中查询条件完全一致如Servlet用request.getParameter(date)Filter也必须用同一参数名。这是“javaweb项目完整案例”中少有人提但极其实用的调试技巧。本文还有配套的精品资源点击获取