简介这是一份基于Java SE的物流管理系统完整项目采用Swing构建图形界面、MySQL存储数据适合Java初、中级学习者用于课程设计或毕业设计参考。项目覆盖输入输出、JDBC、GUI、多线程等核心知识点从数据采集到增删改查均有清晰实现可帮助读者理解面向对象开发与数据库编程的落地流程。压缩包共770个文件以717个Java源码文件为主另含SQL脚本、properties配置、jar驱动及项目报告PDF等资源整体仅5.76MB内容紧凑且便于下载学习。目前已有199人学习浏览源码自带详细注释并配备IDEA导入教程和完整项目报告书按说明配置好MySQL后即可在IDEA中快速导入运行适合需要一套可运行、可讲解、可扩展的物流管理项目作为实践模板的开发者。1. 基于Java Swing的物流管理系统IDE导入即跑的课程设计源码这套物流管理系统底层用 Java SE 写业务逻辑界面用 Swing 组件构建数据持久化交给 MySQL连接走 JDBC 驱动。功能上覆盖了物流数据的采集、匹配、保存、显示典型的信息管理系统结构登录验证、运单管理、客户信息维护、状态查询汇总逻辑一圈都能跑通。对正在做 Java 课程设计、需要一套能讲清楚原理的实操项目的人来说这份资源的价值在于它不只是源码还带了项目报告、SQL 初始化脚本、JDBC 驱动 JAR 包和 IDEA 导入教程。以前我带课程设计时最常见的情况是学生代码写完了却卡在环境配置上——JDK 版本不对、驱动没加载、数据库没初始化。这套资源把这些问题都预判到了按照导入教程走一遍基本能直接跑起来。2. JDBC连接与数据访问层驱动加载、工具类封装与增删改查写这类管理系统最容易被低估的是数据库这一层。Swing 界面的组件拖一拖布局代码翻一翻就能出来但一旦牵涉到 JDBC 连接、驱动版本、字符集和时区掉进坑里的人比比皆是。先看驱动加载再谈连接管理最后把增删改查封装成可以复用的形式这是我在每个 Java SE 课程设计里都会固定走的一套顺序。2.1 MySQL 8.x 下的 JDBC 驱动加载与连接参数MySQL 8.0 之后的驱动类全名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver如果还按老写法在 MySQL 8 环境里会直接抛 ClassNotFoundException。我一般会把连接信息单独抽到一个常量区用静态代码块注册驱动import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * 数据库连接工具类 * 所有 DAO 层共享一个连接入口 */ public class DBUtil { // MySQL 8.x 驱动全类名注意中间有 cj private static final String DRIVER com.mysql.cj.jdbc.Driver; // useSSLfalse 关闭本地 SSL 校验serverTimezone 指定时区 // characterEncodingutf8 保证中文写入不乱码 private static final String URL jdbc:mysql://localhost:3306/logistics_db ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这个工具类解决的是驱动生命周期的问题。注意静态代码块——它只在 DBUtil 类第一次被加载时执行一次驱动只需要注册一次没必要每次连接都重复 Class.forName。URL 连接串里有三个参数很关键useSSLfalse 防止本地连数据库时被强制走 SSL 校验serverTimezoneAsia/Shanghai 解决 MySQL 8 默认时区带来的时间偏移characterEncodingutf8 配合 MySQL 端的 utf8mb4 字符集避免下单备注、收件人姓名这类中文出现问号。密码字段在开发环境直接放明文但这是课程设计生产环境不能这么写。参数取值作用useSSLfalse关闭 SSL 校验本地演示环境不启用加密连接serverTimezoneAsia/Shanghai让 java.sql.Timestamp 和 MySQL 时间类型对齐避免时区偏移 8 小时characterEncodingutf8连接串与驱动侧统一用 UTF-8 编码allowPublicKeyRetrievaltrueMySQL 8 缓存认证插件下允许客户端获取服务端公钥如果用 8.0 的驱动在 JDBC URL 里漏掉 serverTimezone启动时通常会报时区乱码错误类似 The server time zone value Öйú±ê׼ʱ¼ä漏掉 allowPublicKeyRetrieval 则会出现 Public Key Retrieval is not allowed。这两个异常都记住到第五章排查时会对应上。2.2 PreparedStatement 与增删改查的封装落地数据库连接写好后下一层任务是把 DAO 写出来。我习惯用 PreparedStatement 而不是 Statement核心原因就一个——参数绑定能直接避开 SQL 注入风险。用户在前端输入运单号或客户名如果代码用SELECT * FROM waybill WHERE waybill_no input 这种拼接写法一旦输入里夹了单引号SQL 语句就变形了轻则查询出错重则整表被清空。import util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; /** * 运单信息 DAO * 只处理单张表的增删改查不掺业务规则 */ public class WaybillDao { /** * 插入一条运单记录 * return 受影响的行数返回 1 表示插入成功 */ public int insertWaybill(Waybill waybill) { String sql INSERT INTO waybill (waybill_no, sender_name, receiver_name, status, create_time) VALUES (?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { // 占位符索引从 1 开始与 SQL 里的顺序一一对应 ps.setString(1, waybill.getWaybillNo()); ps.setString(2, waybill.getSenderName()); ps.setString(3, waybill.getReceiverName()); ps.setString(4, waybill.getStatus()); ps.setTimestamp(5, new Timestamp(System.currentTimeMillis())); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }这段代码里值得留意的是 try-with-resourcesConnection 和 PreparedStatement 都声明在 try 的小括号里方法结束后会自动调用 close()不需要在 finally 里手动释放资源。查询操作多用 executeQuery() 配合 ResultSet 遍历更新操作统一走 executeUpdate()返回的是受影响的行数判断增删改是否成功就看这个返回值是否大于 0。物流场景里还有个高频操作是批量写入——比如从 Excel 导入运单每条记录逐行 insert 会很慢。更合适的做法是用 addBatch() 分批次提交// 批量插入示例把 1000 条运单分批提交 String sql INSERT INTO waybill (waybill_no, sender_name, receiver_name, status) VALUES (?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 先关掉自动提交攒一批再 commit for (Waybill wb : waybillList) { ps.setString(1, wb.getWaybillNo()); ps.setString(2, wb.getSenderName()); ps.setString(3, wb.getReceiverName()); ps.setString(4, wb.getStatus()); ps.addBatch(); // 加入当前批次 if (count % 200 0) { ps.executeBatch(); // 每 200 条刷一次避免内存积压 } } ps.executeBatch(); conn.commit(); } catch (SQLException e) { e.printStackTrace(); }关于批量插入两个经验值得分享一是记得在插入前关闭自动提交用 setAutoCommit(false) 把多条插入包成一个事务否则中途失败了已插入的部分会变成残缺数据二是批次大小控制在 200 到 500 条之间比较稳妥。真实场景下如果数据量只有几千条逐行 insert 也不用纠结性能Java SE 课程设计的数据库日增量通常远达不到需要刻意调优的级别。2.3 连接资源的释放顺序与连接池意识连接资源的释放顺序在面试里被问的概率很高先释放 ResultSet再释放 PreparedStatement最后关 Connection。用 try-with-resources 之后这个顺序是 JVM 帮你保证的但如果你还在写老式的 finally 块注意别把 Connection 关掉后还去读 ResultSet——那会直接抛 SQLRecoverableException。至于连接池很多人会问课程设计要不要上 Druid 或 HikariCP。我的看法是纯课程设计阶段不用。这类系统的并发量通常就是几个人同时点一点单连接走天下也不会出问题。但你要理解连接池存在的意义——每次 new Connection 都要经历 TCP 握手和 MySQL 认证握手在高并发下这部分开销会被放大。如果以后做企业项目HikariCP 配置也就十来行原理还是这套 JDBC 标准接口。提示在 Swing 程序里执行数据库操作时如果查询数据量不大在事件分发线程里同步执行也能跑通。但若是查询 1 万条以上的日志数据建议把查询放到后台线程再通过 SwingUtilities.invokeLater() 刷新界面否则窗口会卡到白屏看起来像程序死掉。3. IDEA导入前的环境准备JDK版本、MySQL初始化与SQL脚本落地很多同学拿到这套源码第一个卡点反而不是 Java 代码而是软件环境。JDK 版本对不上、IDEA 没配置过项目 SDK、MySQL 没初始化数据库、SQL 脚本不知道在哪执行——每一步都会卡住准备运行的进度。这一章把从环境准备到项目导入的完整链路理一遍。3.1 JDK环境变量与IDEA版本选择这套系统的技术基础是 Java SEJDK 8 或 JDK 11 都能编译运行。Swing 组件在 JDK 8 以后几乎没有大的 API 变动所以老的 Swing 项目放到新 JDK 上一般也能继续跑。需要注意的是如果本地已经装了 JDK 17 或更高版本IDEA 里项目结构那块要手动指到具体的 SDK不能让它默认用最新 JDK 跑老代码否则会遇到模块访问限制比如 java.desktop 模块相关的访问报错。IDEA 的选择上社区版IntelliJ IDEA Community Edition就能完成 Java SE Swing 项目的导入、编译和运行没必要去找付费版。社区版官方免费功能覆盖课程设计所需的全部操作。安装时看清勾选项把 Java 插件选上Create Desktop Shortcut 之类按自己习惯勾导入后把 Project SDK 指到 JDK 8 即可。JDK 环境变量里需要配置的是 JAVA_HOME 和 PATH。以 Windows 为例系统变量里新建 JAVA_HOME变量值指向 JDK 安装目录比如 C:\Program Files\Java\jdk1.8.0_202然后在 Path 里追加 %JAVA_HOME%\bin。配置完成后在命令行窗口执行 java -version能打印版本信息说明环境变量生效。这一步是后续所有 Java 操作的前提没有 JAVA_HOMEIDEA 很多功能也会失灵。# 验证 JDK 环境变量是否配置成功 echo %JAVA_HOME% java -version # 正常输出类似 java version 1.8.0_2023.2 IDEA项目导入的标准操作步骤这套源码下载后解压出来是一个完整的 IDEA 工程目录。不要用 File - New 去新建项目那样反而会把源码目录弄乱。直接用 Open 打开解压后的目录IDEA 识别到 .idea 文件夹后会按原项目的构建配置自动加载。具体流程打开 IDEA选择 Open 或 File - Open定位到解压后的项目根目录。等待 IDEA 完成索引右下角进度条走完。如果出现 Missing project SDK 提示按 CtrlAltShiftS 打开 Project Structure。在 Project 标签页里Project SDK 下拉框选择 JDK 8没有就点 Add SDK - JDK选中 JDK 安装目录。确认 Language Level 与 JDK 版本匹配比如 JDK 8 就选 8。切到 Libraries 标签页确认 MySQL 驱动 JAR 包已存在不存在则手动添加 JAR 包路径。点 Apply 后关闭窗口找到包含 main 方法的启动类点绿色运行三角。整个过程里第 4 步和第 6 步是最容易出问题的有人安装了 JDK 但没配 JAVA_HOMEIDEA 自动发现不了 SDK也有人把驱动 JAR 放在项目文件夹里却忘了确认 Libraries 引用运行时抛 ClassNotFoundException。IDEA 的库引用和磁盘上的文件是两回事JAR 放在磁盘上不代表被编译进项目这个意识要建立起来。3.3 SQL脚本执行与数据库初始化验证源码包里自带的 SQL 文件是数据库初始化脚本建库、建表、插入测试数据全在一个文件里完成。执行前先确认 MySQL 服务已经启动然后建议用命令行方式登录 root 执行避免不同图形化客户端对脚本语法兼容不一致的问题。# 在 cmd 或终端中执行 mysql -u root -p logistics_db.sql回车后输入密码。如果脚本里已经包含了 CREATE DATABASE数据库会自动建好不需要预先手动建库。执行完成后登录 MySQL验证表是否创建成功USE logistics_db; SHOW TABLES; -- 正常应显示 user、waybill、customer 等核心表如果 SHOW TABLES 返回空集多半是脚本执行时没有落库大概率是文件路径没写对或者 root 权限不足。Windows 下执行重定向时注意 mysql 命令的工作目录要包含该 SQL 文件或者使用绝对路径。MySQL 服务端的字符集也要确认特别是 Windows 环境如果 MySQL 5.7 的默认字符集是 latin1后续程序无论怎么指定连接字符集写入的中文都可能变成问号。查看当前服务端字符集的命令SHOW VARIABLES LIKE character_set_server; -- 结果若为 utf8mb4 表示正常若是 latin1 则需修改配置如果返回 latin1需要把 my.ini 或 my.cnf 里的 character-set-server 改成 utf8mb4保存后重启 MySQL 服务。MySQL 8.0 的默认字符集已经是 utf8mb4一般不需要额外修改。这里的坑在于——JDBC 连接串指定 characterEncoding 只是客户端行为服务端的建表字符集是另一回事两者必须同时满足才能保证中文不乱码。很多时候学员说是连接串的问题其实是数据库建表时的字符集没设对。提示执行 TRUNCATE 或 DROP 语句前一定先做好备份。SQL 脚本里如果带了测试数据误删后重新导入会很麻烦。课程设计阶段建议把初始 SQL 文件留一份原始副本反复修改表结构导致数据错乱时直接重建库重新初始化。4. Swing核心功能实现登录验证、运单匹配与表格刷新有了稳定的数据访问层就到了系统的门面——Swing 界面。导航栏、表格、搜索框都是 JFrame、JTable、JTextField 这些基础组件堆出来的难的不是组件调用而是怎么把界面操作和数据库访问串联起来让点击按钮之后有数据返回。这一章拆开看登录、核心匹配逻辑和表格展示三块。4.1 登录模块JFrame 表单与账号校验登录页面是用户进入系统的第一道门功能很简单输入用户名和密码点击登录去 user 表里校验账号是否存在、密码是否正确。Swing 里的做法是给登录按钮注册 ActionListener在事件回调里读取输入框内容调用 DAO 层做校验。import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class LoginFrame extends JFrame { private JTextField usernameField new JTextField(16); private JPasswordField passwordField new JPasswordField(16); private UserDao userDao new UserDao(); public LoginFrame() { setTitle(物流管理系统登录); setSize(360, 220); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel panel new JPanel(new GridLayout(3, 2, 8, 8)); panel.add(new JLabel(用户名)); panel.add(usernameField); panel.add(new JLabel(密码)); panel.add(passwordField); JButton loginBtn new JButton(登录); loginBtn.addActionListener(new LoginAction()); panel.add(loginBtn); setContentPane(panel); setVisible(true); } private class LoginAction implements ActionListener { Override public void actionPerformed(ActionEvent e) { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()).trim(); if (userDao.checkLogin(username, password)) { dispose(); // 关闭登录窗口释放资源 new MainFrame(); // 打开主界面 } else { JOptionPane.showMessageDialog(LoginFrame.this, 用户名或密码错误); } } } }这里有两个常见的新手错误。一个是 JPasswordField 取密码用的是 getPassword() 方法拿到的是 char 数组再转成 String 做校验另一个是登录成功后要调用 dispose() 而不是 setVisible(false)否则旧登录窗口还挂在内存里程序关不掉进程一直在后台。还有个小细节密码框的默认回显字符是圆点想改成星号可以用 setEchoChar(*)。4.2 运单匹配的核心逻辑状态流转与动态条件查询物流系统的核心不是界面而是数据匹配逻辑。一张运单从创建到签收通常会经历待发货、运输中、已签收这几个状态状态变化后前端列表要能立刻反映出来。所谓匹配就是把输入条件转成查询 SQL 的可选条件。初学者容易拼 SQL比如SELECT * FROM waybill WHERE 11 AND waybill_no LIKE % keyword %。乍一看能跑但一旦输入里带单引号、百分号这种特殊字符查询行为就会偏离预期。更稳的做法是参数化查询配合动态条件先搭好 SQL 骨架再用占位符绑定import util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class WaybillQueryDao { /** * 按关键字查询运单支持运单号、客户名模糊搜索 * param keyword 搜索关键字传空字符串则查询全部 */ public ListWaybill queryByKeyword(String keyword) { ListWaybill list new ArrayList(); StringBuilder sql new StringBuilder(SELECT * FROM waybill WHERE 11); ListString params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append( AND (waybill_no LIKE ? OR sender_name LIKE ? OR receiver_name LIKE ?)); String likeKeyword % keyword.trim() %; params.add(likeKeyword); params.add(likeKeyword); params.add(likeKeyword); } try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setString(i 1, params.get(i)); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Waybill wb new Waybill(); wb.setId(rs.getInt(id)); wb.setWaybillNo(rs.getString(waybill_no)); wb.setSenderName(rs.getString(sender_name)); wb.setReceiverName(rs.getString(receiver_name)); wb.setStatus(rs.getString(status)); wb.setCreateTime(rs.getTimestamp(create_time)); list.add(wb); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }写动态条件查询最稳妥的形式就是三段式第一段拼基础 SQL第二段根据输入决定是否追加 WHERE 条件第三段统一设置占位符参数。不要把用户输入直接嵌入 SQL 字符串。这里的 LIKE 模糊查询参数值是%关键字%占位符里不能自己加引号引号交给 PreparedStatement 处理。另一个容易被忽略的点是查询条件顺序。占位符的顺序必须和 SQL 里 ? 的排列一致否则结果会错乱到怀疑人生。字段多的时候我一般会在 SQL 里把条件逐个列出来按顺序绑定参数并打日志运行后对照数据库表看结果。4.3 JTable 展示查询结果与刷新机制匹配结果最终要落回界面。Swing 里做表格展示JTable 配合 DefaultTableModel 是最常见的组合。查询结果转成二维数组塞进 TableModel后续刷新只需要重新查询并替换 modelimport javax.swing.*; import javax.swing.table.DefaultTableModel; import java.util.List; public class WaybillTableHelper { /** * 把查询结果转成表格模型并刷新界面 */ public static void refreshTable(JTable table, ListWaybill waybills) { String[] columns {运单号, 发货人, 收货人, 状态, 创建时间}; Object[][] rows new Object[waybills.size()][5]; for (int i 0; i waybills.size(); i) { Waybill wb waybills.get(i); rows[i][0] wb.getWaybillNo(); rows[i][1] wb.getSenderName(); rows[i][2] wb.getReceiverName(); rows[i][3] wb.getStatus(); rows[i][4] wb.getCreateTime() null ? : wb.getCreateTime().toString(); } // 每次刷新都重置 model列名和数据一并更新 DefaultTableModel model new DefaultTableModel(rows, columns); table.setModel(model); table.getColumnModel().getColumn(0).setPreferredWidth(140); } }JTable 刷新的常见误区是直接调用 table.repaint() 或 table.updateUI()。这个方法只能触发重绘无法更新数据。真正有效的方式是把新的 DefaultTableModel set 到 table 上JTable 底层通过 TableModelListener 自动触发重绘。setAutoCreateRowSorter 如果之前开了每次刷新后会自动重新排序某些列不希望被排序打乱顺序可以手动关闭。Swing 线程模型的纪律也要记住Swing 组件只能在事件分发线程上修改。如果为了查询性能在后台线程里做数据库操作查询完成准备更新表格时一定要用 SwingUtilities.invokeLater() 把模型更新包一层否则界面会出现随机花屏控制台也会打出线程违反约束的警告。5. 避坑排查JDBC连接失败、中文乱码与导入异常这部分列的问题都是我在这类 Java SE Swing MySQL 项目里实际踩过或在答疑里出现率最高的单独记成一个排查章节。按现象对号入座逐项检查基本都能解决。5.1 现象抛出 ClassNotFoundException: com.mysql.jdbc.Driver原因MySQL 8.x 之后驱动全类名改成了 com.mysql.cj.jdbc.Driver而代码或配置文件里还写着老驱动名或者驱动 JAR 没有编译进项目运行时在类路径里找不到。解决先确认引入的驱动包版本。如果用的是 mysql-connector-java 8.0.x把 DRIVER 字符串改成 com.mysql.cj.jdbc.Driver如果驱动包是 5.1.x类名可以继续用 com.mysql.jdbc.Driver。说完驱动名再检查 IDEA 的 Project Structure - Libraries 面板确认 JAR 包已经加载而不是只放在文件系统里。写个临时方法打一条日志确认驱动注册到底走没走到static { try { Class.forName(DRIVER); System.out.println(驱动加载成功); } catch (ClassNotFoundException e) { System.err.println(驱动类不存在请检查 JAR 包与类名); } }如果多个 JDBC 驱动 JAR 混在工程里还可能出现加载到旧版本驱动的情况。建议 lib 目录里只保留一个和 MySQL 版本匹配的驱动包不要图省事把一堆版本全塞进去。5.2 现象连接 MySQL 8 报 Public Key Retrieval is not allowed原因MySQL 8 默认的 caching_sha2_password 认证插件在 SSL 关闭的情况下要求客户端先获取服务端公钥JDBC URL 里没有指定允许公钥检索于是连接被拒。解决在 JDBC URL 里追加 allowPublicKeyRetrievaltrue。本地开发环境推荐这样写jdbc:mysql://localhost:3306/logistics_db?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaiallowPublicKeyRetrieval 和 useSSL 是固定搭配allowPublicKeyRetrievaltrue 时通常要求 useSSLfalse因为公钥检索本身是明文传输开启 SSL 时属于不安全操作反过来把 useSSL 设成 true 而 allowPublicKeyRetrieval 为 false又会遇到 SSL 握手失败。课程设计本地环境两个都按上面推荐的值配就够用。5.3 现象MySQL 5.7 写入中文变成 ??原因数据库或表的默认字符集是 latin1JDBC 连接串里的 characterEncodingutf8 只能影响客户端发送编码覆盖不了服务端建表字符集。汉字在 latin1 下无法表达落库后变成问号。解决分三层检查。第一层连接串确认带 characterEncodingutf8第二层用 SHOW CREATE TABLE waybill; 查看表字符集如果是 latin1执行 ALTER TABLE waybill CONVERT TO CHARACTER SET utf8mb4;第三层检查服务端默认字符集老版本 MySQL 需要修改 my.ini 里的 character-set-server 并重启。已写入的问号没法自动修复只能删掉重新导入 SQL 脚本里的初始数据。5.4 现象IDEA 导入后代码全红运行 main 方法提示类不存在原因项目导入时IDEA 没有把 src 目录标记为源根或者驱动 JAR 没有被解析为 Library。更多时候是 Project SDK 未指定项目索引机制无法完成导致所有类都标红。解决右键 src 目录 - Mark Directory as - Sources Root接着检查 Project Structure - Libraries确认驱动 JAR 在列最后确认 Project SDK 不是 No SDK。完成这三步后等待 IDEA 右下角进度条跑完红色报错会逐步消失。如果还有零散标红执行一次 File - Invalidate Caches让 IDEA 清理缓存重新索引。5.5 现象运行按钮灰色提示 SDK is not defined原因新导入的项目没有绑定 Project SDK。IDEA 只识别了项目结构文件但 JDK 路径没配置编译和运行按钮全部不可用。解决进入 Project Structure - Project点击 Project SDK 旁边的 New选择 JDK 安装目录的根路径注意是 JDK 的根目录而不是 bin 目录。选中后 Language Level 会自动匹配。如果 JDK 路径没问题还提示 SDK not defined检查 JAVA_HOME 环境变量在命令行里执行 echo %JAVA_HOME% 看输出为空就按第三章的方式补上。# 验证 JAVA_HOME 是否为空 echo %JAVA_HOME% # 输出为空说明环境变量未配置需重新设置这类问题的共性是 IDEA 的项目配置、磁盘文件、环境变量三者各自独立任何一个环节脱节IDE 都会报出各种看似玄学的错误。排查时不要盯着报错本身看要按 SDK、依赖、源根三个方向分别验证一般五分钟内能定位。6. 交付前的自测技巧核心流程跑通再上台演示这套系统能跑起来不代表演示时不会翻车。我通常在答辩或提交前把核心流程按顺序过一遍登录、新增运单、修改状态、条件查询、退出系统每步记录结果。验证方式不用复杂直接在 main 方法里写一段自测程序依次调用 DAO 层的方法关键步骤打印结果public class SelfTest { public static void main(String[] args) { WaybillDao dao new WaybillDao(); // 1. 插入一条测试运单 Waybill wb new Waybill(); wb.setWaybillNo(WL20250612001); wb.setSenderName(张三); wb.setReceiverName(李四); wb.setStatus(待发货); System.out.println(插入结果 dao.insertWaybill(wb)); // 2. 按关键字查询 ListWaybill list new WaybillQueryDao().queryByKeyword(WL20250612001); System.out.println(查询结果数量 list.size()); // 3. 更新状态为运输中 System.out.println(更新结果 dao.updateStatus(WL20250612001, 运输中)); } }跑一次自测插入、查询、更新三个核心操作都验证过比现场临时点点按钮更可控。每次改动表结构后我也习惯重新执行一次自测保证 DAO 层没有因为字段变更而崩掉。演示时除了功能流程还值得提前准备一两个加分项比如把登录错误次数的提示做得更友好或者在表格上方加一个状态筛选下拉框这些改动在 Swing 里都是几行代码的事却能明显提升完成度。从那以后我每次做课程设计项目都强制在交付前走一遍这个自测流程把数据库连接、核心操作和界面入口三个环节单独验证再交给老师演示。希望帮到你。本文还有配套的精品资源点击获取