
简介基于Java Swing与JDBC技术实现的航空订票管理系统源码面向需要完成课程设计、毕业设计或入门桌面应用开发的Java学习者。系统以MySQL 8.0为数据库覆盖登录注册、航班查询、订票退票、账户管理等核心业务模块界面采用Swing构建后端通过JDBC完成数据交互适合用于理解桌面应用界面与业务逻辑的分离方式。资源包共33个文件包含17个Java源码、数据库脚本、mysql-connector驱动jar包以及登录主界面等截图压缩包大小2.87MB结构清晰便于直接导入开发环境运行调试。目前已有357人学习下载适合作为Java项目实战参考。读者可从源码中提取界面与业务分离的写法、JDBC工具类封装以及数据重置与浏览器打开等辅助功能的实现思路对照截图和SQL脚本可快速完成本地环境搭建与功能演示。1. 这套 Java 航空订票管理系统源码解决的是课程设计与毕设里最头疼的那一关在 Java 课程设计和毕业设计里“XX管理系统”是出现频率最高的选题但真正能把 Swing 界面、JDBC 数据访问、MySQL 8.0 数据库这三样东西串成一个完整闭环的源码其实不多。大多数情况是界面画出来了数据库连不上或者 SQL 能跑但按钮一按就报 NullPointerException。这套基于 Java 的航空订票管理系统源码恰好卡在“麻雀虽小五脏俱全”的档位上——Swing 负责五个窗口的交互JDBC 负责连接 MySQL 8.0配套完整的 flight_data.sql 数据库脚本和 mysql-connector-java-8.0.27.jar 驱动包从登录注册一路做到查航班、订票、退票、改签、数据重置。适合正在做课程设计的人用来跑通流程、读懂分层也适合想快速搭一个可演示的模拟项目的人直接用。2. Swing 界面与控制层登录注册模块的前后端解耦套路很多新手写 Swing 程序习惯把所有逻辑全部堆在actionPerformed方法里一个按钮监听里塞几十行 JDBC 代码窗口一多就彻底乱套。这套源码在结构上比较清楚界面放在view包业务处理放在control包数据访问放在database包辅助工具放在other包。界面层只负责创建组件和触发事件真正的校验、查询、跳转全部交给控制层处理这样后面扩展改签、退票、换账户功能时不需要大改 UI。2.1 从 LoginView 到 LoginHandle界面层如何把动作交给控制层打开LoginView.java核心逻辑不是画登录框而是按钮监听里那一行跳转。源码的处理方式是让LoginView自己持有窗口引用点击登录后调用LoginHandle由它去校验账号密码再把结果返回到界面层做窗口切换。下面是这套源码里典型的写法// LoginView.java —— 登录按钮监听入口 loginButton.addActionListener(e - { // 从输入框取值 String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); // 控制层处理登录逻辑界面层不直接写 JDBC LoginHandle handle new LoginHandle(username, password); boolean ok handle.checkLogin(); if (ok) { // 关闭登录窗口打开主界面 Manager dispose(); new Manager(username).setVisible(true); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } });逻辑说明这里PasswordField用getPassword()拿字符数组而不是字符串算是 Swing 里的一个良好习惯。LoginHandle接收用户名和密码后去做数据库查询返回布尔值给界面层。界面层拿到结果后决定是弹错误框还是跳主界面职责边界比较清楚。参数说明dispose()只释放当前窗口不会结束 JVM 进程所以主界面能正常打开。如果你直接在监听里调System.exit(0)整个程序就退出了这是新手最常见的翻车点。setVisible(true)必须在主界面初始化完成后调用否则可能出现还没加载完就被用户看到半成品窗口的情况。2.2 主界面 Manager 与 TableShowPanel航班表格的数据填充方式登录成功后进入Manager.java它是整个系统的主窗口壳子放菜单、按钮和中间的面板。航班列表展示用的是TableShowPanel.java这个面板做的事情是把二维字符串数组转换成JTable能识别的表格模型。源码里有个关键点TableShowPanel不直接查数据库而是由控制层把数据准备好后调用填充方法这样面板可以复用。// TableShowPanel.java —— 填充航班表格数据的核心方法 public void fillTable(String[][] rowData) { String[] columns {航班号, 出发地, 目的地, 起飞时间, 到达时间, 余票, 票价}; DefaultTableModel model new DefaultTableModel(rowData, columns); table.setModel(model); // 让表格内容根据数据长度自适应刷新 table.updateUI(); }逻辑说明DefaultTableModel是 Swing 表格的常用数据模型传入二维数组和列名后它会负责渲染。updateUI()作用是强制让表格重新绘制否则你查完新数据后界面可能还是旧内容看起来就像“点了没反应”。参数说明列名数组必须与 SQL 查询结果的字段顺序一一对应。如果你的数据库表加了字段比如加一列“机型”记得同步改这里的列名数组和查询语句否则会出现数据串列的问题。rowData可以传空数组表格会只显示表头这在“还没有查询结果”的场景下比较实用。2.3 辅助工具类StringArryToString、SystemTime、Reflush 各管什么源码里other包下面有几个容易被忽略的工具类但实际跑起来缺一不可。StringArryToString.java作用是把字符串数组拼成带逗号的字符串常见用途是拼 SQL 里的IN (...)条件SystemTime.java负责生成当前时间字符串订票记录里需要时间戳Reflush.java提供统一的表格刷新入口调完数据库更新后调用它重新加载数据。// Reflush.java —— 统一的刷新入口避免每处重复写刷新逻辑 public static void refreshTable(TableShowPanel panel) { // 重新从数据库加载航班数据 Object[][] data MySqld.queryFlights(); panel.fillTable(data); panel.updateUI(); }逻辑说明如果你在每个按钮监听里都手写一遍查库和填充的代码后续改字段会改到崩溃。把这套逻辑抽成静态方法后订票完、退票完、重置完只要一行调用就能恢复表格状态。这也是为什么源码文件不多但功能点能串起来的原因——复用做得比较到位。3. JDBC 到 MySQL 8.0连接配置、建表脚本与查询封装的三个关键点这一章是整份源码的“地基”也是新手最容易卡住的地方。database/MySqld.java承担所有数据库操作配套的flight_data.sql是建表和初始数据的脚本libs下面的mysql-connector-java-8.0.27.jar是连接 MySQL 8.0 必须的驱动包。三者缺一个程序就跑不起来。下面把连接参数、预处理语句和表结构设计分别展开说。3.1 驱动注册与连接串时区、编码、SSL 三个参数不能省MySQL 8.0 和老的 5.x 有一个明显区别驱动类名从com.mysql.jdbc.Driver换成了com.mysql.cj.jdbc.Driver。如果你用的还是旧类名Class.forName 会直接抛ClassNotFoundException。源码里 MySqld 的写法是标准的新版配置连接串里包含时区、编码、SSL 三个关键项。// MySqld.java —— 获取数据库连接的核心方法 private static Connection getConnection() throws SQLException { String url jdbc:mysql://localhost:3306/flight_data ?useSSLfalse serverTimezoneAsia/Shanghai characterEncodingutf8; String user root; String password 123456; // MySQL 8.0 的驱动类名注意不是 com.mysql.jdbc.Driver try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(url, user, password); }逻辑说明url里第一个路径是数据库名也就是flight_data库。useSSLfalse是因为本地开发环境一般没有配置 SSL 证书省掉这个参数在某些 MySQL 版本下会报警告serverTimezoneAsia/Shanghai解决的是时区漂移问题不加它可能在插入时间字段时报错“The server time zone value Öйú±ê׼ʱ¼ä”。characterEncodingutf8是中文显示不乱码的保证不加的话数据库里传入的中文可能变成问号。参数说明user和password默认是 root 和 123456这是开发环境的常见配置。实际用的时候要根据自己的 MySQL 修改。如果你的 MySQL 密码不是 123456程序会在连接阶段就抛Access denied for user这属于正常现象改掉源码里的常量即可。3.2 预处理语句与 ResultSet 转数组查询结果的序列化套路源码里的查询操作基本都是三步走拿连接、用 PreparedStatement 执行 SQL、把 ResultSet 遍历成二维数组返回给界面层。之所以用 PreparedStatement 而不是 Statement是因为前者能通过占位符传参避免字符串拼接导致的 SQL 注入风险同时同一个 SQL 多次执行时性能也更好。// MySqld.java —— 根据关键字查询航班 public static Object[][] queryFlights(String keyword) { String sql SELECT flight_no, departure, destination, depart_time, arrive_time, remain_seats, price FROM flights WHERE flight_no LIKE ? OR departure LIKE ?; try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { // 参数填充模糊查询用 %keyword% ps.setString(1, % keyword %); ps.setString(2, % keyword %); try (ResultSet rs ps.executeQuery()) { ListObject[] rows new ArrayList(); while (rs.next()) { rows.add(new Object[]{ rs.getString(flight_no), rs.getString(departure), rs.getString(destination), rs.getString(depart_time), rs.getString(arrive_time), rs.getInt(remain_seats), rs.getDouble(price) }); } return rows.toArray(new Object[0][]); } } catch (SQLException e) { e.printStackTrace(); return new Object[0][]; } }逻辑说明这个方法返回Object[][]是为了直接对接上一章说的TableShowPanel.fillTable。try-with-resources写法能确保连接、语句、结果集在方法结束时自动关闭避免数据库连接泄漏。查询航班时用 LIKE 做模糊匹配用户在输入框输“北京”能同时匹配出发地或目的地。参数说明%是 SQL 里的通配符%keyword%表示包含关键字即可。rs.getInt和rs.getDouble对应数据库里remain_seats整数、price浮点数的类型。如果你的表字段名改了这里必须同步改否则会抛Column not found异常。3.3 flight_data.sql用户表、航班表、订单表的最小可用设计打开flight_data.sql可以看到这套源码背后是三张表的关联设计用户表存登录账号航班表存票务基础数据订单表存预订记录。订单表通过外键关联用户和航班形成一套比较标准的业务关系。下面是源码脚本里最核心的建表语句结构-- flight_data.sql 关键表结构精简展示 CREATE TABLE users ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE flights ( flight_id INT AUTO_INCREMENT PRIMARY KEY, flight_no VARCHAR(10) NOT NULL, departure VARCHAR(30) NOT NULL, destination VARCHAR(30) NOT NULL, depart_time DATETIME NOT NULL, arrive_time DATETIME NOT NULL, remain_seats INT DEFAULT 0, price DECIMAL(10,2) DEFAULT 0 ); CREATE TABLE orders ( order_id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, flight_id INT NOT NULL, order_no VARCHAR(20), status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(user_id), FOREIGN KEY (flight_id) REFERENCES flights(flight_id) );逻辑说明users表的username设置了 UNIQUE 约束注册时如果用户名重复数据库会直接报错这也解释了为什么注册模块里要先查一次“用户名是否已存在”再决定是否执行插入。orders表的status用TINYINT存状态码0 表示已订1 表示已退2 表示已改签这样退票和改签通过UPDATE就能完成不用物理删除订单。参数说明DECIMAL(10,2)是价格字段的推荐类型比 FLOAT 精确不会出现 199.99999 这种浮点误差。DATETIME适合这里展示航班时刻的需求不需要时区转换如果你需要跨时区记录才考虑TIMESTAMP。外键关联保证了订单不会指向不存在的用户和航班但删除测试数据时要注意先删订单再删航班否则会卡在外键约束上。4. 订票、退票、改签全流程从按钮事件到数据落库的完整链路看懂界面层和数据层各自的工作后关键是明白一个完整业务操作在源码里是怎么流转的。以“订票”为例它在界面上表现为用户点了一下表格里的某行航班、再点“预订”按钮但背后经历了四层调用TableShowPanel拿到选中行的航班 ID传给ItemHandle处理业务再调MySqld更新航班余票并插入订单记录最后调Reflush.refreshTable刷新界面。这一章把三个核心业务按流程拆开讲。4.1 订票流程选航班、写入订单、扣减余票的前后顺序订票的核心逻辑在ItemHandle.java里。它的输入是用户 ID、航班 ID 和乘机人信息输出是是否订票成功。源码里比较关键的一个设计是先校验余票大于 0再插入订单最后更新余票为原值减 1。这三步顺序不能换否则可能出现余票显示为 0 但订单已生成的情况。// ItemHandle.java —— 订票业务处理关键逻辑 public boolean bookTicket(int userId, int flightId) { Connection conn null; try { conn MySqld.getConnection(); conn.setAutoCommit(false); // 开启事务 // 第一步查询当前余票 PreparedStatement check conn.prepareStatement( SELECT remain_seats FROM flights WHERE flight_id ? FOR UPDATE); check.setInt(1, flightId); ResultSet rs check.executeQuery(); if (rs.next() rs.getInt(remain_seats) 0) { // 第二步生成订单 PreparedStatement insertOrder conn.prepareStatement( INSERT INTO orders (user_id, flight_id, status) VALUES (?, ?, 0)); insertOrder.setInt(1, userId); insertOrder.setInt(2, flightId); insertOrder.executeUpdate(); // 第三步余票减一 PreparedStatement updateSeat conn.prepareStatement( UPDATE flights SET remain_seats remain_seats - 1 WHERE flight_id ?); updateSeat.setInt(1, flightId); updateSeat.executeUpdate(); conn.commit(); // 全部成功才提交 return true; } return false; } catch (SQLException e) { // 出错则回滚保证数据一致 if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } }逻辑说明setAutoCommit(false)是事务控制的关键它让“插订单”和“扣余票”变成同一批操作要么都成功要么都失败。如果不用事务第二步成功而第三步失败就会出现“票订了但余票没少”用户多付一次钱还能再订一张的脏数据。SELECT ... FOR UPDATE是行级锁避免两个用户同时订最后一班航班时出现超卖。参数说明FOR UPDATE强制锁定这一行直到事务提交或回滚。这是模拟并发场景下比较可靠的做法。如果你的 MySQL 存储引擎是 MyISAM事务和行锁都不生效需要把表改成 InnoDB——MySQL 8.0 默认就是 InnoDB这点基本不用担心。4.2 退票与改签状态流转和座位回补的实现方式退票逻辑在ChangeTicket.java对应的事件里处理核心动作是把orders表里指定订单的status改为 1同时把对应航班的remain_seats加回 1。改签则是把订单的flight_id改成新航班同时处理新旧航班各自的余票增减。// ChangeTicket 退票核心逻辑 public boolean refund(int orderId) { String sqlOrder SELECT flight_id FROM orders WHERE order_id ? AND status 0; String updateStatus UPDATE orders SET status 1 WHERE order_id ?; String updateSeats UPDATE flights SET remain_seats remain_seats 1 WHERE flight_id (SELECT flight_id FROM orders WHERE order_id ?); // 依次执行查订单 - 改状态 - 还座位 // 同样建议放在一个事务里执行保持一致性 }逻辑说明退票操作要查到订单对应的航班号才能知道是给哪一班航班加回余票。源码里改座位用的是子查询把flight_id从订单里反查出来再更新。这里最容易踩的坑是用户退了一张已经退过的票导致余票被重复加回。所以在执行更新前必须检查status 0。参数说明改签逻辑类似区别是不仅要把老订单状态改成 2还要把新航班的余票减 1、老航班的余票加 1。如果你的源码里改签页面叫ChangeTicket或ChangeAcoountOther注意看清楚它操作的是账号还是订单两者不是一回事。换账号功能走的是ChangeAccountHandle它处理的是当前登录用户的切换不改动任何订单数据。4.3 数据重置与窗口刷新ReSetDataHandle 和 Reflush 的配合课程设计答辩有个常见需求演示完一堆订票退票操作后要把数据恢复成初始状态方便下一组同学演示。如果靠手工重导 SQL 文件就太笨了。源码里ReSetDataHandle.java做的事就是“一键还原”清空订单记录、把所有航班余票恢复成数据库初值。这个操作本质上是执行一组 SQL 语句加上刷新界面。// ReSetDataHandle.java —— 数据重置 public static void resetAll() { String deleteOrders DELETE FROM orders; String resetSeats UPDATE flights f SET remain_seats (SELECT initial_seats FROM flight_init WHERE flight_id f.flight_id); MySqld.executeUpdate(deleteOrders); MySqld.executeUpdate(resetSeats); Reflush.refreshTable(currentPanel); }逻辑说明先删订单再重置余票。注意顺序不能反因为订单表里有外键关联航班先重置余票没有意义。Reflush.refreshTable由界面层传入当前表格面板重置完立刻重新查询数据库并刷新表格保证用户在界面上看到的数据和数据库一致。参数说明flight_init这张表在源码里可能是虚拟存在的也可能是通过备份表实现的。如果你发现重置后余票没有变化先检查有没有这张初始表或者initial_seats字段名是不是init_seats这是源码里名称最容易对不上的地方。5. 避坑记录驱动、时区、中文乱码与界面卡死的五个排查点拿到这套源码最打击人的不是看不懂代码而是环境配好之后一运行全是红字。这里列五个我实际跑这类 Swing JDBC 项目时最容易撞上的问题每条按现象、原因、解决三步说清楚。5.1 高频故障与对应处置现象原因解决ClassNotFoundException: com.mysql.jdbc.Driver驱动类名写成了旧版com.mysql.jdbc.DriverMySQL 8.0 里已改名改成com.mysql.cj.jdbc.DriverAccess denied for user rootlocalhostMySQL 密码与源码里MySqld.java默认的 123456 不一致改源码里的 password 常量或执行ALTER USER rootlocalhost IDENTIFIED BY 123456;The server time zone value Öйú±ê׼ʱ¼ä连接串缺serverTimezone参数在 URL 末尾加serverTimezoneAsia/Shanghai数据库里的中文全部变成???连接串缺characterEncodingutf8或表编码不是 utf8URL 加characterEncodingutf8建表加DEFAULT CHARSETutf8mb4点击按钮后界面卡死转圈圈JDBC 查询耗时较长直接跑在 Swing 事件线程上阻塞了 UI常用做法是用 SwingWorker 在后台执行查询查完再回 UI 线程刷新5.2 从报错堆栈定位问题一张实用的排查顺序遇到程序跑不起来先别急着改代码。我一般按以下顺序排查第一步看控制台第一行异常是什么类型是ClassNotFoundException、SQLException还是NullPointerException这决定了问题在加载阶段还是运行阶段。第二步检查libs目录下的驱动 jar 是否被正确引入到项目的 classpath很多 IDE 里引入 jar 后还需要在模块设置里确认它处于“导出”状态。第三步用数据库客户端单独执行flight_data.sql确认脚本本身能跑通数据能查出来。第四步回到MySqld.java看连接串、账号密码、时区编码参数是否齐全。最后说一下关于时区的一个冷门坑如果你本机 MySQL 是 8.0.29 以上版本或者操作系统是某些精简版 Linux 发行版系统默认时区可能不是 Asia/Shanghai。这时候即使代码里写了serverTimezoneAsia/Shanghai也可能报 CST 相关的警告。我的做法是在 MySQL 的配置文件my.cnf里加一行default-time-zone 08:00从数据库源头把时区固定下来代码里就不需要依赖操作环境了。6. 进阶改造从“能跑通”到“能答辩、能扩展”的三个小改法如果你打算把这份源码直接当成课程设计去交只跑通原始功能可能还不够。评委大概率会问几个扩展性问题密码安全怎么保证余票不足时有没有提示如果航班数据上百条表格怎么查这里给三个投入小、收益大的改造方向。第一个改造点是密码加密。源码里注册和登录大概率是明文比对这在演示场景没问题但回答“安全性”提问时容易露怯。常见做法是在RegisterHandle里用MessageDigest对密码做 SHA-256 哈希再存库登录时把输入的密码同样哈希后比对。改动量大约二十行代码但它能直接体现你对安全基础的理解。第二个改造点是航班查询增加条件组合。原始表格如果数据量大了全表加载会越来越慢。可以在Manager.java的查询区域加一个下拉框按“出发地”查还是按“目的地”查再配合TableShowPanel的刷新逻辑把 SQL 从SELECT * FROM flights改成SELECT * FROM flights WHERE departure ?这就是一个完整的查询增强演示点。第三个改造点是把Orders表和用户信息联动展示。原始代码里订单记录可能只显示用户 ID不算直观。你可以增加一个多表联查的 SQLSELECT order_no, username, flight_no, status FROM orders JOIN users ON orders.user_id users.user_id JOIN flights ON orders.flight_id flights.flight_id然后把结果输出到另一个表格面板。这一个改动就能串联起三张表也解释了外键关联的意义。验证方面我拿到这份源码后通常强制自己走一遍完整冒烟测试新建一个数据库导入flight_data.sql→ 改好MySqld.java的密码 → 启动登录窗口 → 注册一个新账号 → 登录 → 查航班 → 订票 → 退票 → 再订票 → 改签 → 重置数据 → 确认表格回到初始状态。每一步用眼睛核对界面数据和数据库数据是否一致。这套流程走完你基本就能理清 Swing 界面、控制层、JDBC 数据层三者是怎么协作的。拿到源码别急着改功能先把数据库导入、驱动配好、跑通登录三个原始动作做顺再考虑扩展。设计模式可以后面学但代码分层和事务控制这两个习惯越早养成越好。希望帮到你。本文还有配套的精品资源点击获取