ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java Swing民航订票系统源码解析:JDBC+SQL Server实现订票退票与延误管理

Java Swing民航订票系统源码解析:JDBC+SQL Server实现订票退票与延误管理 简介这份资源是面向高校软件工程课程设计场景的Java民航订票管理系统完整源码包适合正在做课设、需要参考完整业务闭环与数据库设计的计算机专业学生也可作为Swing桌面应用与JDBC持久层练手项目。系统覆盖航班信息查询、客户订票与退票、航班与航线管理、航班延误处理、已订票客户信息管理及会员信息管理等核心模块业务链条较为完整。压缩包共120个文件约2.29MB其中30个java源文件承载各功能模块与DAO实现75个class为编译产物另有2个sql脚本用于建库建表、5个xml与若干工程配置文件支撑Eclipse或IDEA导入并附docx说明文档。目前已有890人学习下载。读者可据此快速理清订票系统的实体关系、界面分层与数据库表结构对照源码理解登录、查询、下单、退票等流程的实现思路也可在原有基础上扩展支付、改签等功能作为课设答辩与二次开发的参考底稿。1. 从一份能跑起来的 Java Swing 订票系统说起很多软件工程课程设计的选题里飞机订票系统几乎是出现频率最高的一个但真正拿到手能直接跑通、数据库脚本齐全、功能覆盖到退票和延误管理的源码包并不多。这份 JAVA 民航订票管理系统源码配套 SQL Server 数据库脚本用 Swing 做界面、JDBC 做数据访问覆盖了航班信息查询、客户订票、退票、航班信息管理、航线管理、航班延误管理、已订票客户信息管理、会员信息管理这几条主线。从类名就能看出分层意图BookTicket、FlightQuery、Login负责交互入口FlightInformationDao、CustomersBTInformationDao负责数据落地AirLineInformation、VIPRegister把航线和会员单独抽出来。适合两类人一是课程设计需要一份结构完整、能讲清楚分层逻辑的参考实现二是刚接触 JDBC Swing 组合、想找一个业务闭环完整的小系统练手的人。下面按「资源是什么 → 怎么跑起来 → 坑在哪 → 怎么改」的顺序拆开讲。2. 环境搭建与数据库初始化把 SQL Server 和 JDBC 先接通2.1 开发工具与运行环境选型这份源码标注的开发工具是 Eclipse 和 IntelliJ IDEA数据库用 SQL Server界面库是 Java Swing数据访问走原生 JDBC。选型上没什么花哨的东西但恰恰是这种「老三样」组合在课程设计场景里最稳Swing 不需要额外依赖JDK 自带JDBC 驱动只要把 jar 包丢进 classpath 就能用SQL Server 在 Windows 上装起来比 MySQL 少一层配置心智负担。常见做法是 JDK 8 或 JDK 11再高的版本 Swing 本身没问题但部分老驱动 jar 在高版本 JDK 上会有反射警告能跑但日志不干净。需要提前准备的东西JDK建议 8 或 11、Eclipse 或 IDEA、SQL ServerExpress 版就够、对应的 JDBC 驱动 jarmssql-jdbc或老版本的sqljdbc4.jar。驱动版本要和 SQL Server 版本对上SQL Server 2019 之后建议用mssql-jdbc-9.x以上否则登录阶段容易报 TLS 相关错误。2.2 建库建表先跑脚本再连代码数据库脚本一般随源码包一起给导入顺序不能乱。先建库再建表最后插初始数据。如果脚本里没有显式CREATE DATABASE需要手动建一个库名再把表建进去。下面是一段典型的建库建表语句字段名按源码里 DAO 的映射来对齐-- 创建数据库 CREATE DATABASE FlightTicketDB; GO USE FlightTicketDB; GO -- 航班信息表 CREATE TABLE FlightInformation ( FlightNo VARCHAR(20) PRIMARY KEY, -- 航班号 AirLineName VARCHAR(50), -- 所属航线 StartCity VARCHAR(30), -- 出发城市 EndCity VARCHAR(30), -- 到达城市 StartTime DATETIME, -- 起飞时间 EndTime DATETIME, -- 到达时间 Price DECIMAL(10,2), -- 票价 SeatCount INT, -- 剩余座位 FlightState VARCHAR(20) -- 航班状态正常/延误/取消 ); GO -- 客户订票信息表 CREATE TABLE BookTicketOrder ( OrderId INT IDENTITY(1,1) PRIMARY KEY, CustomerName VARCHAR(30), IdCard VARCHAR(20), FlightNo VARCHAR(20), BookTime DATETIME, SeatNo VARCHAR(10), OrderState VARCHAR(20) -- 已订/已退 ); GO建表时最容易忽略的是字段类型和 DAO 里ResultSet.getXxx()的对应关系。比如Price用DECIMALDAO 里就得用getBigDecimal或getDouble用getInt会直接抛异常。FlightState这种状态字段建议用VARCHAR而不是BIT因为延误、取消、正常是三态用布尔存不下。2.3 配置 JDBC 连接URL、账号、驱动三件事连接配置通常写在某个工具类里比如DBUtil或直接在 DAO 的构造函数里。核心就是四要素驱动类名、URL、用户名、密码。SQL Server 的 URL 写法有坑实例名和端口二选一混着写会连不上。public class DBUtil { // SQL Server 驱动类名老版本是 com.microsoft.sqlserver.jdbc.SQLServerDriver private static final String DRIVER com.microsoft.sqlserver.jdbc.SQLServerDriver; // 若为默认实例用 1433 端口命名实例则用 instanceName private static final String URL jdbc:sqlserver://127.0.0.1:1433;databaseNameFlightTicketDB;encryptfalse; private static final String USER sa; private static final String PASSWORD your_password; public static Connection getConnection() throws Exception { Class.forName(DRIVER); // 加载驱动 return DriverManager.getConnection(URL, USER, PASSWORD); } }encryptfalse这一项在新版驱动里很关键SQL Server 默认开启加密协商本地开发环境没配证书时会直接握手失败报「PKIX path building failed」或者「The driver could not establish a secure connection」。加上这一项本地能通但生产环境不能这么写课程设计阶段够用。databaseName必须和实际建的库名一致大小写不敏感但拼错就是「Cannot open database」错误。2.4 导入项目并跑通第一个界面把源码导入 IDE 后先确认三件事JDBC 驱动 jar 是否加进了 Build Path 或 Maven 依赖DBUtil里的账号密码是否改成自己本地的数据库服务是否已启动。然后从Login.class对应的源文件启动登录界面能弹出来说明 Swing 部分没问题输入账号能进主界面说明 JDBC 通了。如果登录按钮点了没反应先看控制台有没有异常堆栈八成是驱动没加载或 URL 写错。3. 核心业务模块拆解订票、退票、延误管理怎么串起来3.1 航班查询与订票主流程FlightQuery负责查询BookTicket负责下单两者通过FlightInformationDao共享数据。查询一般支持按出发城市、到达城市、日期三个条件组合SQL 用动态拼接或者PreparedStatement占位。订票流程是查到航班 → 选座位 → 写订单 → 扣减座位数。扣座位这一步必须和写订单在同一个事务里否则会出现「订单写了但座位没扣」或者「座位扣了订单没写」的脏数据。// 订票核心逻辑事务包裹写订单 扣座位 public boolean bookTicket(BookTicketOrder order) { Connection conn null; PreparedStatement psOrder null; PreparedStatement psSeat null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 写入订单 String sqlOrder INSERT INTO BookTicketOrder (CustomerName, IdCard, FlightNo, BookTime, SeatNo, OrderState) VALUES (?, ?, ?, GETDATE(), ?, 已订); psOrder conn.prepareStatement(sqlOrder); psOrder.setString(1, order.getCustomerName()); psOrder.setString(2, order.getIdCard()); psOrder.setString(3, order.getFlightNo()); psOrder.setString(4, order.getSeatNo()); psOrder.executeUpdate(); // 2. 扣减剩余座位同时校验不能超卖 String sqlSeat UPDATE FlightInformation SET SeatCount SeatCount - 1 WHERE FlightNo ? AND SeatCount 0; psSeat conn.prepareStatement(sqlSeat); psSeat.setString(1, order.getFlightNo()); int rows psSeat.executeUpdate(); if (rows 0) { conn.rollback(); // 座位不足回滚订单 return false; } conn.commit(); // 两步都成功才提交 return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (Exception ignored) {} e.printStackTrace(); return false; } finally { // 关闭资源顺序Statement - Connection try { if (psOrder ! null) psOrder.close(); } catch (Exception ignored) {} try { if (psSeat ! null) psSeat.close(); } catch (Exception ignored) {} try { if (conn ! null) conn.close(); } catch (Exception ignored) {} } }setAutoCommit(false)是事务的开关忘了写这一步两条 SQL 各自提交超卖就防不住。WHERE SeatCount 0这个条件配合executeUpdate返回的影响行数是防超卖的经典写法返回 0 说明没扣到直接回滚。GETDATE()用数据库时间而不是 Java 的new Date()避免客户端时区不一致导致订单时间错乱。3.2 退票与座位回补退票逻辑和订票相反把订单状态改成「已退」同时把座位加回去。这里有个容易翻车的地方——退票要判断订单当前状态已经退过的订单不能再退否则座位会被重复加回越退越多。public boolean refundTicket(int orderId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 查询订单状态和航班号 String sqlQuery SELECT FlightNo, OrderState FROM BookTicketOrder WHERE OrderId ?; PreparedStatement psQuery conn.prepareStatement(sqlQuery); psQuery.setInt(1, orderId); ResultSet rs psQuery.executeQuery(); if (!rs.next() || 已退.equals(rs.getString(OrderState))) { conn.rollback(); return false; // 订单不存在或已退直接拒绝 } String flightNo rs.getString(FlightNo); // 2. 更新订单状态 PreparedStatement psUpdate conn.prepareStatement( UPDATE BookTicketOrder SET OrderState 已退 WHERE OrderId ?); psUpdate.setInt(1, orderId); psUpdate.executeUpdate(); // 3. 座位回补 PreparedStatement psSeat conn.prepareStatement( UPDATE FlightInformation SET SeatCount SeatCount 1 WHERE FlightNo ?); psSeat.setString(1, flightNo); psSeat.executeUpdate(); conn.commit(); return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (Exception ignored) {} e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.close(); } catch (Exception ignored) {} } }状态判断放在事务里做而不是先查再开事务否则并发下两个退票请求可能都读到「已订」都执行回补。OrderState用中文字符串存虽然直观但要注意数据库排序规则和 Java 源文件编码一致否则比较时可能匹配不上。3.3 航班延误管理与状态联动FlightInformation里有个FlightState字段延误管理就是改这个字段。但延误不是孤立事件它会影响已订票客户的行程。常见做法是管理员把某航班标记为「延误」后系统能按航班号查出所有OrderState 已订的客户供人工通知或批量短信。这部分源码里通常是一个查询 一个更新逻辑不复杂但字段联动要理清。操作涉及表关键字段注意事项标记延误FlightInformationFlightState只改状态不动座位查询受影响客户BookTicketOrderFlightNo, OrderState只查「已订」的取消航班FlightInformationFlightState需批量退票并回补座位恢复航班FlightInformationFlightState状态改回「正常」取消航班比延误多一步要把该航班所有已订订单批量退掉座位统一回补。这一步如果逐条调退票接口事务边界会碎掉正确做法是在一个事务里批量更新订单状态 一次性回补座位数。3.4 会员管理与 VIP 注册VIPRegister和CustomersBTInformationDao负责会员这块。会员信息一般比普通客户多几个字段会员等级、累计里程、折扣率。订票时根据会员等级算折扣价这个折扣计算要放在写订单之前把折后价存进订单表而不是每次查询时现算——现算的话会员等级一变历史订单价格就跟着变了对账会出问题。// 根据会员等级计算折后价 public double calcDiscountPrice(double originalPrice, String vipLevel) { double rate; switch (vipLevel) { case 钻石: rate 0.80; break; case 黄金: rate 0.90; break; case 白银: rate 0.95; break; default: rate 1.00; break; // 普通客户无折扣 } // 保留两位小数避免浮点误差 return Math.round(originalPrice * rate * 100.0) / 100.0; }Math.round(... * 100.0) / 100.0是保留两位小数的常用写法直接Math.round会丢精度。折扣率用switch硬编码在代码里课程设计够用但真要扩展得挪到配置表或枚举里否则每加一个等级都要改代码重新编译。4. 避坑与排查这份源码跑不起来时先看这几条4.1 驱动加载失败或连接被拒现象启动后点登录控制台报ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver或者Connection refused。原因通常是 JDBC 驱动 jar 没加进 classpath或者 SQL Server 的 TCP/IP 协议没启用。解决确认 jar 在 Build Path 里打开 SQL Server 配置管理器把 TCP/IP 协议启用端口设为 1433重启服务。命名实例的话 URL 要改成;instanceName你的实例名不能再用端口。4.2 中文乱码现象界面显示正常但数据库里存进去的中文变成问号或者查出来的中文显示成方块。原因有两个层面数据库字段排序规则不是中文兼容的比如SQL_Latin1_General或者 JDBC URL 没指定编码。解决建库时选Chinese_PRC_CI_AS排序规则URL 里加;sendStringParametersAsUnicodetrueJava 源文件编码统一设成 UTF-8。三处都对齐基本就不会乱。4.3 座位超卖或退票后座位不恢复现象两个人同时订最后一张票都显示成功或者退票后座位数没变。原因订票没开事务或者退票只改了订单状态没回补座位。解决订票和退票都必须用setAutoCommit(false)包住多条 SQL扣座位用WHERE SeatCount 0加影响行数判断退票先查状态再回补。这是血泪经验课程设计答辩时老师最爱问这个点。4.4 Swing 界面卡死现象点查询按钮后界面无响应过几秒才刷新。原因数据库查询直接在事件分发线程EDT里执行网络或磁盘慢的时候整个界面就冻住。解决把耗时查询放到SwingWorker的doInBackground里结果在done里更新界面。课程设计不强制要求但加上去是个加分项也避免演示时尴尬。4.5 日期格式转换异常现象插入订单时报Conversion failed when converting date and/or time from character string。原因Java 的String日期直接拼进 SQL格式和数据库期望的不一致。解决统一用PreparedStatement.setTimestamp()传java.sql.Timestamp或者用SimpleDateFormat转成yyyy-MM-dd HH:mm:ss再传。不要用字符串拼接日期格式稍有出入就翻车。5. 二次开发与验证把课程设计改成能讲出亮点的作品5.1 用 DAO 分层做一次代码走查拿到源码后别急着改功能先做一次分层走查Login里有没有直接写 SQLBookTicket有没有绕过 DAO 直接连库如果界面类里混着 JDBC 代码说明分层没做干净二次开发前先把这些 SQL 抽到 DAO 里。走查方法很简单全局搜DriverManager和PreparedStatement看它们出现在哪些文件里。理想情况下只应该出现在 DAO 和工具类里出现在 Swing 面板类里就是坏味道。5.2 加一个「订单查询」功能验证分层想验证自己是否理解了这套结构最直接的办法是加一个按身份证号查订单的功能。步骤在BookTicketOrder对应的 DAO 里加一个queryOrdersByIdCard(String idCard)方法在 Swing 里加一个查询面板输入身份证号表格展示结果表格用DefaultTableModel列名和 DAO 返回的字段对齐。这个功能加完你就把「界面 → DAO → 数据库」整条链路走了一遍比看十遍代码都管用。// DAO 层新增按身份证号查订单 public ListBookTicketOrder queryOrdersByIdCard(String idCard) throws Exception { ListBookTicketOrder list new ArrayList(); String sql SELECT OrderId, CustomerName, FlightNo, BookTime, SeatNo, OrderState FROM BookTicketOrder WHERE IdCard ? ORDER BY BookTime DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, idCard); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { BookTicketOrder o new BookTicketOrder(); o.setOrderId(rs.getInt(OrderId)); o.setCustomerName(rs.getString(CustomerName)); o.setFlightNo(rs.getString(FlightNo)); o.setBookTime(rs.getTimestamp(BookTime)); o.setSeatNo(rs.getString(SeatNo)); o.setOrderState(rs.getString(OrderState)); list.add(o); } } } return list; }try-with-resources写法比手动finally关资源干净JDK 7 以上都支持。ORDER BY BookTime DESC让最近的订单排前面符合使用直觉。返回List而不是ResultSet是为了让 DAO 之外的地方不依赖 JDBC 类型这是分层的基本要求。5.3 验证清单答辩前自己先过一遍验证项操作预期结果登录输入正确/错误账号正确进主界面错误提示查询按城市日期查返回匹配航班订票订一张有余票的订单生成座位减一超卖订最后一张后并发再订第二单失败退票退已订订单状态变已退座位加一重复退票对已退订单再退拒绝座位不变延误标记航班延误状态更新可查受影响客户会员折扣不同等级订票价格按折扣计算这张表建议答辩前逐项跑一遍尤其是超卖和重复退票这两项是区分「能跑」和「跑对」的分水岭。从那以后我每次拿到这类课程设计源码都先把事务边界和状态判断这两处翻出来看一遍再决定要不要在它基础上改——这两处干净后面加功能就顺这两处糊弄加什么都是在沙子上盖楼。希望帮到你。本文还有配套的精品资源点击获取
返回列表