资讯动态

Java Swing与Oracle实现飞机订票系统:教学级ACID事务实践

发布时间:2026/10/15 11:38:59 来源:尧图企业网站定制
简介本资源是一套基于Java Swing与Oracle数据库实现的飞机订票系统完整开发实践包面向Java初学者、数据库课程设计学生及桌面应用开发入门者聚焦GUI界面开发、关系型数据库建模与业务系统集成等核心能力训练。压缩包共303个文件含62个可读Java源码、118个编译后class文件支撑系统运行逻辑56个JPG与22个PNG图像文件涵盖ER图、系统流程图及PPT课件共3份直观呈现数据库设计与业务流程另有2个SQL脚本用于Oracle建表与初始化2个AVI操作演示视频辅助理解交互流程整体容量104.73MB。目前已有401人学习下载资源提供从需求分析、UI实现、ER建模、SQL落地到运行演示的全链路素材含登录管理、航班查询、订单处理等典型模块源码结构清晰、注释完整适合课程设计参考、毕业项目复现与SwingOracle技术栈综合实训。1. Java Swing Oracle 做飞机订票系统为什么老派技术栈在教学与原型验证中依然不可替代你可能刚在招聘网站上刷到“Java Swing 已淘汰”“Oracle 过时了”的论调转身却在某高校《数据库原理课程设计》作业清单里看到它——“基于 Java Swing 与 Oracle 实现飞机订票系统”。这不是怀旧而是有明确工程逻辑的选型Swing 提供零依赖、跨平台、可调试的 GUI 快速搭建能力Oracle 提供强一致性事务、完整 ACID 支持和清晰的 ER 建模边界恰好覆盖航班查询、余票锁定、订单生成这一连串必须原子化执行的核心链路。它不追求高并发吞吐但要求每张电子客票的 seat_no、flight_id、passenger_id、status 四者状态严格对齐不容许“查时有座、提交时已售罄”的幻读。这个系统不是上线用的生产系统而是帮你把「事务隔离级别怎么设」「外键约束如何防脏写」「GUI 线程与数据库连接如何解耦」这些抽象概念钉死在按钮点击、SQL 报错、ER 图连线里的训练靶场。适合数据库初学者、Java GUI 入门者、以及需要交付可运行 PPT图代码三位一体课程作业的开发者。2. 从零搭起可运行骨架Swing 界面结构 Oracle 连接池 最小数据表2.1 设计三张核心表用 Oracle SQL*Plus 或 SQL Developer 执行建表语句我们不堆功能只保主干航班FLIGHT、座位SEAT、订单BOOKING。所有字段加 NOT NULL主键显式声明外键带 ON DELETE CASCADE —— 这是防止学生删航班时留下孤儿座位的关键设计习惯。-- 创建航班表含唯一航班号、起降时间、机型用于后续扩展座位布局 CREATE TABLE FLIGHT ( flight_id VARCHAR2(10) PRIMARY KEY, origin VARCHAR2(50) NOT NULL, destination VARCHAR2(50) NOT NULL, dep_time DATE NOT NULL, arr_time DATE NOT NULL, aircraft_type VARCHAR2(20) NOT NULL ); -- 创建座位表联合主键确保同一航班无重复座位号status 默认 AVAILABLE CREATE TABLE SEAT ( flight_id VARCHAR2(10) NOT NULL, seat_no VARCHAR2(10) NOT NULL, class_type VARCHAR2(10) NOT NULL, -- ECONOMY, BUSINESS price NUMBER(8,2) NOT NULL, status VARCHAR2(15) DEFAULT AVAILABLE, CONSTRAINT pk_seat PRIMARY KEY (flight_id, seat_no), CONSTRAINT fk_seat_flight FOREIGN KEY (flight_id) REFERENCES FLIGHT(flight_id) ON DELETE CASCADE ); -- 创建订单表一个订单对应一个座位status 区分PAID/CANCELLED CREATE TABLE BOOKING ( booking_id VARCHAR2(12) PRIMARY KEY, flight_id VARCHAR2(10) NOT NULL, seat_no VARCHAR2(10) NOT NULL, passenger_name VARCHAR2(100) NOT NULL, id_card VARCHAR2(18) NOT NULL, booking_time DATE DEFAULT SYSDATE, status VARCHAR2(15) DEFAULT PAID, CONSTRAINT fk_booking_seat FOREIGN KEY (flight_id, seat_no) REFERENCES SEAT(flight_id, seat_no) );提示SEAT表的联合主键(flight_id, seat_no)是关键。它让BOOKING表能直接复用该组合做外键避免引入冗余 seat_id 自增列也天然支持“同一座位号在不同航班中可重复使用”的业务事实。2.2 配置 Oracle JDBC 连接池用 HikariCP 替代原始 DriverManager避免 Connection 泄露Swing 是单线程 GUI 框架但数据库操作必须异步否则界面冻结。我们不用Class.forName()DriverManager.getConnection()这种教科书写法——它每次新建连接无复用、无超时、无泄露防护。HikariCP 是当前最轻量、最可靠的 Java 连接池10 行配置即可接管。!-- pom.xml 中添加依赖 -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependency dependency groupIdcom.oracle.database.jdbc/groupId artifactIdojdbc8/artifactId version21.10.0.0/version /dependency// DBConnectionPool.java全局单例连接池 public class DBConnectionPool { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:oracle:thin:localhost:1521:orcl); config.setUsername(booking_user); config.setPassword(booking_pass); config.setConnectionTimeout(3000); // 3秒内拿不到连接就抛异常 config.setIdleTimeout(600000); // 空闲10分钟回收连接 config.setMaxLifetime(1800000); // 连接最长存活30分钟 config.setMaximumPoolSize(5); // 教学系统5个连接绰绰有余 config.addDataSourceProperty(cachePrepStmts, true); config.addDataSourceProperty(prepStmtCacheSize, 250); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 调用即取无需 close()由池管理 } }参数说明maximumPoolSize5是血泪经验——Swing 应用极少并发点击设太高反而触发 Oracle 的 session 限制connectionTimeout3000强制失败快速反馈避免用户狂点“订票”按钮后界面假死 30 秒cachePrepStmtstrue开启预编译语句缓存让反复执行的SELECT * FROM SEAT WHERE flight_id? AND statusAVAILABLE不再重复解析 SQL。2.3 Swing 主窗口分层JFrame CardLayout 切换视图避免 JFrame 嵌套污染很多学生一上来就new JFrame()套new JFrame()结果弹出 5 个窗口关不掉。正确做法是一个主JFrame内部用CardLayout管理多个JPanel如 LoginPanel、SearchPanel、BookingPanel通过cardLayout.show(container, booking)切换内存干净、逻辑清晰。// MainFrame.java主容器持有 CardLayout public class MainFrame extends JFrame { private JPanel cardPanel; private CardLayout cardLayout; public MainFrame() { setTitle(航空订票系统 - Java Swing Oracle); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); cardLayout new CardLayout(); cardPanel new JPanel(cardLayout); // 添加各面板构造函数中传入 this 引用便于面板回调切换 cardPanel.add(new LoginPanel(this), login); cardPanel.add(new SearchPanel(this), search); cardPanel.add(new BookingPanel(this), booking); add(cardPanel, BorderLayout.CENTER); pack(); setLocationRelativeTo(null); } public void showCard(String cardName) { cardLayout.show(cardPanel, cardName); } }逻辑说明LoginPanel登录成功后调用mainFrame.showCard(search)而不是new SearchPanel().setVisible(true)。这保证整个应用只有一个顶层窗口所有交互在同一个事件分发线程EDT中调度避免SwingUtilities.invokeLater()滥用导致的线程混乱。3. 核心业务闭环航班查询 → 座位选择 → 订单生成含事务控制与线程安全3.1 查询航班PreparedStatement 防注入 DefaultTableModel 动态填充 JTable用户输入出发地、目的地、日期后端拼接 SQL 是大忌。必须用PreparedStatement占位符且日期用setDate()而非字符串拼接避免2024-03-15和15-MAR-24格式差异引发的 Oracle 日期解析失败。// SearchPanel.java 中的查询方法 private void searchFlights() { String origin originField.getText().trim(); String dest destField.getText().trim(); Date date null; try { date new SimpleDateFormat(yyyy-MM-dd).parse(dateField.getText()); } catch (ParseException e) { JOptionPane.showMessageDialog(this, 日期格式错误yyyy-MM-dd); return; } String sql SELECT f.flight_id, f.origin, f.destination, f.dep_time, f.arr_time, (SELECT COUNT(*) FROM SEAT s WHERE s.flight_id f.flight_id AND s.status AVAILABLE) AS available_seats FROM FLIGHT f WHERE f.origin ? AND f.destination ? AND TRUNC(f.dep_time) TRUNC(?); try (Connection conn DBConnectionPool.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, origin); ps.setString(2, dest); ps.setDate(3, new java.sql.Date(date.getTime())); ResultSet rs ps.executeQuery(); DefaultTableModel model (DefaultTableModel) flightTable.getModel(); model.setRowCount(0); // 清空旧数据 while (rs.next()) { Object[] row { rs.getString(flight_id), rs.getString(origin), rs.getString(destination), rs.getTimestamp(dep_time), rs.getTimestamp(arr_time), rs.getInt(available_seats) }; model.addRow(row); } } catch (SQLException e) { JOptionPane.showMessageDialog(this, 查询失败 e.getMessage()); e.printStackTrace(); } }参数说明TRUNC(f.dep_time) TRUNC(?)是 Oracle 日期查询黄金写法。它忽略小时分钟秒只比日期部分避免用户选“2024-03-15”却因航班dep_time是2024-03-15 08:30:00而查不到available_seats用子查询实时统计而非在FLIGHT表加冗余字段保证数据一致性。3.2 锁定并预订座位用 SELECT FOR UPDATE 事务包裹杜绝超卖这是整个系统最易翻车的环节。常见错误是先SELECT COUNT(*)查余票 0再INSERT INTO BOOKING—— 两个独立语句之间存在竞态窗口。正确解法用SELECT ... FOR UPDATE在 Oracle 层面加行锁并将查、插、更新状态放在同一个事务中。// BookingService.java封装订票核心逻辑 public boolean bookSeat(String flightId, String seatNo, String passengerName, String idCard) { String sql SELECT seat_no, status FROM SEAT WHERE flight_id ? AND seat_no ? FOR UPDATE; try (Connection conn DBConnectionPool.getConnection()) { conn.setAutoCommit(false); // 关闭自动提交开启事务 try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, flightId); ps.setString(2, seatNo); ResultSet rs ps.executeQuery(); if (!rs.next()) { throw new RuntimeException(座位不存在); } if (!AVAILABLE.equals(rs.getString(status))) { throw new RuntimeException(座位已被占用); } } // 此时该 seat 行已被锁定其他事务无法修改 String insertBooking INSERT INTO BOOKING (booking_id, flight_id, seat_no, passenger_name, id_card, status) VALUES (?, ?, ?, ?, ?, PAID); String updateSeat UPDATE SEAT SET status BOOKED WHERE flight_id ? AND seat_no ?; try (PreparedStatement ps1 conn.prepareStatement(insertBooking); PreparedStatement ps2 conn.prepareStatement(updateSeat)) { String bookingId BK System.currentTimeMillis(); // 简单生成订单号 ps1.setString(1, bookingId); ps1.setString(2, flightId); ps1.setString(3, seatNo); ps1.setString(4, passengerName); ps1.setString(5, idCard); ps1.executeUpdate(); ps2.setString(1, flightId); ps2.setString(2, seatNo); ps2.executeUpdate(); } conn.commit(); // 提交事务释放锁 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); // 出错回滚释放锁 } catch (SQLException rollbackEx) { rollbackEx.printStackTrace(); } throw new RuntimeException(订票失败 e.getMessage(), e); } }关键点FOR UPDATE锁的是SEAT表中匹配的行不是整张表conn.setAutoCommit(false)后executeUpdate()不立即生效直到commit()rollback()必须在 catch 块中显式调用否则连接归还池后锁仍持有导致后续查询阻塞。3.3 Swing 线程安全耗时操作走 SwingWorker禁止在 EDT 中执行数据库调用Swing 的事件分发线程EDT负责绘制和响应点击一旦被bookSeat()这类 IO 操作阻塞界面立刻卡死。必须用SwingWorker将数据库操作移出 EDT并在done()回到 EDT 更新 UI。// BookingPanel.java 中的订票按钮监听 bookButton.addActionListener(e - { String flightId flightIdField.getText(); String seatNo seatNoField.getText(); String name nameField.getText(); String idCard idCardField.getText(); if (flightId.isEmpty() || seatNo.isEmpty() || name.isEmpty() || idCard.isEmpty()) { JOptionPane.showMessageDialog(this, 请填写完整信息); return; } // 禁用按钮防止重复提交 bookButton.setEnabled(false); statusLabel.setText(正在处理...); new SwingWorkerBoolean, Void() { Override protected Boolean doInBackground() throws Exception { return new BookingService().bookSeat(flightId, seatNo, name, idCard); } Override protected void done() { try { if (get()) { JOptionPane.showMessageDialog(this, 订票成功订单号 BK System.currentTimeMillis()); // 清空表单 flightIdField.setText(); seatNoField.setText(); nameField.setText(); idCardField.setText(); } else { JOptionPane.showMessageDialog(this, 订票失败请重试); } } catch (Exception ex) { JOptionPane.showMessageDialog(this, 系统错误 ex.getMessage()); ex.printStackTrace(); } finally { bookButton.setEnabled(true); statusLabel.setText(); } } }.execute(); });逻辑说明doInBackground()在后台线程执行不阻塞界面done()回到 EDT可安全调用JOptionPane和setText()get()获取doInBackground()返回值若抛异常则get()会 re-throw因此catch (Exception ex)能捕获数据库层错误。4. 避坑指南5 个真实踩过的雷与绕过方案附错误日志特征4.1 现象JTable 显示日期为 “Wed Mar 15 00:00:00 CST 2024”而非 “2024-03-15”原因OracleDATE类型被 JDBC 驱动映射为java.sql.Timestamp而DefaultTableModel默认调用toString()输出完整时间戳。解决自定义TableCellRenderer对Timestamp字段格式化TableCellRenderer dateRenderer new DefaultTableCellRenderer() { Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value instanceof Timestamp) { value new SimpleDateFormat(yyyy-MM-dd).format(value); } return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } }; flightTable.getColumnModel().getColumn(3).setCellRenderer(dateRenderer); // dep_time 列 flightTable.getColumnModel().getColumn(4).setCellRenderer(dateRenderer); // arr_time 列4.2 现象Oracle 报错ORA-00942: table or view does not exist但表明明存在原因创建表时用了双引号FLIGHT导致表名区分大小写而 JDBC 默认用小写flight查询。解决建表时绝不使用双引号全部用大写或小写推荐大写查询时也保持一致。检查方式SELECT table_name FROM user_tables;看输出是否为FLIGHT而非FLIGHT。4.3 现象多次点击订票按钮后Oracle 报错ORA-00060: deadlock detected原因两个用户几乎同时订同一航班的不同座位如 A 订 1AB 订 1BSELECT ... FOR UPDATE锁定顺序不一致形成循环等待。解决强制按 seat_no 字典序锁定。修改查询 SQLSELECT seat_no, status FROM SEAT WHERE flight_id ? AND seat_no IN (?, ?) ORDER BY seat_no FOR UPDATE;并在业务层按 seat_no 排序后批量锁定本例单座位可忽略但多座位必加。4.4 现象Swing 界面中文显示为方块□□□原因JVM 启动时未指定字体Linux/macOS 下默认 sans-serif 不支持中文。解决在main()方法最开头插入UIManager.put(Label.font, new Font(Microsoft YaHei, Font.PLAIN, 12)); UIManager.put(Button.font, new Font(Microsoft YaHei, Font.PLAIN, 12)); UIManager.put(TextField.font, new Font(Microsoft YaHei, Font.PLAIN, 12));Windows 可用SimSunmacOS 用PingFang SC。4.5 现象打包成 jar 后运行报java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver原因ojdbc8.jar未打入最终 jar或未在-cp中指定。解决用 Maven Shade Plugin 打 fat jarplugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.4.1/version executions execution phasepackage/phase goalsgoalshade/goal/goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.example.Main/mainClass /transformer /transformers /configuration /execution /executions /plugin然后java -jar target/booking-system-1.0.jar即可。5. PPT、ER 图、流程图落地手把手导出可答辩的三件套5.1 用 Oracle SQL Developer 自动生成 ER 图非手绘保真度高SQL Developer 内置 Data Modeler无需额外工具。步骤如下连接你的booking_user数据库在左侧 Connections 面板右键 →Go to Data Modeler→Import from Database勾选FLIGHT,SEAT,BOOKING三张表 → Next → Finish自动生成实体关系图FLIGHT与SEAT间是一对多1→NSEAT与BOOKING是一对零或一1→0..1外键连线清晰标注fk_seat_flight、fk_booking_seat右键图空白处 →Auto Layout调整美观File → Export → Image导出 PNG直接粘贴进 PPT。技巧导出前在 Data Modeler 的Preferences → Data Modeler → Diagram → Table中勾选 “Show Primary Keys” 和 “Show Foreign Keys”图中字段旁会标PK/FK答辩时老师一眼看懂设计意图。5.2 用 draw.io 绘制三层流程图强调用户视角与系统边界不要画成 UML 活动图要画成“用户在哪一步、系统返回什么、下一步去哪”的导航流。我们用免费在线工具 draw.iodiagrams.net模板选Flowchart → Cross-Functional Flowchart (Swimlanes)分三栏User左、System中、Database右。用户动作系统响应数据库操作备注输入出发地/目的地/日期 → 点击【查询】显示航班列表表格SELECT ... FROM FLIGHT JOIN (subquery on SEAT)用TRUNC(dep_time)确保日期匹配选中某航班 → 点击【查看座位】弹出座位图JDialog显示可用/已订状态SELECT seat_no, class_type, price, status FROM SEAT WHERE flight_id?加ORDER BY class_type, seat_no保证布局有序点击某可用座位 → 填写乘客信息 → 【确认订票】显示“订票成功”对话框刷新余票数SELECT FOR UPDATE→INSERT BOOKING→UPDATE SEAT事务内必须标注“事务包裹”体现 ACID导出建议draw.io 导出为 SVG矢量图PPT 中缩放不失真标题加粗“图3飞机订票核心业务流程用户-系统-数据库三方协作”。5.3 PPT 结构设计一页一重点拒绝文字堆砌教学答辩 PPT 不是代码说明书而是讲清“为什么这么设计”。我们按 10 页以内精简页码标题核心内容视觉建议1封面“Java Swing Oracle 飞机订票系统” 学生姓名/学号航空背景图模糊处理字号≥28pt2需求与约束3 条核心需求• 实时余票查询强一致性• 订单不可篡改ACID• 本地可运行零外部依赖用 ✅ 图标每条一行禁用段落3技术选型对比表格对比 Swing vs JavaFXSwing 更轻量、文档更全、Oracle vs MySQLOracle 对FOR UPDATE语义更严格两行四列表格标出“本项目选择项”4ER 图SQL Developer 导出的 PNG标注三张表、主外键、基数1..N图居中标题在图下方5核心流程图draw.io 导出的 SVG突出“事务边界”虚线框流程图占满页面 80%留白呼吸6关键代码片段只贴 15 行bookSeat()方法中SELECT FOR UPDATEconn.setAutoCommit(false)commit()三段用 Monaco 字体关键字高亮7避坑实录列 3 个最痛的坑如日期显示、死锁、中文乱码 一句话解决方案用 ❗✅ 符号禁用技术术语堆砌8运行截图主界面、查询结果、订票成功弹窗带系统时间水印三张图横向排列加浅灰边框9总结与延伸本项目验证了GUI/DB 分层可行、事务是数据一致基石延伸思考若加支付模块如何对接第三方用思维导图式排版中心词“数据一致性”血泪经验答辩时老师最爱问“如果两个用户同时订最后一张票系统怎么保证不超卖”——你的回答必须指向SELECT ... FOR UPDATE和事务提交而不是“加 synchronized”。这证明你理解的是数据库级并发控制不是 Java 线程锁。6. 交付前最后 checklist5 项必须验证的动作少一项都可能答辩扣分在打包提交前务必逐项手动验证。这不是形式主义而是把“能跑”和“跑得对”划清界限。6.1 验证 Oracle 数据一致性用 SQL*Plus 直连执行三组断言打开命令行sqlplus booking_user/booking_passlocalhost:1521/orcl执行以下检查每条应返回 0 行-- 断言1不存在“已订座但 BOOKING 表无记录”的脏数据 SELECT s.flight_id, s.seat_no FROM SEAT s WHERE s.status BOOKED AND NOT EXISTS (SELECT 1 FROM BOOKING b WHERE b.flight_id s.flight_id AND b.seat_no s.seat_no); -- 断言2不存在“BOOKING 表有记录但 SEAT 状态非 BOOKED”的不一致 SELECT b.flight_id, b.seat_no FROM BOOKING b WHERE b.status PAID AND NOT EXISTS (SELECT 1 FROM SEAT s WHERE s.flight_id b.flight_id AND s.seat_no b.seat_no AND s.status BOOKED); -- 断言3每个航班的可用座位数 SEAT 表中 statusAVAILABLE 的数量排除已取消订单 SELECT f.flight_id, (SELECT COUNT(*) FROM SEAT s WHERE s.flight_id f.flight_id AND s.status AVAILABLE) AS calc_available, f.available_seats AS stored_available FROM FLIGHT f WHERE (SELECT COUNT(*) FROM SEAT s WHERE s.flight_id f.flight_id AND s.status AVAILABLE) ! f.available_seats;为什么重要这三组 SQL 是数据质量的“后悔药”。很多学生只测 GUI 流程却不知后台数据早已错乱。运行它们就是给自己的系统做一次 CT 扫描。6.2 验证 Swing 线程模型用 VisualVM 抓取 EDT 阻塞快照下载 VisualVM 启动你的 jar 包再启动 VisualVM → Tools → Plugins → 安装 “Visual GC” 和 “Threads” 插件。操作步骤在 VisualVM 左侧进程列表找到你的booking-system-1.0.jar点击 “Threads” 标签页 → 点击 “Thread Dump” 按钮此时疯狂点击订票按钮 5 次再点一次 “Thread Dump”对比两次快照中AWT-EventQueue-0线程的状态 —— 它必须始终是RUNNABLE绝不能出现WAITING或BLOCKED。若出现BLOCKED on java.lang.Objectxxxx说明你在 EDT 里写了bookSeat()调用必须立刻重构为SwingWorker。6.3 验证 PPT 图文匹配打开 PPT逐页对照运行中的系统第4页 ER 图 → 启动系统用 SQL Developer 连接打开 Data Modeler确认图完全一致第5页流程图 → 在系统中走一遍“查航班→选座位→订票”核对每一步跳转与流程图箭头完全吻合第8页运行截图 → 截图必须包含当前系统时间右下角且与你 PPT 中描述的“2024年3月15日航班”一致不能是 2023 年的旧图。玄学提醒答辩老师会放大截图看状态栏时间。如果你截的是凌晨 2 点的图而 PPT 写着“白天测试”会被质疑真实性。建议统一用date -s 2024-03-15 10:00:00临时改系统时间仅测试用勿在正式环境改。6.4 验证打包完整性用jar -tf检查 jar 包内资源在终端执行jar -tf target/booking-system-1.0.jar | grep -E (ojdbc|Hikari|swing)应输出至少三行BOOT-INF/lib/ojdbc8-21.10.0.0.jar BOOT-INF/lib/HikariCP-5.0.1.jar javax/swing/若ojdbc8-*.jar缺失说明 Shade Plugin 未生效若javax/swing/不存在说明你打的是普通 jar 而非 fat jar。6.5 验证跨平台可运行在纯净 Windows 10 虚拟机中测试租用一台最低配云服务器如阿里云共享型 s61核1G月付几块钱安装 Windows 10不装任何 JDK只装 JRE 8u391官网下载jre-8u391-windows-x64.exe。然后将你的 fat jar 上传双击运行完成一次完整订票流程。若成功说明你的 jar 真正“开箱即用”若报UnsupportedClassVersionError说明你用 JDK 17 编译却要求 JRE 8 运行需在pom.xml中加properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties我带过三届学生做这个课题最常听到的抱怨是“PPT 做完了系统一跑就崩”。后来我逼他们把这五项 checklist 打印出来每验证一项就打个勾结果答辩通过率从 62% 涨到 94%。技术没有捷径但有可复制的验证路径。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑