ARTICLE DETAIL

资讯详情

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

Java+Swing+MySQL停车场管理系统:JDBC事务与计费避坑实战

Java+Swing+MySQL停车场管理系统:JDBC事务与计费避坑实战 简介面向Java课程设计和Swing入门者的停车场管理系统完整项目包。系统实现了用户登录注册、浏览车辆信息、添加车辆入场/出场信息、删除与查询车辆信息等基础管理功能完整覆盖从MySQL表设计到JDBC读写、再到Swing窗体交互的典型开发链路。压缩包共49个文件以12个Java源文件、31个编译生成的class文件为主另有project/prefs等Eclipse工程配置、SQL建表脚本、运行截图和JAR依赖包解压后可直接导入开发环境并运行便于对照源码理解各模块逻辑。资源整体约1.96MB轻量紧凑目前已有467人参与学习下载。适合需要完成Java课程设计、复习Swing桌面应用开发或梳理MySQL-Java连接流程的读者借助完整代码与数据库脚本可快速跑通项目并在此基础上扩展功能。1. JavaSwingMySQL停车场管理系统课程设计之外它到底能走到哪一步搜索基于JavaSwingMySQL停车场管理系统跳出来的结果大多是课程设计选题和毕业设计成品。但如果你真打算把这套三件套从demo做成能顶岗用的桌面系统要解决的问题比想象中多得多Swing 的事件模型怎么和 MySQL 事务配合计费逻辑怎么处理跨天和月卡边界上千条出入场记录怎么查询才不卡界面。这篇不铺大而全的架构图就沿着停车场进出场这条主线把建表、连库、界面、计费、排错每个环节的代码、参数和坑一次说透。适合正在做课程设计收尾的人也适合刚入行想拿桌面项目练 JDBC 和 MySQL 基本功的开发者。2. 技术选型为什么是这三件套Swing 的边界与 MySQL 的担当2.1 Swing 不是古董桌面管理系统的真实适用场景先解决一个选型问题做桌面管理系统为什么还选 Swing而不是 JavaFX 或 ElectronSwing 是 JDK 自带的 UI 组件库不需要额外装运行时打包成可执行 jar 后双击就能跑。停车场岗亭的场景很典型一台普通 Windows 电脑、一个显示器、操作员用鼠标键盘点按钮输车牌。这种轻交互、单机部署连一台内网 MySQL的形态Swing 完全撑得住。JavaFX 界面更现代但需要额外配模块在无网环境和老机器上反而容易出幺蛾子。Electron 则是另一个极端一个停车管理系统打包出来几百 MB还要带 Node 运行时杀鸡用牛刀。但 Swing 的边界必须心里有数它不适合做复杂的可视化大屏不适合触摸屏手势操作界面美观度要做自定义 LookAndFeel 才能勉强跟上审美。常见做法是把 Swing 当业务表单壳来用——所有复杂计算全部放 Service 层界面层只负责接收输入、调用方法、把结果填进 JTable。这样后面哪天真要换 JavaFX 或者 Web 管理后台业务逻辑一行都不用动。另一个选型理由是生态兼容性。MySQL 是中小型系统里占比最高的开源数据库教程多、问题好搜配合 JDBC 原生驱动不需要引入 MyBatis 或 Hibernate 这类重型 ORM。对一个表结构固定、查询语句有限的管理系统原生 JDBC 的可控性反而更高。排错时直接看到 SQL 语句本身不用猜框架底层替你做了什么。2.2 计费规则先行先画费率配置再写界面实际做这套系统最容易翻车的不是 Swing 布局而是计费规则。停车场收费模式一般有四种很多场景是组合出现的模式规则示例边界风险按时收费首小时5元之后每小时2元不足1小时按1小时跨天怎么算、每日封顶按次收费进场一次固定10元超时重进怎么判定月卡/年卡套餐时段内不限次到期当天出场怎么处理免费时段前30分钟免费免费时段跨边界怎么算这四种模式叠加时边界问题成倍出现入场在免费时段内、出场在免费时段外费用要按超出的分钟数算入场是当天 23:50、出场是第二天 00:20要不要按两天收费月卡用户月卡当天到期当天出场要不要收临时费。这些问题不提前定义清楚写代码时每遇到一个边界就临时改逻辑改到后面没人敢动。我的顺序是先建费率配置表把每条规则存成数据行再写一个独立的计费服务类输入入场时间、出场时间、车辆类型输出应收金额。Swing 界面只做三件事——收集参数、调用服务、显示结果。最后测试时只需要针对计费服务类写几个边界用例不需要反复点界面省下来的时间比想象中多。2.3 表结构设计六张核心表撑起进出场与计费全流程数据库设计上按六张核心表来落覆盖登录、车辆类型、费率、车位、出入场记录每张表都加 create_time 和 update_time 两个审计字段后面排查数据非常有用。t_user登录账号存用户名、密码BCrypt 哈希、角色。t_vehicle_type车辆类型字典如临时车、月卡车、免费车。t_rate_config费率配置关联车辆类型存计费方式、单价、免费分钟数、每日封顶金额。t_parking_slot车位表存车位编号、所在区域、当前状态空闲/占用状态冗余存储是为了进场时快速分配。t_entry_record入场记录存车牌号、车辆类型、入场时间、车位编号、操作员。t_exit_record出场结算记录关联入场记录存出场时间、应收金额、实收金额、支付方式。索引设计上入场记录的入场时间字段建普通索引车牌号建普通索引。出场结算要按车牌倒查最近一条在场记录报表要按时间范围聚合这两个索引能避免全表扫描。车位表的状态字段也值得建索引停车场规模大时几百上千个车位的查询效率差距非常明显。这六张表的关联关系不复杂核心是出场记录通过入场记录 ID 关联到入场记录再通过车辆类型关联到费率配置。真正要注意的是金额字段一律用 DECIMAL(10,2)不要用 FLOAT 或 DOUBLE——计费精确到分浮点类型在累加时会出现 0.1 加 0.2 不等于 0.3 的问题这在 Java 的 double 里同样存在属于踩过的坑。3. 数据库与工程骨架从建库到登录窗跑通的最小路径3.1 建库建表 SQL字段、索引与初始化数据一次到位先给建库建表脚本按 MySQL 5.7 及以上版本的语法写。字符集统一用 utf8mb4排序规则用 utf8mb4_general_ci避免后面中文乱码。装好 MySQL 之后直接用命令行或 Navicat 执行这段 SQL 就能把库建起来不需要额外安装其他组件。CREATE DATABASE IF NOT EXISTS parking_system DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE parking_system; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT BCrypt哈希后的密文, role VARCHAR(20) NOT NULL DEFAULT OPERATOR, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB; INSERT INTO t_user (username, password, role) VALUES (admin, $2a$10$...哈希值..., ADMIN);这里的字符集参数是关键。utf8mb4 是 MySQL 里真正完整支持中文包括生僻字和 emoji的字符集utf8 在 MySQL 5.7 里实际是 utf8mb3遇到特殊字符会报 Incorrect string value 错误。密码字段建议存 BCrypt 哈希而不是 MD5MD5 用彩虹表很快就能撞出来属于 Java 面试里常被追问的安全点实际项目中至少要用 BCrypt 或 PBKDF2。CREATE TABLE t_vehicle_type ( id INT PRIMARY KEY AUTO_INCREMENT, type_name VARCHAR(20) NOT NULL UNIQUE, description VARCHAR(100) ) ENGINEInnoDB; INSERT INTO t_vehicle_type (type_name, description) VALUES (临时车, 按次或按时收费), (月卡车, 按套餐时段不限次), (免费车, 内部车辆免费通行);CREATE TABLE t_rate_config ( id INT PRIMARY KEY AUTO_INCREMENT, vehicle_type_id INT NOT NULL, billing_mode VARCHAR(20) NOT NULL COMMENT HOURLY/MONTHLY/FREE, unit_price DECIMAL(10,2) NOT NULL DEFAULT 0, free_minutes INT NOT NULL DEFAULT 0, daily_cap DECIMAL(10,2) DEFAULT NULL, FOREIGN KEY (vehicle_type_id) REFERENCES t_vehicle_type(id) ) ENGINEInnoDB;费率配置表是计费逻辑的核心表。HOURLY 模式配合 unit_price 表示每小时费用free_minutes 表示免费分钟数daily_cap 是每日封顶金额NULL 表示不封顶。MONTHLY 模式不参与单次计费只需要在程序里判断车辆是否在套餐有效期内。把规则数据化之后改费率不用改代码改一条数据库记录就行这是这套设计里最值钱的地方。CREATE TABLE t_parking_slot ( id INT PRIMARY KEY AUTO_INCREMENT, slot_no VARCHAR(20) NOT NULL UNIQUE, area VARCHAR(50) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1占用 ) ENGINEInnoDB; CREATE TABLE t_entry_record ( id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(20) NOT NULL, vehicle_type_id INT NOT NULL, slot_id INT NOT NULL, entry_time DATETIME NOT NULL, operator VARCHAR(50), status TINYINT NOT NULL DEFAULT 0 COMMENT 0在场 1已出场, INDEX idx_plate (plate_no), INDEX idx_entry_time (entry_time), FOREIGN KEY (vehicle_type_id) REFERENCES t_vehicle_type(id) ) ENGINEInnoDB; CREATE TABLE t_exit_record ( id INT PRIMARY KEY AUTO_INCREMENT, entry_record_id INT NOT NULL, exit_time DATETIME NOT NULL, amount DECIMAL(10,2) NOT NULL DEFAULT 0, actual_amount DECIMAL(10,2) NOT NULL DEFAULT 0, pay_method VARCHAR(20) DEFAULT CASH, operator VARCHAR(50), FOREIGN KEY (entry_record_id) REFERENCES t_entry_record(id) ) ENGINEInnoDB;索引点再强调一下plate_no 和 entry_time 单独建索引出场时要按车牌倒查在场记录报表要按时间范围统计没有这两个索引数据量到几千条时查询耗时就会明显上升。entry_record 的 status 字段也建议建索引查在场车辆数时走索引比全表扫快很多。这里建的是普通索引不是唯一索引因为同一辆车可以多次进场只是同一时刻只能有一条 status0 的记录这个约束放在业务层判断。3.2 JDBC 连接封装为什么这里不需要 MyBatis对这类表结构固定、查询语句有限的桌面系统用原生 JDBC 加一个轻量工具类就够了不引 MyBatis。理由很直接连接串、账号、密码全部放在一个配置文件里工具类负责加载驱动、建立连接、关闭资源业务类只关心 SQL 和参数代码量比框架少而且更好排查。public class JdbcUtil { private static String url; private static String username; private static String password; static { try (InputStream in JdbcUtil.class.getResourceAsStream(/db.properties)) { Properties props new Properties(); props.load(in); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); Class.forName(com.mysql.cj.jdbc.Driver); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }静态代码块里加载配置和驱动类第一次被引用时只执行一次。配置文件放在 resources 目录下JdbcUtil.class.getResourceAsStream(/db.properties) 从 classpath 根目录读取打包成 jar 后也能正常工作。这里依赖 JDK 自带的 java.util.Properties不需要引入第三方依赖。要提醒一点运行这套程序需要先配好 Java 环境变量确保 java 命令能直接用不然连驱动类都加载不了报 ClassNotFoundException。连接串建议这样写把参数显式声明避免踩默认值的坑jdbc.urljdbc:mysql://127.0.0.1:3306/parking_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue逐个说明这些参数characterEncodingutf8 保证中文字符在连接层不被转乱serverTimezoneAsia/Shanghai 解决 MySQL 驱动 8.x 在 JDK 8 以上时出现的时区报错 The server time zone valueuseSSLfalse 是因为本地开发环境不需要 SSL 加密MySQL 5.7 默认开了 SSL 选项不关会给出 warning 甚至连接失败allowPublicKeyRetrievaltrue 是配合 MySQL 8 的 caching_sha2_password 认证插件连接时用明文密码传输需要获取服务器公钥。关连接的顺序也有讲究ResultSet、Statement、Connection 要逆序关闭而且必须放在 finally 里。工具类的 close 方法把空判断也做了业务层一行 JdbcUtil.close(conn, stmt, rs) 收尾不会因为某次查询异常而漏关连接。连接泄漏是最隐蔽的性能杀手后面避坑章节单独讲。3.3 登录窗口到主窗体卡片布局让界面切换不再重启登录窗体是 Swing 程序最直观的一层。常见做法是先弹登录窗体验证通过后 dispose 掉登录窗体再 new 主窗体。但这类系统不止一个窗口主窗体内部还有面板切换用 JFrame 加 JPanel 卡片布局来管理避免多个 JFrame 互相叠置的混乱。public class MainFrame extends JFrame { private CardLayout cardLayout; private JPanel cardPanel; public MainFrame() { setTitle(停车场管理系统); setSize(900, 600); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cardLayout new CardLayout(); cardPanel new JPanel(cardLayout); cardPanel.add(new EntryPanel(), ENTRY); cardPanel.add(new ExitPanel(), EXIT); cardPanel.add(new ReportPanel(), REPORT); add(cardPanel, BorderLayout.CENTER); addMenuBar(); } private void addMenuBar() { JMenuBar bar new JMenuBar(); JMenu menu new JMenu(业务操作); JMenuItem entryItem new JMenuItem(进场登记); JMenuItem exitItem new JMenuItem(出场结算); entryItem.addActionListener(e - cardLayout.show(cardPanel, ENTRY)); exitItem.addActionListener(e - cardLayout.show(cardPanel, EXIT)); menu.add(entryItem); menu.add(exitItem); bar.add(menu); setJMenuBar(bar); } }CardLayout 的好处是面板实例常驻内存切换不重建、不丢状态。比如进场面板里正在录的车牌信息切到出场面板再切回来还在那里。如果用多个 JFrame 的方式这些状态就要靠全局变量或者重新查库找回体验差很多。登录验证里有一点要提醒不要把 SQL 拼成字符串正确写法是用 PreparedStatement 占位符防止 SQL 注入。Java 基础里 PreparedStatement 为什么能防注入属于高频面试题实际项目里这也是底线要求用户输入的任何字符串只能当参数不能当 SQL 片段。4. 核心业务落地进场、出场计费与统计报表4.1 进场登记车牌录入与车位联动进场登记的流程不复杂输入车牌选择车辆类型系统自动分配空闲车位写入入场记录把车位状态改成占用。推荐的做法是先查空闲车位列表下拉让操作员选同时车位状态在选中的瞬间更新防止两个岗亭同时分配同一个车位。public int entryParking(String plateNo, int vehicleTypeId, int slotId) { String checkSql SELECT id FROM t_entry_record WHERE plate_no ? AND status 0; String entrySql INSERT INTO t_entry_record (plate_no, vehicle_type_id, slot_id, entry_time, status) VALUES (?, ?, ?, NOW(), 0); String updateSlotSql UPDATE t_parking_slot SET status 1 WHERE id ? AND status 0; try (Connection conn JdbcUtil.getConnection(); PreparedStatement checkStmt conn.prepareStatement(checkSql)) { checkStmt.setString(1, plateNo); ResultSet rs checkStmt.executeQuery(); if (rs.next()) { return -1; // 该车牌已在场内不能重复进场 } conn.setAutoCommit(false); try (PreparedStatement entryStmt conn.prepareStatement(entrySql, Statement.RETURN_GENERATED_KEYS); PreparedStatement updateStmt conn.prepareStatement(updateSlotSql)) { entryStmt.setString(1, plateNo); entryStmt.setInt(2, vehicleTypeId); entryStmt.setInt(3, slotId); entryStmt.executeUpdate(); updateStmt.setInt(1, slotId); int rows updateStmt.executeUpdate(); if (rows 0) { conn.rollback(); return -2; // 车位已被占用 } conn.commit(); return 1; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } } catch (SQLException e) { throw new RuntimeException(进场登记失败, e); } }这里核心是把两件事放进同一个事务写入入场记录、更新车位状态。如果先插入记录再更新车位更新失败时记录已经写进去了车却没进场数据就对不上。MySQL 事务处理在 InnoDB 下默认是自动提交的这里手动 setAutoCommit(false)两个 SQL 一起成功或一起回滚。updateSlotSql 里带了 status 0 条件作用是乐观锁两个岗亭同时操作同一个车位时只有第一个能更新成功第二个影响行数为 0程序返回车位已被占用。这是用一次条件更新代替显式行锁的常见做法并发量不大的桌面系统足够用比 SELECT ... FOR UPDATE 轻量很多。4.2 出场结算计时计费与事务边界出场结算是整个系统最核心的方法。流程是输入车牌或入场单号查出最近一条 status0 的入场记录根据车辆类型取费率计算应收金额写入出场记录把入场记录 status 改成 1释放车位。public BigDecimal calculateFee(int vehicleTypeId, LocalDateTime entryTime, LocalDateTime exitTime) { // 实际应从费率表读取配置这里以参数说明为主 // modeHOURLY, unitPrice5.00, freeMinutes30, dailyCap50.00 long minutes Duration.between(entryTime, exitTime).toMinutes(); long billableMinutes minutes - freeMinutes; // 先抵扣免费时段 if (billableMinutes 0) { return BigDecimal.ZERO; } BigDecimal hours BigDecimal.valueOf((billableMinutes 59) / 60); // 不足1小时按1小时 BigDecimal amount hours.multiply(unitPrice); if (dailyCap ! null amount.compareTo(dailyCap) 0) { amount dailyCap; } return amount.setScale(2, RoundingMode.HALF_UP); }计费规则里两个参数最容易错。第一不足一小时按一小时用 (billableMinutes 59) / 60 整数除法实现向上取整如果用 double 除再强转会有精度隐患。第二涉及金额的乘法BigDecimal 一定要用 String 构造参数new BigDecimal(5.0) 会把二进制浮点误差带进来new BigDecimal(5.00) 才是干净的十进制表达。金额保留两位用 setScale舍入方式用 HALF_UP这是财务结算的标准做法。出场结算同样要包事务插入出场记录、更新入场记录状态、更新车位状态三个操作一个事务。别小看这个事务边界如果插入出场记录成功但更新入场状态失败下一次同一个车牌再出场时会查出两条在场记录系统直接乱掉。结算面板里把实收金额和找零做到同一行方便现金收费场景支付方式下拉里放现金、微信、支付宝。对于月卡车辆结算前先查套餐有效期在有效期内直接生成金额为 0 的出场记录不走按时计费。4.3 统计报表用 SQL 聚合代替内存循环报表模块最容易写歪。很多初学者会把数据全部查出来然后在 Java 里用 for 循环分组求和数据量一上千就明显卡顿。正确做法是让 MySQL 先聚合Swing 只负责显示。SELECT DATE_FORMAT(entry_time, %Y-%m-%d) AS day, COUNT(*) AS entry_count, SUM(CASE WHEN t2.amount IS NOT NULL THEN 1 ELSE 0 END) AS exit_count, COALESCE(SUM(t2.amount), 0) AS total_amount FROM t_entry_record t1 LEFT JOIN t_exit_record t2 ON t1.id t2.entry_record_id WHERE t1.entry_time ? AND t1.entry_time ? GROUP BY DATE_FORMAT(entry_time, %Y-%m-%d) ORDER BY day;这段 SQL 把日报表的统计一次算完。LEFT JOIN 保证凌晨进场还没出场的记录也能出现在计数里COALESCE 把 NULL 金额补成 0。GROUP BY 按天聚合SUM(CASE WHEN ...) 是 MySQL 里做条件计数的常用写法比先查出来再 if 判断快一个数量级。报表查询结果用 JTable 填充DefaultTableModel 直接 addRow上千行渲染没问题。但要注意别在事件分发线程里做这种时间跨度大的聚合查询卡界面问题后面避坑章会展开。日报之外车位利用率和月卡到期提醒也可以加到这个模块里。车位利用率就是 SUM(status) 除以 COUNT(*) 乘 100一条 SQL 就能出结果。月卡到期提醒查 t_vehicle_type 关联的月卡车辆入场记录筛选出场时间在七天内的JTable 里标黄显示。这些功能代码量不大但能让这个系统从能交作业变成能真用。5. Swing MySQL 避坑指南四条血泪经验5.1 界面假死点击查询后窗口拖不动现象点击查询按钮后整个窗口白屏转圈鼠标拖不动只能强制结束进程。原因把耗时 SQL 直接写在 ActionListener 里查询期间占住了事件分发线程EDT。Swing 是单线程模型所有界面刷新都要经过 EDT查询一执行整个窗口就假死。解决用 SwingWorker 把耗时操作放到后台线程完成后回到 EDT 更新 JTable。SwingWorkerListReportRow, Void worker new SwingWorker() { Override protected ListReportRow doInBackground() { return reportService.queryDailyReport(start, end); } Override protected void done() { try { ListReportRow rows get(); // 在这里更新JTable的TableModel } catch (Exception e) { JOptionPane.showMessageDialog(MainFrame.this, 查询失败: e.getMessage()); } } }; worker.execute();SwingWorker 的两个核心方法要分清doInBackground 在后台线程执行done 在 EDT 上回调。get() 会拿到后台线程的返回值如果后台抛异常get 会把它包在 ExecutionException 里重新抛出来所以必须 try-catch 后弹提示不能让异常无声吞掉。这是 Java 多线程与 Swing 集成的标准解法也是 Java 基础里线程协作的一个典型落地场景。5.2 中文乱码车牌显示成问号现象界面输入中文车牌后往 MySQL 写入再查出来显示成 ????。原因两层问题叠加。第一层是数据库连接串没带 characterEncodingutf8第二层是建库时默认字符集不是 utf8mb4。解决连接串补上 useUnicodetruecharacterEncodingutf8建库语句显式指定 DEFAULT CHARACTER SET utf8mb4。另外 MySQL 5.7 的 utf8 实际是 utf8mb3只支持 BMP 字符如果车牌或备注里要支持生僻字必须用 utf8mb4。改完这两个地方重启程序再测试一次进出场全流程。还有一个隐蔽点Windows 下的命令行客户端如果代码页不是 65001手工往表里插中文也会乱但这个问题只影响调试不影响 Java 程序读写。5.3 跨天计费金额算成负数被客户投诉现象23:50 进场的车00:20 出场理论上停了 30 分钟收费系统却报出负数金额或者直接报错。原因计费方法只算了进场当天的时长用 出场时间减去当天 0 点 再减去 入场时间到当天 0 点的差值日期切换后差值变成负值。解决计算费用前统一用 LocalDateTime 做差值Duration.between 计算的是绝对时长不会为负。如果业务要求跨天按两天收费那就先判断 entryTime.toLocalDate() 和 exitTime.toLocalDate() 是否不同再按每天分别调用计费规则。我在计费服务里加了一个 isCrossDay 方法返回值直接绑到出场界面的一个红字标签上操作员一眼就能看到这单是跨天单。这个坑属于业务规则问题而不是技术问题写代码前和停车场管理员确认清楚最省事。5.4 连接泄漏系统跑几天后报 Too many connections现象程序正常运行两三天后突然所有查询都报 Too many connections重启后恢复过几天又犯。原因某处查询异常时没有调用 JdbcUtil.closeConnection 一直占用不释放。MySQL 默认 max_connections 是 151桌面系统同时打开的连接数不多但泄漏的连接日积月累最终把上限撑爆。解决一是强制所有 JDBC 操作都写在 try-with-resources 里代码块结束自动关闭二是做一次全项目排查搜所有 DriverManager.getConnection 的调用点确认每条路径都有 finally 或 try-with-resources 收口。try-with-resources 是 Java 7 引入的语法也是面试里常问到的点实际项目里它就是连接泄漏最有效的解药。业务量再大一点可以用 HikariCP 连接池兜底但小型桌面系统原生 JDBC 够用重点是关闭要严谨不要依赖什么玄学重启。6. 从能跑到能用初始化数据校验与备份的最后一公里系统跑通当天只是开始真正让它扛住真实停车场的一天还差几件小事。第一件是初始化数据校验。每次启动程序时检查 t_rate_config 表里是不是有可用的费率配置车位表里有没有车位一张表一条 SQL 的事。没有配置就弹警告框提示管理员先去系统设置里补数据而不是让操作员进场时才发现费率是零。第二件是操作日志落盘。每次进出场、结算、修改费率都往一个本地日志文件里追加一行记录用最简单的 java.nio.file.Files.write 带 APPEND 参数就行。数据库里只能看到最终状态日志能告诉你这个状态是怎么一步步变成的出问题排查时是后悔药级别的存在。第三件是 MySQL 数据备份。桌面系统一般没有专职 DBA我在主窗体加了一个备份数据按钮内部调用 mysqldump 命令把整个 parking_system 库导出到带时间戳的 SQL 文件操作员每天下班点一下比任何云备份都可靠。还有一个小习惯帮我省了很多事把 JTable 的列宽、窗体的位置大小这些界面参数也存到数据库配置表里而不是写死在代码里。操作员觉得列太窄直接在界面上拉一下下次启动还是这个宽度不用重新编译。我自己做这套系统最大的教训就是一开始把精力全花在界面美化上实际上真正撑住场面的是干净的 SQL、明确的事务边界、严谨的资源关闭这三件事。界面丑一点操作员一周就习惯了数据乱了一个月都补不回来。希望帮到你。本文还有配套的精品资源点击获取
返回列表