简介这是一套基于Java Swing与MySQL开发的轻量级停车场管理系统面向Java初学者与课程设计实践者解决小型停车场基础业务管理需求涵盖车辆出入场、计费标准配置、用户信息维护及多角色权限控制等核心功能。资源包共80个文件含25个Java源码文件实现GUI界面与业务逻辑、47个编译后Class文件可直接运行、1个SQL建表脚本含charger/park/station/users四张关键表、2个配置偏好文件及配套图标与项目元数据整体压缩包仅2.11MB结构清晰便于理解MVC分层与Swing事件驱动机制。已有728人学习下载提供完整可运行工程含JDK 1.8与MySQL 8.0.13环境适配说明附带实际运行截图与数据库设计文档链接帮助读者快速部署、调试并掌握桌面端数据库应用开发全流程。1. 这不是个“练手小项目”一个能真跑在Windows服务窗口、连上MySQL 8.0、支持多角色权限切换的Java Swing停车场系统为什么值得你花45分钟搭起来你可能见过几十个叫“停车场管理系统”的Java课程设计——界面花哨但一运行就报ClassNotFoundException数据库脚本执行失败管理员登录后点“车辆入场”直接弹空指针。而这个ParkingSwing.zip不一样它不是教学Demo是我在本地Win11JDK1.8MySQL 8.0.13实测通过的可交付最小闭环系统。它真正实现了“用户查当前车位历史记录缴费明细”和“管理员做入场登记出场结算计费标准动态调整”两套权限流所有Swing组件JTable渲染、JComboBox联动、JDialog模态弹窗都经过真实数据驱动验证。特别适合三类人刚学完Swing事件监听想落地的同学、需要Java桌面端实战案例投简历的应届生、以及被Spring Boot Web项目搞晕想回溯Java基础IO/线程/数据库连接本质的转岗工程师。它不炫技但每行代码都在解决一个具体问题比如park表里in_time用datetime类型存Swing里就必须用SimpleDateFormat(yyyy-MM-dd HH:mm:ss)格式化显示否则JTable单元格会显示成java.util.Datexxxx再比如MySQL 8.0默认开启SSL不加?useSSLfalseserverTimezoneUTC参数Class.forName(com.mysql.cj.jdbc.Driver)之后DriverManager.getConnection()必连不上——这些血泪细节本文全部拆开给你看。2. 从解压到双击运行环境配置、数据库初始化与源码结构三步落地2.1 JDK 1.8 MySQL 8.0.13 环境确认与关键参数校验这不是“装好JDK就行”的粗放配置。必须逐项验证因为Swing桌面应用对JVM启动参数和MySQL连接字符串极其敏感。先确认你的JDK版本是否真的匹配java -version # 必须输出类似 # java version 1.8.0_391 # Java(TM) SE Runtime Environment (build 1.8.0_391-b12) # Java HotSpot(TM) 64-Bit Server VM (build 25.391-b12, mixed mode)提示如果显示openjdk version 17或更高说明你用的是新版OpenJDK必须卸载并安装Oracle JDK 1.8官网已下架可用国内镜像如清华源下载jdk-8u391-windows-x64.exe。Swing的JFileChooser在JDK 11有路径解析bug会导致“选择图片上传”功能失效。MySQL 8.0.13同样要核对两点一是root密码是否为明文该系统未加密存储密码若你MySQL启用了caching_sha2_password插件需降级为mysql_native_password二是时区是否强制设为UTC。执行以下SQL-- 登录MySQL后执行 SELECT global.time_zone, session.time_zone; -- 若非UTC立即修正 SET GLOBAL time_zone 00:00; SET time_zone 00:00;2.2 数据库建表与初始数据注入四个表的字段逻辑与业务约束项目摘要提到四张表charger计费标准、park停车记录、station车位信息、users用户。但CSDN链接已失效我们从src目录下的SQL文件反推真实结构。在src/sql/路径找到init.sql若无则从res/或lib/中提取其核心建表语句如下-- users表区分user_type0(普通用户)和1(管理员) CREATE TABLE users ( id int NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL UNIQUE, password varchar(100) NOT NULL, -- 明文存储仅用于教学 user_type tinyint NOT NULL DEFAULT 0, -- 0用户,1管理员 balance decimal(10,2) DEFAULT 0.00, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- park表关键字段in_time/out_time必须为datetime且out_time允许NULL CREATE TABLE park ( id int NOT NULL AUTO_INCREMENT, plate_number varchar(20) NOT NULL, in_time datetime NOT NULL, out_time datetime DEFAULT NULL, fee decimal(10,2) DEFAULT 0.00, station_id int NOT NULL, user_id int NOT NULL, PRIMARY KEY (id), KEY fk_station (station_id), KEY fk_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- station表status0表示空闲1表示占用这是Swing界面实时刷新的依据 CREATE TABLE station ( id int NOT NULL AUTO_INCREMENT, station_code varchar(10) NOT NULL UNIQUE, status tinyint NOT NULL DEFAULT 0, -- 0空闲,1占用 PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- charger表单条记录fee_per_hour单位为元/小时支持小数 CREATE TABLE charger ( id int NOT NULL AUTO_INCREMENT, fee_per_hour decimal(5,2) NOT NULL DEFAULT 5.00, min_fee decimal(5,2) NOT NULL DEFAULT 3.00, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意park表中out_time设为DEFAULT NULL至关重要。Swing界面点击“车辆出场”时程序会执行UPDATE park SET out_timeNOW(), fee? WHERE id?若该字段为NOT NULLMySQL 8.0会直接报错中断事务。这是新手最容易翻车的点之一。2.3 源码包结构解剖src/res/bin/lib四层分工与编译依赖链解压ParkingSwing.zip后目录结构不是随意摆放而是遵循Java桌面应用经典分层目录作用关键文件示例为什么不能删src/Java源码根目录LoginFrame.java,AdminMainFrame.java,ParkDAO.java所有业务逻辑在此Swing事件监听、JDBC操作全在.java里res/资源文件非代码icon.png,bg.jpg,config.propertiesLoginFrame构造函数中setIconImage(Toolkit.getDefaultToolkit().getImage(res/icon.png))硬编码路径删了图标变空白bin/编译输出目录LoginFrame.class,ParkDAO.classEclipse/IDEA默认输出路径若你用命令行编译javac -d bin src/*.java必须指向此目录lib/第三方JAR包mysql-connector-java-8.0.13.jar唯一必需JARSwing本身是JDK内置但连接MySQL必须此驱动版本必须严格匹配MySQL 8.0.13编译与运行命令链在项目根目录执行# 1. 编译所有Java文件到bin目录 javac -sourcepath src -d bin src/*.java # 2. 运行注意必须指定classpath包含lib/mysql-connector-java-8.0.13.jar和bin java -cp bin;lib/mysql-connector-java-8.0.13.jar LoginFrame逻辑说明-cp参数中bin提供编译后的class文件lib/...jar提供JDBC驱动。若写成-cp bin:lib/...Linux冒号分隔Windows会报Could not find or load main class LoginFrame——这是平台路径分隔符导致的玄学错误务必用分号。3. Swing界面与数据库交互的核心链路从登录验证到车辆出场结算的完整数据流3.1 登录验证Swing事件监听如何触发JDBC查询并跳转不同主界面LoginFrame.java是入口类其loginButton.addActionListener()监听器是整个系统的第一个数据枢纽。关键代码段如下loginButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); // getPassword()返回char[]避免String常量池泄露 // 1. 构造SQL查询注意此处无预编译仅教学用途生产环境必须用PreparedStatement防SQL注入 String sql SELECT id, username, password, user_type FROM users WHERE username username ; try (Connection conn DBUtil.getConnection(); // DBUtil是封装好的工具类读取res/config.properties获取URL Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { if (rs.next()) { String dbPassword rs.getString(password); if (password.equals(dbPassword)) { // 明文比对教学简化 int userType rs.getInt(user_type); int userId rs.getInt(id); // 2. 根据user_type跳转不同主界面 if (userType 1) { // 管理员 new AdminMainFrame(userId).setVisible(true); // 传入userId用于后续操作日志 } else { // 普通用户 new UserMainFrame(userId).setVisible(true); } dispose(); // 关闭登录窗口 } else { JOptionPane.showMessageDialog(null, 密码错误, 登录失败, JOptionPane.ERROR_MESSAGE); } } else { JOptionPane.showMessageDialog(null, 用户名不存在, 登录失败, JOptionPane.ERROR_MESSAGE); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, 数据库连接失败 ex.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); } } });参数说明DBUtil.getConnection()内部读取res/config.properties其内容必须为urljdbc:mysql://localhost:3306/parkingdb?useSSLfalseserverTimezoneUTC usernameroot passwordyour_root_password若config.properties中url缺少?useSSLfalseserverTimezoneUTCgetConnection()会抛出java.sql.SQLException: The server time zone value ... is unrecognized——这是MySQL 8.0最经典的SSL与时区双坑。3.2 车辆入场Swing表单提交如何生成park记录并更新station状态管理员点击“车辆入场”弹出EntryDialog.java用户填写车牌号、选择车位后点击确定触发核心逻辑okButton.addActionListener(e - { String plate plateField.getText().trim(); int stationId (int) stationCombo.getSelectedItem(); // stationCombo是JComboBox值为station.id if (plate.isEmpty()) { JOptionPane.showMessageDialog(null, 请输入车牌号); return; } // 1. 检查该车位是否空闲关键业务校验 String checkSql SELECT status FROM station WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement psCheck conn.prepareStatement(checkSql)) { psCheck.setInt(1, stationId); try (ResultSet rs psCheck.executeQuery()) { if (rs.next() rs.getInt(status) 1) { JOptionPane.showMessageDialog(null, 该车位已被占用请选择其他车位); return; } } } // 2. 插入park记录in_time自动设为NOW() String insertSql INSERT INTO park (plate_number, in_time, station_id, user_id) VALUES (?, NOW(), ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement psInsert conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { psInsert.setString(1, plate); psInsert.setInt(2, stationId); psInsert.setInt(3, adminUserId); // 当前管理员ID用于审计 int affected psInsert.executeUpdate(); if (affected 0) { // 3. 更新station状态为占用 String updateSql UPDATE station SET status 1 WHERE id ?; try (PreparedStatement psUpdate conn.prepareStatement(updateSql)) { psUpdate.setInt(1, stationId); psUpdate.executeUpdate(); } JOptionPane.showMessageDialog(null, 入场成功车位 stationId); dispose(); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, 入场失败 ex.getMessage()); } });逻辑说明这里体现了Swing桌面应用的典型事务模式——先查后插再更新。Statement.RETURN_GENERATED_KEYS确保能获取自增主键park.id虽然后续未使用但为扩展留接口。station.status的更新必须在同一个Connection内完成否则并发场景下可能出现“两个管理员同时选中同一空闲车位”。3.3 车辆出场与费用计算Swing定时刷新数据库触发式结算UserMainFrame.java中有一个JTable展示当前在场车辆其数据源来自ParkDAO.getAllCurrentParked()方法。但真正的结算发生在管理员点击“车辆出场”时// 在AdminMainFrame中调用 public void doExit(int parkId) { try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 // 1. 查询入场记录 String selectSql SELECT in_time, station_id FROM park WHERE id ? AND out_time IS NULL; try (PreparedStatement psSelect conn.prepareStatement(selectSql)) { psSelect.setInt(1, parkId); try (ResultSet rs psSelect.executeQuery()) { if (!rs.next()) { throw new RuntimeException(记录不存在或已出场); } Timestamp inTime rs.getTimestamp(in_time); int stationId rs.getInt(station_id); // 2. 计算费用核心算法 long diffMinutes Duration.between(inTime.toInstant(), Instant.now()).toMinutes(); double hours Math.ceil((double) diffMinutes / 60); // 向上取整到小时 double fee hours * getFeePerHour(); // getFeePerHour()从charger表查 // 3. 更新park记录设置out_time和fee String updateSql UPDATE park SET out_time NOW(), fee ? WHERE id ?; try (PreparedStatement psUpdate conn.prepareStatement(updateSql)) { psUpdate.setDouble(1, fee); psUpdate.setInt(2, parkId); psUpdate.executeUpdate(); } // 4. 释放车位 String releaseSql UPDATE station SET status 0 WHERE id ?; try (PreparedStatement psRelease conn.prepareStatement(releaseSql)) { psRelease.setInt(1, stationId); psRelease.executeUpdate(); } conn.commit(); // 提交事务 JOptionPane.showMessageDialog(null, String.format(出场成功停车%.1f小时费用%.2f元, hours, fee)); } } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, 出场失败 ex.getMessage()); } }参数说明Duration.between(...)是Java 8新API替代了易出错的Date毫秒差计算。Math.ceil((double)diffMinutes/60)确保“停1分钟也收1小时钱”符合停车场计费惯例。getFeePerHour()方法内部执行SELECT fee_per_hour FROM charger LIMIT 1支持管理员后台修改计费标准。4. 避坑五个让90%新手卡住的真实问题与一招解决法4.1 现象双击运行LoginFrame.class提示“找不到或无法加载主类LoginFrame”原因java -cp参数未包含mysql-connector-java-8.0.13.jar或Windows下误用冒号:分隔classpath应为分号;解决在项目根目录执行完整命令java -cp bin;lib/mysql-connector-java-8.0.13.jar LoginFrame验证技巧先执行dir lib\确认JAR文件存在再执行echo %CLASSPATH%检查系统变量未干扰。4.2 现象登录时弹出“数据库连接失败The server time zone value ... is unrecognized”原因MySQL 8.0默认时区为系统时区如Asia/Shanghai但JDBC驱动要求显式声明serverTimezone解决修改res/config.properties中的url末尾强制添加serverTimezoneUTCurljdbc:mysql://localhost:3306/parkingdb?useSSLfalseserverTimezoneUTC补充若MySQL已设为00:00仍报错则必须加useSSLfalse因MySQL 8.0默认启用SSL但本地未配置证书。4.3 现象管理员点击“车辆入场”后JTable不刷新新记录不显示原因Swing的JTable数据模型DefaultTableModel未在插入数据库后调用fireTableDataChanged()通知界面更新解决在AdminMainFrame.java的入场成功回调中找到tableModel变量并刷新// 假设tableModel是类成员变量 tableModel.addRow(new Object[]{plate, stationId, 入场中..., ...}); tableModel.fireTableDataChanged(); // 关键否则界面静止血泪经验Swing所有UI更新必须在Event Dispatch Thread中执行fireTableDataChanged()内部已处理线程安全无需额外SwingUtilities.invokeLater()。4.4 现象用户查询“当前在场信息”时JTable显示java.util.Datexxxx而非时间字符串原因JTable默认调用Object.toString()而ResultSet.getTimestamp(in_time)返回java.sql.Timestamp对象解决重写JTable的getColumnClass()方法或更简单——在DefaultTableModel添加数据前格式化SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); String timeStr sdf.format(rs.getTimestamp(in_time)); tableModel.addRow(new Object[]{..., timeStr, ...});注意SimpleDateFormat非线程安全必须在方法内新建实例不可作为静态变量复用。4.5 现象MySQL 8.0.13执行init.sql时CREATE TABLE station报错“Unknown character set: utf8mb4”原因MySQL 8.0默认字符集已升级为utf8mb4_0900_ai_ci但旧版SQL脚本未声明兼容性解决在init.sql每条CREATE TABLE语句末尾显式指定引擎与字符集) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;验证登录MySQL后执行SHOW VARIABLES LIKE character_set%;确认character_set_database为utf8mb4。5. 进阶技巧用Swing Timer实现车位状态实时监控与JTable自动刷新5.1 为什么需要Timer——Swing桌面应用的“伪实时”本质Web系统靠AJAX轮询而Swing没有HTTP请求概念。要让管理员主界面的“当前在场车辆”表格每10秒自动刷新必须用javax.swing.Timer——它在Event Dispatch Thread中安全触发避免Swing线程冲突。AdminMainFrame.java中添加如下代码// 在类成员变量声明 private Timer refreshTimer; private final int REFRESH_INTERVAL 10000; // 10秒 // 在构造函数末尾初始化 refreshTimer new Timer(REFRESH_INTERVAL, e - { try { // 1. 重新查询当前在场数据 ListParkRecord currentParked ParkDAO.getAllCurrentParked(); // 2. 清空原有TableModel DefaultTableModel model (DefaultTableModel) table.getModel(); model.setRowCount(0); // 清空所有行 // 3. 逐行添加新数据含时间格式化 SimpleDateFormat sdf new SimpleDateFormat(MM-dd HH:mm); for (ParkRecord r : currentParked) { String inTimeStr sdf.format(r.getInTime()); model.addRow(new Object[]{ r.getId(), r.getPlateNumber(), r.getStationCode(), inTimeStr, 等待出场 }); } } catch (Exception ex) { ex.printStackTrace(); // 刷新失败时不中断Timer继续下次尝试 } }); refreshTimer.start(); // 启动定时器逻辑说明Timer的ActionListener在EDT中执行因此可直接操作JTable模型。setRowCount(0)比循环removeRow(0)高效避免数组越界异常。sdf.format()每次新建实例规避线程安全风险。5.2 如何控制Timer开关——避免内存泄漏与重复启动Timer若不手动停止窗口关闭后仍会持续运行消耗CPU。必须在AdminMainFrame的windowClosing事件中关闭// 在构造函数中添加窗口监听 this.addWindowListener(new WindowAdapter() { Override public void windowClosing(WindowEvent e) { if (refreshTimer ! null refreshTimer.isRunning()) { refreshTimer.stop(); } System.exit(0); // Swing桌面应用退出需显式exit } });参数说明refreshTimer.isRunning()防止重复调用stop()报IllegalStateException。System.exit(0)是Swing应用标准退出方式仅关闭窗口dispose()会导致JVM进程残留。5.3 扩展用JComboBox动态加载车位列表避免硬编码当前EntryDialog.java中车位下拉框是手动添加station_code字符串但实际应从数据库实时加载。改造stationCombo初始化逻辑// 替换原来的 stationCombo.addItem(A01); ... stationCombo.removeAllItems(); // 先清空 try (Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT id, station_code FROM station WHERE status 0)) { while (rs.next()) { // 存储id-station_code映射显示station_code提交时取id stationCombo.addItem(rs.getString(station_code)); // 将id与station_code关联用Map或自定义类 stationMap.put(rs.getString(station_code), rs.getInt(id)); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(null, 加载车位失败); }实现细节stationMap是HashMapString, Integer类成员变量okButton点击时通过stationMap.get((String)stationCombo.getSelectedItem())获取真实station_id。这样新增车位只需在station表插入记录界面自动生效。从那以后我每次调试Swing桌面应用都强制走一遍“关掉IDEA → 手动javac编译 →java -cp运行 → 修改数据库 → 观察界面响应”的全流程因为只有脱离IDE的自动构建才能暴露classpath、资源路径、线程模型这些底层真相。希望帮到你。本文还有配套的精品资源点击获取