ARTICLE DETAIL

资讯详情

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

Java Swing + JDBC 智慧公交管理系统:课程设计实战与调度逻辑实现

Java Swing + JDBC 智慧公交管理系统:课程设计实战与调度逻辑实现 简介这是一套面向计算机专业学生与Java初学者的智慧公交管理系统完整项目源码基于Java GUI与MySQL开发可作为课程设计、数据库大作业或毕业设计的参考方案。系统围绕公交公司日常运营场景提供车辆信息、员工信息、线路与站点信息、排班方案管理以及密码修改等功能并区分管理员、调度员、员工三类角色登录信息分别存放于不同数据表贴近真实业务权限划分。资源包共2个文件包含1个zip工程压缩包与1个busmanage.sql数据库脚本整体约10.72MB前者为可直接导入IDEA运行的Java工程后者用于在MySQL Workbench中还原员工表、登录表、排班表、站点表、线路表、车辆表及线路-站点表等结构。项目基于JDK 1.8与mysql-connector-java-8.0.29开发配置好数据库后运行Login.java即可启动描述中标注百分百可运行。目前已有1136人学习下载适合需要快速获取可运行项目、理解GUI与数据库联动逻辑的读者参考借鉴。1. 从一次课程设计答辩说起为什么我选 Swing JDBC 做智慧公交公交调度这类题目很多同学第一反应是套 Spring Boot 加前端框架结果答辩时被问「数据库连接在哪一层关闭」就卡住了。智慧公交管理系统的核心诉求其实很朴素线路、车辆、站点、班次、乘客刷卡记录这几张表要能增删改查调度员坐在一台普通 Windows 机器上双击就能跑。用 Java GUI 加 MySQL 的组合恰好把「面向对象建模」和「JDBC 事务」这两块基本功暴露得清清楚楚这也是它常年出现在 java课程设计案例源码 里的原因。我拆的这套系统走的是 Swing 桌面端路线界面用 JFrame、JTable、JDialog 搭数据落 MySQL中间靠 JDBC 直连。它适合三类人正在找 java课程设计 题目的在校生、想复习 java基础 与 JDBC 的转行者、以及需要一份能改造成实训模板的讲师。下面按「建表 → 连库 → 界面 → 调度逻辑 → 排错」的顺序把能复现的细节摊开讲。2. 数据库建模与 JDBC 连接层五张表撑起调度业务2.1 线路、车辆、站点、班次、刷卡记录的表结构智慧公交的数据关系不复杂但字段设计决定了后面 GUI 好不好写。核心是「线路—班次—车辆」三级关联一条线路有多个班次一个班次绑定一辆车。站点用中间表挂在线路上刷卡记录则同时引用班次和乘客。-- 线路表一条公交线路的基础信息 CREATE TABLE line ( line_id INT PRIMARY KEY AUTO_INCREMENT, line_name VARCHAR(50) NOT NULL, -- 如 K1路 start_stop VARCHAR(50), end_stop VARCHAR(50), first_time TIME, -- 首班时间 last_time TIME -- 末班时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 车辆表车牌唯一状态区分在营/维修 CREATE TABLE bus ( bus_id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(20) UNIQUE NOT NULL, seat_count INT DEFAULT 30, status TINYINT DEFAULT 1 -- 1在营 0维修 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 班次表线路 车辆 发车时间的组合 CREATE TABLE schedule ( schedule_id INT PRIMARY KEY AUTO_INCREMENT, line_id INT, bus_id INT, depart_time DATETIME, arrive_time DATETIME, FOREIGN KEY (line_id) REFERENCES line(line_id), FOREIGN KEY (bus_id) REFERENCES bus(bus_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;line_name加普通索引即可因为 GUI 里最常按线路名模糊查询schedule上的两个外键别省删线路时能挡住脏数据。字符集统一用utf8mb4否则站名里的生僻字会变成问号这是中文项目最常见的坑。2.2 用 DBUtil 封装连接避免每个按钮都 new 一次Swing 是单线程事件模型如果每次点击都DriverManager.getConnection界面会明显卡顿。常见做法是抽一个DBUtil把 URL、账号、密码集中管理并配一个简易连接池。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/bus_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PWD 123456; static { // 只加载一次驱动 try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 统一关闭防止 GUI 反复操作后连接泄漏 public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (SQLException ignored) {} try { if (s ! null) s.close(); } catch (SQLException ignored) {} try { if (c ! null) c.close(); } catch (SQLException ignored) {} } }serverTimezoneAsia/Shanghai必须写MySQL 8 驱动不配时区会直接抛The server time zone value异常这是 java环境变量配置 之外最容易被忽略的一步。useSSLfalse只是本地开发图省事正式环境应换成加密连接。参数characterEncodingutf8要和建表字符集对齐否则中文站名写入即乱码。2.3 预编译语句与事务边界调度业务里「新增班次」往往要同时写schedule并更新车辆状态这两步必须在一个事务里。用PreparedStatement既防注入又提升批量插入性能。public void addSchedule(int lineId, int busId, Timestamp depart) throws SQLException { String sql INSERT INTO schedule(line_id, bus_id, depart_time) VALUES(?,?,?); try (Connection c DBUtil.getConn()) { c.setAutoCommit(false); // 手动开事务 try (PreparedStatement ps c.prepareStatement(sql)) { ps.setInt(1, lineId); ps.setInt(2, busId); ps.setTimestamp(3, depart); ps.executeUpdate(); c.commit(); // 两步都成功才提交 } catch (SQLException e) { c.rollback(); // 任一步失败整体回滚 throw e; } } }setAutoCommit(false)之后忘记commit是新手高频错误表现为数据「插进去了但查不到」。try-with-resources保证连接自动归还比手写finally更稳。批量导入刷卡记录时把addBatch()和executeBatch()配合使用一千条记录能从秒级降到百毫秒级。3. Swing 界面与表格联动JTable 怎么和 ResultSet 对上3.1 用 DefaultTableModel 承载查询结果Swing 里展示数据的主力是JTable但它本身不存数据真正干活的是TableModel。把ResultSet逐行塞进DefaultTableModel是最直接的写法也方便后续刷新。public void loadSchedule(JTable table) { String[] cols {班次号, 线路, 车牌, 发车时间}; DefaultTableModel model new DefaultTableModel(cols, 0); String sql SELECT s.schedule_id, l.line_name, b.plate_no, s.depart_time FROM schedule s JOIN line l ON s.line_idl.line_id JOIN bus b ON s.bus_idb.bus_id ORDER BY s.depart_time; try (Connection c DBUtil.getConn(); Statement st c.createStatement(); ResultSet rs st.executeQuery(sql)) { while (rs.next()) { model.addRow(new Object[]{ rs.getInt(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4) }); } } catch (SQLException e) { e.printStackTrace(); } table.setModel(model); // 一次性替换避免逐行刷新闪烁 }列名数组要和SELECT的字段顺序严格对应错位是「数据串列」的元凶。setModel放在循环外能避免每加一行就重绘一次。查询用JOIN而不是在 Java 里循环查线路名减少往返次数这在数据量上千后差别很明显。3.2 事件监听按钮、下拉框与表格选中行GUI 的交互靠ActionListener和ListSelectionListener。比如「删除选中班次」按钮要先拿到表格当前行再取主键去删。delBtn.addActionListener(e - { int row table.getSelectedRow(); if (row -1) { // 没选中直接提示 JOptionPane.showMessageDialog(null, 请先选择一行); return; } int id (int) table.getValueAt(row, 0); // 第0列是班次号 try (Connection c DBUtil.getConn(); PreparedStatement ps c.prepareStatement( DELETE FROM schedule WHERE schedule_id?)) { ps.setInt(1, id); ps.executeUpdate(); loadSchedule(table); // 删完立即刷新 } catch (SQLException ex) { ex.printStackTrace(); } });getSelectedRow()在未选中时返回 -1不判断就会ArrayIndexOutOfBounds。删除后必须重新loadSchedule否则界面还显示旧数据用户会以为没删掉。下拉框JComboBox装线路列表时建议把line_id作为隐藏值存进Item对象而不是靠显示文本反查。3.3 界面与数据层解耦的常见做法把 SQL 全写在按钮监听里代码很快就没法维护。我一般会抽一个ScheduleDao界面只调dao.findAll()、dao.delete(id)。这样换数据库或加缓存时改动集中在 DAO 层。Swing 的SwingWorker适合把耗时查询放到后台线程避免查询大数据量时窗口假死doInBackground里查库done里更新表格模型。4. 调度核心逻辑时间冲突检测与班次自动生成4.1 同一车辆时间重叠的判定调度最怕一辆车被排了两个重叠班次。判定逻辑是对同一bus_id新班次的发车时间落在已有班次的[depart, arrive]区间内即冲突。-- 查询某车在指定时间段内是否已有班次 SELECT COUNT(*) FROM schedule WHERE bus_id ? AND depart_time ? -- 新班次到达时间 AND arrive_time ?; -- 新班次发车时间两个条件交叉判断是区间重叠的标准写法比在 Java 里遍历所有班次再比较更省事。返回大于 0 就弹窗提示「该车辆时段冲突」。注意arrive_time允许为空时要用IFNULL(arrive_time, depart_time INTERVAL 1 HOUR)兜底否则比较结果不可靠。4.2 按发车间隔批量生成班次一条线路通常按固定间隔发车手点太慢。给一个起始时间、结束时间和间隔分钟数循环生成即可。public int batchGenerate(int lineId, int busId, LocalDateTime start, LocalDateTime end, int gapMin) throws SQLException { String sql INSERT INTO schedule(line_id,bus_id,depart_time,arrive_time) VALUES(?,?,?,?); int count 0; try (Connection c DBUtil.getConn(); PreparedStatement ps c.prepareStatement(sql)) { c.setAutoCommit(false); LocalDateTime t start; while (!t.isAfter(end)) { ps.setInt(1, lineId); ps.setInt(2, busId); ps.setTimestamp(3, Timestamp.valueOf(t)); ps.setTimestamp(4, Timestamp.valueOf(t.plusMinutes(40))); // 假设单程40分钟 ps.addBatch(); // 攒批不立即执行 t t.plusMinutes(gapMin); count; } ps.executeBatch(); // 一次性提交 c.commit(); } return count; }addBatch攒够再executeBatch比逐条executeUpdate快一个数量级。gapMin是发车间隔plusMinutes(40)是预估单程时长这两个参数应做成界面输入框而不是写死。生成前先跑一遍 4.1 的冲突查询能避免批量插入后才发现整段冲突。4.3 参数怎么调间隔、单程时长与首末班参数含义常见取值调大/调小的影响gapMin发车间隔高峰 5-10平峰 15-20调小班次密车辆占用高单程时长预估到达耗时30-60 分钟调小易致到达时间早于实际首末班运营时间窗06:00-22:00决定批量生成边界seat_count座位数20-45影响满载率统计这些值没有标准答案取决于线路实际客流。我一般把高峰和平峰拆成两段分别生成而不是全程一个间隔。批量生成后建议在表格里按发车时间排序核对一遍肉眼扫一眼有没有明显空档。5. 打包运行与常见报错排查5.1 从 IDE 到可双击运行的 jar课程设计要交的是能直接跑的东西不是一堆源码。用 Maven 打可执行 jar把 MySQL 驱动一起打进去。# 在项目根目录执行生成带依赖的 fat jar mvn clean package # 运行Windows 双击或命令行 java -jar target/bus-system-1.0-jarless.jarpom.xml里要配maven-shade-plugin或maven-assembly-plugin否则运行时抛ClassNotFoundException: com.mysql.cj.jdbc.Driver。Main-Class指向带main方法的启动类。如果报源发行版 17 需要目标发行版 17说明maven.compiler.source和target没对齐统一改成同一个版本即可。5.2 高频异常对照表报错信息根因处理The server time zone valueURL 缺时区加serverTimezoneAsia/ShanghaiAccess denied for user账号密码错核对 DBUtil 常量Communications link failureMySQL 未启动/端口错检查服务与 3306中文乱码字符集不一致建表与 URL 都用 utf8mb4界面假死查询在主线程改用 SwingWorker排查顺序建议从「驱动是否加载 → 连接串是否正确 → 表是否存在 → 数据是否提交」逐层往下别一上来就怀疑代码逻辑。e.printStackTrace()一定要保留很多同学 catch 后什么都不打出问题只能干瞪眼。5.3 一个容易被忽略的收尾技巧答辩演示前把数据库导出成bus_system.sql随项目一起交并在 README 里写清「先建库、再导数据、最后运行 jar」。演示机没装 MySQL 时可以临时切到 H2 内存库只需改DBUtil里的 URL 和驱动类SQL 基本不用动。这样即便环境不配合系统也能跑起来比现场手忙脚乱强得多。本文还有配套的精品资源点击获取
返回列表