ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

基于Java Swing+MySQL的停车场管理系统设计与实现

基于Java Swing+MySQL的停车场管理系统设计与实现 简介这是一份基于Java Swing与MySQL的停车场管理系统完整源码包适合Java初学者、课程设计或毕业设计人群覆盖车辆出入场、计费、用户注册充值、信息查询与后台管理等典型业务场景。资源共80个文件zip压缩包大小约2.11MB主要包含25个Java源文件、47个class编译文件及1个sql数据库脚本另有项目配置文件、运行截图与jar依赖库可直接导入开发环境运行调试。配套SQL脚本提供charger、park、station、users等数据表结构便于快速初始化数据库。系统按用户与管理员双角色划分功能模块登录后分别支持车位查询、入场出场操作、计费标准维护、历史记录查看等界面基于Swing实现适合用于理解桌面端管理系统的分层设计与MySQL交互逻辑。已有728人学习下载适合需要参考完整代码结构、数据库设计或快速搭建同类管理系统的人员使用。1. 基于 Java Swing MySQL 的停车场管理系统把课程设计做成能演示的完整项目如果说“管理系统”是 Java 课程设计里的常青题那“停车场管理”又是其中最考验数据一致性的一个。很多人界面画得挺好一算费用就对不上账一查在场车辆又卡死。这套基于 Java Swing MySQL 的停车场管理系统恰好把业务链路补全了登录区分用户和管理员两个角色车辆入场、出场结算、历史查询、用户充值、计费标准管理都串在 users、charger、park、station 四张表上是典型的 java 课程设计案例源码 应有的完整度。适合两类人一种是准备交 Java 课设、需要一套能跑通全流程的参考代码的另一种是想弄清 Swing 事件和 JDBC 事务怎么衔接的开发者。资源里除了代码还带运行截图和 sql 脚本照着配置就能在新环境里跑起来。2. 先拆数据库四张表怎么撑起停车场的状态管理2.1 表与表之间的关系四个表名背后的业务分工拿到压缩包以后我建议先别急着在 IDEA 里点运行先把 sql 目录下的脚本打开看一遍。很多课设项目看起来功能齐全一查数据库只有两张表所有逻辑都堆在内存里关掉窗口就全丢了。这个项目和那种不同users、station、park、charger 四张表各管一段组合起来才叫“停车场管理系统”。四张表的分工大概是这样的表名承担的职责常见字段设计users用户和管理员共用一张账号表用角色字段区分身份id、username、password、role、balance、create_timestation停车场里的区域或车位位置信息station_no、station_name、location、total_space、available_spacepark每一次停车形成的订单记录id、car_no、user_id、station_no、in_time、out_time、fee、statuscharger计费标准配置出场结算时读取id、type、price_per_hour、free_minutes、max_charge、is_active注意这些字段名是按这类项目的通用设计来描述的你导入 sql 后以实际脚本里的建表语句为准。但从“链路”上看这几张表的关系几乎是固定的park 表通过 user_id 关联 users通过 station_no 关联 station用 status 标识在场还是已出场charger 独立于另外三张表只负责提供费率配置。为什么要把用户和管理员放在同一张 users 表因为系统里“用户”和“管理员”的动作高度相似都要登录、都要改口令管理员只是多了车场管理和计费标准维护。用 role 字段区分能省掉一套几乎重复的账号表登录时根据角色跳转到不同主界面逻辑更干净。2.2 当前可用车位一条 SQL 统计出实时数字系统功能里有个“当前可用车位”这是最容易做成假功能的地方。有些实现是建一个 int 字段存车位剩余数入场减一、出场加一听起来合理但一旦程序异常退出这个数字就再也不会被修正。更稳的做法是实时统计 park 表里的在场记录。SELECT s.station_no, s.station_name, s.total_space - COUNT(p.id) AS available_space FROM station s LEFT JOIN park p ON p.station_no s.station_no AND p.status IN GROUP BY s.station_no, s.station_name;这段 SQL 的意思是从 station 表开始左连接 park 表只统计 status 为 IN 的在场记录然后用 total_space 减去在场数量得到可用数。LEFT JOIN 保证了即使某个 station 一条停车记录都没有也会出现在结果里。这里有两点值得注意。第一不要用 DELETE 去删除出场记录出场时应该 UPDATE status 为 OUT这样用户查历史信息、管理员看历史车流都有数据第二如果停车记录量大要给 park 表加一个联合索引 (status, station_no)这条统计 SQL 就是走索引的简单覆盖查询不会拖慢界面。2.3 计费标准先定义“怎么算钱”再写代码系统里计费标准单独放在 charger 表说明设计者是有意识把“费率配置”和“计费动作”拆开的。这比把价格写死在代码里强得多——管理员在“计费标准管理”界面上改价格出场结算那边不用改代码。常见的计费规则是普通车辆按小时收费免费停车时长内不收费超过免费时长后按小时向上取整累加如果单日上限就用 max_charge 封顶。计算费用的 Java 代码大概长这样long minutes Duration.between(inTime.toInstant(), outTime.toInstant()).toMinutes(); long freeMinutes charger.getFreeMinutes(); long billableMinutes Math.max(0, minutes - freeMinutes); long hours (billableMinutes 59) / 60; // 向上取整 double fee hours * charger.getPricePerHour(); if (fee charger.getMaxCharge()) { fee charger.getMaxCharge(); }这段代码里两个细节决定对账对不对。一是(billableMinutes 59) / 60这是把“不足一小时按一小时算”用整除实现比 Math.ceil 更直接二是封顶判断要放在最后因为先封顶再乘单价会把结果算错。如果管理界面允许配置不同车型费率就在 charger 表里加 type 字段区分查询时带上类型条件。提示如果实际 sql 脚本里 chargter 表用的是“每小时价格半小时精度”这类配置把这个计算方法里的 60 改成对应分钟粒度即可大头逻辑不用动。3. 功能实现登录、出入场、充值这些模块怎么连起来3.1 双角色登录登录按钮背后不只是 SELECT系统的入口是登录窗口用户端和管理员端共用同一套账号体系。Swing 里的实现套路是 JFrame 放账号和密码输入框点击按钮后调 UserDao根据 username 和 password 查 users 表查出结果后按 role 字段决定跳转哪个主窗体。private void loginAction() { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); User user userDao.findByUsernameAndPassword(username, password); if (user null) { JOptionPane.showMessageDialog(this, 账号或密码错误); return; } if (ADMIN.equals(user.getRole())) { new AdminMainFrame(user).setVisible(true); } else { new UserMainFrame(user).setVisible(true); } dispose(); }这段代码里值得留意的不是跳转本身而是new String(passwordField.getPassword())。JPasswordField 的 getPassword 返回的是 char[] 而不是 String用 String 直接接收是常见写法但如果你对安全敏感用完这个字符串后可以及时置空引用。课设场景一般不会追究到这个深度但面试官问起来你能答出来就是加分项。另一个细节是 admin 账号的管理员注册入口在“系统管理”里普通用户不能直接注册管理员。这是通过 role 字段控制的登录后所有需要权限的动作都要判断当前 User 对象的角色而不是只在登录界面拦一次。3.2 车辆入场往 park 表写一条记录同时扣减车位车辆入场功能在“车场管理”菜单下。管理员选择或输入 station_no填入车牌号系统要完成两件事往 park 表插入一条入场记录并把 station 表的可用车位减一。两件事必须在一个事务里完成否则就会出现“车进去了车位没减”的数据不一致。public boolean vehicleIn(String carNo, String stationNo, Integer userId) { String insertParkSql INSERT INTO park(car_no, station_no, user_id, in_time, status) VALUES(?, ?, ?, NOW(), IN); String updateSpaceSql UPDATE station SET available_space available_space - 1 WHERE station_no ? AND available_space 0; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(insertParkSql)) { ps1.setString(1, carNo); ps1.setString(2, stationNo); ps1.setInt(3, userId); ps1.executeUpdate(); } try (PreparedStatement ps2 conn.prepareStatement(updateSpaceSql)) { ps2.setString(1, stationNo); int rows ps2.executeUpdate(); if (rows 0) { conn.rollback(); return false; } } conn.commit(); return true; } catch (SQLException e) { rollbackQuietly(conn); return false; } finally { DBUtil.closeQuietly(conn); } }这里有一个容易被忽略的边界UPDATE ... WHERE available_space 0。如果没有这个条件两个管理员同时给同一个区域入场时可能出现车位数减成负数。加上条件后执行行数为 0 就回滚从数据库层面拦住超卖。提示in_time 用数据库的 NOW() 而不是 Java 的 System.currentTimeMillis()是为了保证后续出场计算时时间基准和设备一致也避免应用服务器和数据库服务器时间偏差造成的计费争议。3.3 车辆出场先算费再扣余额最后更新记录出场逻辑比入场复杂。管理员输入车牌号后系统要做的动作是查出该车在场记录按 2.3 的方法计算费用如果用户余额足够则扣除不够就提示充值同时把 park 表的 status 改成 OUT、回填 fee 和 out_time。这四个步骤同样放进一个事务。public boolean vehicleOut(String carNo) { ParkRecord record parkDao.findActiveByCarNo(carNo); if (record null) { throw new RuntimeException(未找到在场车辆); } double fee calcFee(record.getInTime(), chargerDao.getActiveCharger()); User user userDao.findById(record.getUserId()); if (user.getBalance() fee) { throw new RuntimeException(余额不足请先充值); } String updateParkSql UPDATE park SET out_timeNOW(), statusOUT, fee? WHERE id?; String updateBalanceSql UPDATE users SET balancebalance-? WHERE id? AND balance?; // 两个 update 在同一事务内提交 }先说 fee 的计算。这里不建议把计算逻辑写在 DAO 里而是单独抽一个 calcFee 方法把 inTime 和 charger 对象传进去。这样以后如果改成“首小时 10 元以后每小时 5 元”这种阶梯价格只改这一个方法不用动 DAO。再看扣款 SQLUPDATE users SET balancebalance-? WHERE id? AND balance?这个条件比 Java 里先查余额再扣款要安全。并发场景下两个请求同时读到余额足够的旧值就可能扣成负数。把余额判断放进 WHERE 条件执行行数为 0 就说明余额不足直接回滚。出场完成后再刷新“当前在场信息”和“当前可用车位”两个界面实际上就是把 2.2 那条统计 SQL 重新跑一次。3.4 用户充值、口令修改和计费标准维护这三个功能看上去独立其实都在同一个原则下凡是涉及金额和密码的操作都不要用简单的 replace 更新整行。充值功能的核心 SQL 是UPDATE users SET balance balance ? WHERE id ?注意用的是相对更新不是先查出余额再加。相对更新在并发下不会互相覆盖而且省一次查询。金额校验放哪一层都行至少要在 DAO 入口判断充值金额为正数。口令修改的核心问题是“旧密码校验”。有些实现直接拿用户输入的新密码 update 覆盖等于一个能登录的人改任何人的密码。至少应该先校验旧密码public boolean changePassword(Integer userId, String oldPwd, String newPwd) { String sql UPDATE users SET password? WHERE id? AND password?; }计费标准管理则是 charger 表的增改操作。需要注意的坑是不要删旧费率再插新费率因为历史停车记录的费用是已经算好的不依赖当前费率改动费率前应确认旧在场车辆在出场时用哪一档费率通常做法是出场时查 is_active1 的新费率这会导致早前进场的老车按新价格结算实不实现“入场锁定费率”就看课设要求了。4. 从资源到运行JDK 1.8 MySQL 8.0.13 的环境配置4.1 版本选择的理由与改动边界这个项目的环境锁定在 JDK 1.8 和 MySQL 8.0.13。JDK 1.8 对 Swing 桌面应用是兼容性最好的版本Java 9 以后 Swing 仍然存在但模块化之后有些项目的构建方式要调整课设资源普遍不做这种适配所以建议直接用 1.8 跑。MySQL 版本其实有一定弹性我用 8.0.3x 也跑通过关键在于 JDBC 驱动和连接串写法。关于驱动注意 lib 目录里如果只有 mysql-connector-java 5.x 的 jar连接 MySQL 8 时会报认证协议不兼容。这个项目表述里明确环境是 MySQL 8.0.13driver 用 8.0.13 对应的 connector 是稳妥的。4.2 导入 sql 脚本四张表一步到位第一步是建库导表。项目压缩包里 sql 目录放的是建表脚本用命令行导入是最直接的不依赖 Navicat 或 MySQL Workbench 的中文字符集设置。mysql -uroot -p CREATE DATABASE IF NOT EXISTS parking DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE parking; SOURCE /your/path/ParkingSwing/sql/parking.sql; SHOW TABLES;执行后应该能看到 users、charger、park、station 四张表。如果 SHOW TABLES 只看到三张或一张都没有十有八九是 SOURCE 时的路径写错了Linux 下相对路径容易出问题用绝对路径更稳。这里顺便说明为什么建库语句要单独跑。有些 sql 脚本本身没有 CREATE DATABASE 语句直接导入到默认库会导致表落在错误位置。先建库、指定字符集再 SOURCE是从根上规避中文乱码。4.3 项目的 JDBC 连接配置改三处就能跑代码里连数据库的位置集中在 DBUtil 类或 db.properties 配置文件里。这个项目是 Eclipse 导出的项目根目录有 .classpath、.project你可以在 src 下找到配置文件用 properties 方式集中管理连接信息。jdbc.urljdbc:mysql://localhost:3306/parking?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltruecharacterEncodingUTF-8 jdbc.usernameroot jdbc.password123456 jdbc.drivercom.mysql.cj.jdbc.Driver连接串里的参数不是随便加的。serverTimezone 必须配MySQL 8 的驱动默认要求指定时区不配的话启动会直接抛异常useSSLfalse 是让本机调试不折腾证书allowPublicKeyRetrievaltrue 解决 caching_sha2_password 认证方式下首次连接的密钥交换问题characterEncodingUTF-8 配合 utf8mb4 库是中文数据不乱码的前提。如果你的 MySQL 密码不是 123456只改 jdbc.password 一处就可以。不要在代码里new String(password)去硬编码优先保证所有连接都走 DBUtil 的同一个配置入口。4.4 导入 IDE 并启动对照运行截图验证界面在 IDEA 里导入这个项目时建议选择直接打开 ParkingSwing 根目录让它识别为 Eclipse 项目。导入成功后src 目录下的结构应该是标准的包名类名组合关键类包括各个 DAO、实体类、登录窗体和主窗体。运行前对照检查一遍检查项说明lib 目录 jar 是否被引入IDEA 里确认 mysql-connector 被加进 Libraries数据库名与 jdbc.url 一致url 里写 parking就建 parking 库账号表是否有初始记录用 SQL 查 users 表没有就手动插入管理员记录账号初始记录是个隐藏坑。如果 sql 脚本里没有 INSERT 管理员数据登录界面会卡在“账号密码错误”。手动跑一条插入命令最简单INSERT INTO users(username, password, role, balance) VALUES(admin, admin123, ADMIN, 0);密码字段如果脚本里存的是密文这条 SQL 就按原表结构调整。首次登录成功后对照资源里的 运行截图 1.png界面布局一致基本就算跑通了。5. 避坑记录跑 Swing 停车场项目最容易踩的五个坑5.1 连接 MySQL 8 时抛出 InvalidConnectionAttributeException上手第一个遇到的就是连接失败异常信息长这样Exception in thread main java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Caused by: java.sql.SQLInvalidAuthorizationSpecException: The server time zone value CST is unrecognized or represents more than one time zone.原因几乎都是 jdbc.url 里没加 serverTimezone。MySQL 8 驱动把时区校验做成了硬要求不像 5.x 版本能容忍缺省值。解决方式是在连接串后面追加serverTimezoneAsia/Shanghai参数值别用 GMT8 这种写法直接写 Asia/Shanghai 最稳妥。5.2 界面中文全部显示为问号现象是按钮文字、表格表头、下拉框里的中文全变成 ??英文字母正常。这个坑分两层建库时字符集不是 utf8mb4或者项目文件本身编码混乱。排查顺序是先看数据库字符集SHOW CREATE DATABASE parking;如果发现 DEFAULT CHARACTER SET 不是 utf8mb4重新按第 4.2 节的方式建库。如果库没问题就看启动参数在 IDEA 的 VM options 里加-Dfile.encodingUTF-8并把项目文件的编码统一设成 UTF-8。注意 Eclipse 导出的项目可能默认是 GBK导入 IDEA 后也要改。5.3 点击查询按钮后窗口假死现象是点击“查询历史信息”或“查看当前可用车位”后整个窗口拖不动、按钮没反应过好几秒才恢复。这是 Swing 单线程模型的典型翻车JDBC 查询是耗时操作如果直接写在按钮事件里它会把事件分发线程 EDT 堵住界面自然就冻结了。解决方式是让耗时查询离开 EDT用一个 SwingWorker 做后台查询查完再回到 EDT 刷新表格new SwingWorkerListParkRecord, Void() { Override protected ListParkRecord doInBackground() throws Exception { return parkDao.findHistoryByUserId(userId); } Override protected void done() { // 在这里更新 JTable } }.execute();课设场景不强制要求但面试官很爱问。这个坑的原理比功能本身更重要。5.4 运行一段时间后报 Too many connections现象是程序刚启动正常点几次查询后所有操作都报“Data source rejected establishment of connection... Too many connections”。原因是 DAO 里无论是 Connection、Statement 还是 ResultSet用完后没关。MySQL 默认连接数有限泄漏到上限就整体罢工。解决方式是把每次取连接、执行、释放收口到一个统一工具类用 try-with-resources 保证关闭try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 处理结果 }这个写法里流式资源自动关闭不需要在 finally 里手工写三行关闭代码。如果项目里已经有 DBUtil建议检查它的 closeQuietly 方法是否真的释放了所有资源。5.5 数据库表字段名与代码里的大小写不一致现象是查询不报错但拿不到数据或者 UPDATE 时报 Unknown column。原因很直接sql 脚本里的字段名是car_no代码里写的是carNoMySQL 在 Windows 下不区分大小写换到 Linux 或云端数据库就严格区分了。解决方式不是去改那么多代码而是固定一个标准写一个 SQL 字段映射的实体类或者干脆所有 SQL 都手写列名不依赖 MyBatis 的自动驼峰转换。课设资源常用原生 JDBC只要 DAO 里每个 ResultSet.getString 后面的列名和表字段保持一致就行。提示如果部署到 Linux 服务器的 MySQL导入 sql 脚本时注意表名也区分大小写我一般把数据库名、表名、字段名统一写成小写下划线风格能在根源上避开这类问题。6. 验证跑通与改造方向从能启动到能交付6.1 一条闭环走查路径项目能启动不等于功能闭环。为了确认系统真的可用建议按下面这条路径完整走一遍步骤操作预期结果1管理员登录进入管理员主界面车场管理菜单可点2注册一个普通用户并充值 50 元users 表 balance 变为 503录入一辆车入场选择车位区域park 多一条 IN 记录可用车位减 14用户视角查询在场信息能看到刚才入场车辆且状态为在场5执行出场弹出本次费用余额按费率扣减6查询历史信息出场记录 fee 有值时间正确显示7修改计费标准后再次入场出场费用按新费率计算如果每一步的结果和预期相符说明数据库链接、表设计、业务逻辑三层是通的。第 5 步尤其要算一遍账用笔算金额和系统弹出金额对得上才不算白跑。6.2 三个值得做的低成本改造第一给可用车位增加定时刷新。现阶段的可用车位是点击查询才更新改造方式是加一个 Swing Timer每隔 30 秒重新跑 2.2 节的统计 SQL 并刷新 JTable让界面更像“实时系统”。第二把计费标准管理改成下拉选择生效。现在的 charger 表如果有多个费率配置出场逻辑要注意读的是哪一档。改造方向是让管理员设置“当前启用费率”标识出场时只查 is_active1 的那条。第三增加出入场小票打印。Swing 里可以用 java.awt.print 包直接打印一张简易单据把车牌、入库时间、出场时间、费用打出来演示效果远好于干巴巴的弹窗。最后说一个我个人的习惯从那以后我拿到任何一个课设项目第一件事一定不是看代码而先看 sql 脚本和连接配置先确认表结构符合业务、连接参数能对上再往下读 DAO 才有意义。这个顺序帮我避开过不少“代码没问题但跑不起来”的局面也希望这次关于停车场管理系统的拆解能帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表