尧图精选

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

🕒 发布时间:2026/10/1 5:07:24 📁 来源:尧图网络
简介这是一份基于 Java Swing 与 MySQL 8.0 构建的停车场管理系统源码包适合 Java 初学者、课程设计或毕业设计参考。系统按用户与管理员双角色设计用户端支持登录、计费标准查询、在场信息与个人历史记录查看管理端覆盖车辆入场出场、用户注册修改充值、计费标准维护及管理员账户管理功能划分清晰便于二次开发。压缩包共 80 个文件包含 25 个 Java 源文件、47 个编译后的 class 文件、SQL 数据库脚本及项目配置信息整体仅 2.11MB结构紧凑。数据库层提供 charger、park、station、users 四张表配合 JDK 1.8 与 MySQL 8.0.13 环境即可运行。资源内附运行截图方便对照界面理解代码逻辑。目前已有 728 人学习下载适合需要快速上手桌面数据库应用开发的读者。1. 基于JavaSwingmysql的停车场管理系统课程设计还是小规模商用的起点每到课程设计季总有人拿到这样一个题目用Java Swing做界面MySQL做数据存储实现一个停车场管理系统。这个组合看起来不够“现代”却是桌面应用教学里最经典的搭配之一。它要解决的问题很实在车辆进场登记、车位占用管理、出场计费、历史记录查询和营收统计覆盖了数据库增删改查和一个小型业务闭环的全部关键点。对正在找 Java 课程设计案例源码、或者想快速交付一个完整 JavaWeb 项目的人来说这个题目是一块很好的练手阵地。反直觉的地方在于Swing 虽然老却是能把 MySQL 操作变成看得见、点得着的东西的最快方式比一上来就上 Spring Boot Vue 更适合短周期交付和答辩演示。2. 从需求到表结构停车场管理系统的数据库设计2.1 功能边界一个最小可用系统要管哪些事一个停车场管理系统核心就三件事车进来、车出去、算钱。周边再扩展出车位状态展示、进场记录查询、营收统计、管理员登录。不要一开始就想着做月卡、预约、会员折扣先把“临时车按时间计费”这条主线跑通再加花活也不迟。我把功能拆成五个模块登录模块管理员用账号密码进入系统车位管理初始化车位编号并显示占用或空闲入场登记录入车牌号、选择空闲车位、写入进场时间出场结算根据车牌查进场记录、算时长和金额、回填离场时间记录查询按车牌和日期查历史流水还能按天汇总营收。这样拆完之后数据需求非常清晰一张管理员表、一张车位表、一张停车记录表。停车记录是整个系统的核心流水进场和出场的信息都沉淀在这张表里后面做报表、对账、查询全部围着它转。2.2 三张核心表的字段设计与建表SQL三张表的字段设计我一般遵循“够用且不过度设计”的原则。管理员表只存 id、用户名、密码车位表存 id、车位编号、状态和备注停车记录表存 id、车牌、车位关联、进场时间、离场时间和费用。密码不要存明文至少用 MD5 加盐虽然课程设计不强制但答辩时是加分项。建表 SQL 如下CREATE DATABASE IF NOT EXISTS parking_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE parking_system; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT MD5后的密文 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE parking_space ( id INT PRIMARY KEY AUTO_INCREMENT, space_no VARCHAR(10) NOT NULL UNIQUE, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1占用, remark VARCHAR(100) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE parking_record ( id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(20) NOT NULL, space_id INT NOT NULL, entry_time DATETIME NOT NULL, exit_time DATETIME NULL, fee DECIMAL(10,2) DEFAULT NULL, INDEX idx_plate_no (plate_no), INDEX idx_entry_time (entry_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明status 用 TINYINT 而不是 VARCHAR是为了让 SQL 条件简洁Java 侧映射成 int 也更省事。fee 用 DECIMAL(10,2) 而不是 float 或 double因为浮点数在金额计算时会出 0.30000000000000004 这类精度问题这在 java 基础面试题里也是常考的点。索引方面plate_no 和 entry_time 是查询高频字段分别建普通索引就够对于这个量级的系统联合索引属于过度设计。参数说明字符集统一用 utf8mb4虽然停车场业务只有中文和数字但 utf8mb4 能兼容 emoji 和生僻字避免后期接入其他数据源时出现乱码。DATETIME 类型不带时区Java 侧传参时用字符串或 java.sql.Timestamp 都行关键是要统一格式后面会专门讲时区坑。2.3 计费规则怎么落到数据库里费率表还是存储过程计费规则一般是首小时 X 元之后每小时 Y 元不足一小时按一小时算或者严苛一点按半小时计费。有人建议把计费写成 MySQL 存储过程数据库算好了直接返回金额。我个人的习惯是不用存储过程原因有三第一课程设计场景下费率基本是写死的不需要动态配置第二计费逻辑放在 Java Service 层方便写单元测试答辩时也能直接讲清楚算法第三存储过程对新手不友好调试起来像个黑匣子出问题难定位。但如果你想在系统里体现“可配置”的设计可以在数据库加一张费率表字段为 id、规则名、首小时金额、续小时金额、是否启用。Java 启动时加载到内存出场结算时查一下改费率不用改代码。这个方案在真实停车场系统里很常见加分点在于业务和代码解耦。我一般会把费率值抽成常量放在一个类里public class FeeConfig { // 单位元 public static final double FIRST_HOUR_PRICE 5.0; public static final double ADDITIONAL_HOUR_PRICE 3.0; // 是否按半小时计费false 表示不足一小时按一小时计费 public static final boolean BILL_BY_HALF_HOUR false; }这样参数改动一目了然也方便在测试时快速切换不同费率来验证计费边界。2.4 为什么不用外键和触发器很多教程会给表加外键约束、写触发器在入场时自动更新车位状态。我的建议是课程设计里尽量不用。原因不是外键不好而是这个项目的数据量级和并发量级根本用不到反而会带来麻烦。外键约束要求插入停车记录时 space_id 必须存在于车位表这没问题但删除车位、初始化测试数据时会遇到顺序问题。触发器更像一个黑匣子车位状态是否被自动更新你只能去看数据库日志排错成本高。更实在的做法是在 Java 业务代码里手动控制事务先更新车位 status 为 1再插入停车记录两个操作要么都成功要么都回滚。这样逻辑透明答辩时你能一句话说清楚且不用依赖数据库端的隐式操作。3. 搭建Swingmysql项目骨架从连接池到登录窗口3.1 项目结构分层避免把SQL写在按钮里我见过太多课程设计源码一个 JFrame 里塞了上千行按钮点击事件里直接写 Connection 和 Statement。能跑但改需求时想哭。推荐按分层思想组织包结构但不引入 Spring 等框架纯 Java 实现com.example.parking ├── Main.java // 启动入口 ├── view/ // Swing界面 │ ├── LoginFrame.java │ ├── MainFrame.java │ └── RecordQueryPanel.java ├── service/ // 业务逻辑 │ ├── LoginService.java │ ├── ParkingService.java │ └── FeeCalculator.java ├── dao/ // 数据访问 │ ├── AdminDao.java │ ├── SpaceDao.java │ └── RecordDao.java ├── util/ // 工具 │ ├── DBUtil.java // 连接池封装 │ └── DateUtil.java └── model/ // 实体 ├── Admin.java ├── ParkingSpace.java └── ParkingRecord.java这个结构对新手友好view 只负责显示和收集输入service 做业务判断dao 只做 SQL 操作。好处是以后换成 MyBatis 或者改成 Spring Boot 接口时service 和 model 可以原封不动搬走只需要重写 dao 层。这也是 Java 面试题里常问的分层思想你说得出理由就是加分项。不要贪图省事把 SQL 写在界面类里否则后面做报表或者换数据库时会让你怀疑人生。3.2 用Druid连接池代替DriverManager直连不要用 DriverManager.getConnection() 写死在每个方法里每次连接都创建物理连接数据量大时数据库会报 Too many connectionsSwing 界面操作稍微频繁一点连接浪费非常明显。用连接池是常规操作Druid 对新手友好配置简单还自带监控页。如果你的项目用 Maven 管理添加依赖很直接dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency注意MySQL 8.0 以上要使用 com.mysql.cj.jdbc.Driver老教程里写的 com.mysql.jdbc.Driver 已经在新的 connector 中移除了这是最常见的报错来源之一。然后写连接池工具类package com.example.parking.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.util.Properties; public class DBUtil { private static DataSource dataSource; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(初始化数据库连接池失败: e.getMessage()); } } public static DataSource getDataSource() { return dataSource; } }逻辑说明这个类在静态代码块里加载 druid.properties 配置文件并创建连接池整个 JVM 生命周期只有一个 DataSource 实例所有 DAO 都从它拿连接。这种写法避免了每个方法都 new 工具类的浪费也方便以后对连接池做监控。静态代码块里的异常用 ExceptionInInitializerError 抛出让程序启动时第一时间暴露配置问题而不是运行到一半才报连接失败。参数说明druid.properties 文件放在 src/main/resources 下一个典型配置如下driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/parking_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot passwordyour_password initialSize5 maxActive10 maxWait3000这些参数里最容易踩坑的是 useSSL 和 serverTimezone。MySQL 8 默认开了 SSL但本机开发用不到useSSLfalse 可以避免 SSL 握手报错。serverTimezoneAsia/Shanghai 是给 JDBC 驱动用的没有它查出来的时间会差 8 小时。allowPublicKeyRetrievaltrue 是 MySQL 8 的 caching_sha2_password 认证下必须的不然连不上。Druid 的 initialSize 是启动时初始连接数maxActive 是最大连接数maxWait 是获取连接的超时毫秒数课程设计用默认这几个就够不需要调复杂参数。3.3 登录模块的最小实现校验、跳转、提示登录模块是系统的门面也能把 Swing 的最基本套路走一遍JFrame、JTextField、JPasswordField、JButton、事件监听、对话框提示。写代码时千万不要在 ActionListener 里直接查数据库而是调用 Service 层。我一般这样写public class LoginFrame extends JFrame { private JTextField usernameField; private JPasswordField passwordField; private LoginService loginService new LoginService(); public LoginFrame() { setTitle(停车场管理系统登录); setSize(320, 180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); // 布局和组件初始化省略关注核心逻辑 } private void onLoginClick() { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } boolean ok loginService.checkLogin(username, password); if (ok) { new MainFrame().setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); passwordField.setText(); } } }逻辑说明JPasswordField.getPassword() 返回的是 char[] 而不是 String这是 Java 基础里常考的“为什么密码用 char[] 好”因为 String 是不可变对象会留在内存里char[] 用完后可以手动置空。登录成功后再 new MainFrame() 并 dispose() 掉登录窗口这是 Swing 窗口切换的标准写法不会造成窗口堆积。在 LoginService 里查询用 PreparedStatement 防止 SQL 注入public boolean checkLogin(String username, String password) { String md5Password MD5Util.md5(password); String sql SELECT COUNT(*) FROM admin WHERE username ? AND password ?; try (Connection conn DBUtil.getDataSource().getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Password); try (ResultSet rs ps.executeQuery()) { rs.next(); return rs.getInt(1) 0; } } catch (SQLException e) { e.printStackTrace(); return false; } }这里用 try-with-resources 自动关闭连接实际上是把连接归还给连接池而不是物理关闭这是连接池和 DriverManager 直连最大的区别。MD5 虽然不适合高强度安全场景但对课程设计够用了答辩时可以说“实际生产会用 BCrypt这里为了简化用 MD5 加盐”。管理员账号初始化时用同一套 MD5 算法把明文转成密文插入到 admin 表别把明文直接写进去否则演示时打开数据库就被看穿了。4. 核心业务编码车辆入场、出场计费与车位看板4.1 入场逻辑占车位与写记录的顺序入场业务是用户输入车牌号选择一个空闲车位点击入场按钮。系统要做两件事把 parking_space 的 status 更新为 1向 parking_record 插入一条进场记录。这个顺序不能反而且必须放在同一个事务里。如果先插记录再改车位插入后程序崩溃会导致有记录但车位没被占用下一次同一车位还能进来数据就乱了。反过来先占车位再插记录即使插入失败事务回滚也能让车位恢复空闲。代码用 Connection 手动控制事务public boolean entry(String plateNo, int spaceId) throws SQLException { String updateSpace UPDATE parking_space SET status 1 WHERE id ? AND status 0; String insertRecord INSERT INTO parking_record (plate_no, space_id, entry_time) VALUES (?, ?, NOW()); try (Connection conn DBUtil.getDataSource().getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(updateSpace); PreparedStatement ps2 conn.prepareStatement(insertRecord)) { ps1.setInt(1, spaceId); int rows ps1.executeUpdate(); if (rows 0) { conn.rollback(); return false; // 车位已被占用或不存在 } ps2.setString(1, plateNo); ps2.setInt(2, spaceId); ps2.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } } }注意 UPDATE 条件里的 AND status0这个条件让数据库在更新前检查车位是否空闲相当于用乐观锁的思路解决并发问题。如果用“先 SELECT 查 status再 UPDATE”的做法两台电脑同时入场同一个车位时都会查到空闲然后都会更新成功就爆了。加了这个条件第二次更新受影响行数为 0业务上做回滚即可。这就是 java 怎么保证数据一致性这个面试问题在业务代码里的实际落点。4.2 出场计费时长计算与金额精度出场逻辑是输入车牌号查到未离场的记录显示入场时间和当前时间计算费用确认收款后回填 exit_time 和 fee并把车位置空。时长计算我推荐用毫秒差再手动换算而不是用 SQL 的 TIMESTAMPDIFF。原因还是业务规则要收在 Java 里。比如“不足一小时按一小时”用 SQL 算完还得在 Java 里二次修正不如直接算public static long calculateMinutes(Date entryTime, Date exitTime) { long diffMillis exitTime.getTime() - entryTime.getTime(); return (long) Math.ceil(diffMillis / (60.0 * 1000)); } public static double calculateFee(long minutes) { if (minutes 60) { return FeeConfig.FIRST_HOUR_PRICE; } long extraHours (minutes - 60 59) / 60; // 向上取整到小时 return FeeConfig.FIRST_HOUR_PRICE extraHours * FeeConfig.ADDITIONAL_HOUR_PRICE; }逻辑说明Math.ceil(diffMillis / 60000.0) 得到分钟数向上取整解决“停了 1 分 01 秒也按 1 分钟算”的问题。extraHours 使用 (minutes - 60 59) / 60这是整数向上取整的经典写法避免使用 Math.ceil 再强转。金额用 double 参与运算最后在写入数据库时用 BigDecimal 做四舍五入。这里有个精度坑如果用 float 存金额1.5 元每小时停 3 小时计算 3 * 1.5结果可能是 4.5 但打印出来是 4.5000001。所以数据库字段用 DECIMAL(10,2)Java 侧在入库前把 double 转成 BigDecimalBigDecimal feeDecimal BigDecimal.valueOf(actualFee) .setScale(2, RoundingMode.HALF_UP); record.setFee(feeDecimal);参数说明RoundingMode.HALF_UP 就是四舍五入也是日常对账最常用的舍入模式不要用 BigDecimal 的老常量 BigDecimal.ROUND_HALF_UP那已经标记过时了。计费时如果配置了 BILL_BY_HALF_HOUR把 extraHours 的计算基准从 60 分钟改成 30 分钟即可逻辑不变。4.3 刷新车位看板Swing的事件分发线程车位看板是系统最直观的界面常见做法是用一个 JPanel每个车位一个按钮空闲绿色占用红色。要做到入场或出场后界面立即刷新很多人会直接在业务代码里调用刷新方法界面却卡死或者不更新根本原因是触碰了 Swing 线程模型。Swing 不是线程安全的所有 UI 更新必须在事件分发线程EDT上执行。你的业务操作如果耗时较长比如上万的记录查询就不该直接在主线程里执行。最简单的做法是用 SwingUtilities.invokeLater 把刷新动作丢回 EDTprivate void refreshSpacePanel() { // 从DAO查出所有车位状态比如 ListParkingSpace spaces ListParkingSpace spaces spaceDao.findAll(); SwingUtilities.invokeLater(() - { panel.removeAll(); for (ParkingSpace space : spaces) { JButton btn new JButton(space.getSpaceNo()); btn.setBackground(space.getStatus() 1 ? Color.RED : Color.GREEN); panel.add(btn); } panel.revalidate(); panel.repaint(); }); }注意查询数据库的操作放在 invokeLater 外面只把 UI 刷新放进去。如果你把查询也放进去数据库响应慢时界面照样卡。正确姿势是耗时操作放普通线程结果回来后用 invokeLater 切回 EDT 更新界面。这是 Swing 面试里“界面卡死怎么排查”的标答。revalidate 和 repaint 都要调用前者通知布局管理器重新计算组件位置后者触发重绘漏一个就会出现“按钮明明 add 了却不显示”的问题。5. 停车场管理系统上线前的避坑清单五个真实翻车点5.1 MySQL连接失败时区、SSL与socket路径现象运行程序时控制台报错java.sql.SQLException: The server time zone value CST is unrecognized或者Communications link failure。后者如果出现在 Linux 下还可能是Cant connect to local MySQL server through socket /tmp/mysql.sock这是 mysql 命令行工具走 socket 连接失败和 Java 程序报的 TCP 连接失败不一样排查方向完全不同。原因MySQL 8 的驱动强制校验时区SSL 握手在某些版本 JDK 下会失败MySQL 服务没启动或端口被占用。socket 报错通常是 mysql 客户端连本地服务用的 Unix socket 文件路径不对Java 远程连接用的是 TCP 3306 端口两者要分开看。解决第一确认 MySQL 服务已启动Windows 看服务列表Linux 用 systemctl status mysqld。第二JDBC URL 按前面写的加上serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue。第三如果还连不上用 Navicat 或 MySQL Workbench 先测一次排除网络和账号问题。有个经典坑是 root 账号的 host 是 localhost而 Java 程序用 127.0.0.1 连接MySQL 里 localhost 和 127.0.0.1 是两个账号条目会报 Access denied这时要么建一个 host 为 127.0.0.1 的账号要么 URL 写成 localhost。5.2 中文乱码从URL到表的字符集全线对齐现象插入的车牌号或管理员姓名查询出来全是问号或者“???”。数据库里查出来是正常的但 Java 界面显示乱码反过来界面输入乱码存到数据库也乱。原因字符集链路中某一环断了。链路是IDE 源码编译字符集、JDBC URL 的 characterEncoding、MySQL 连接字符集、表字符集、字段字符集。任何一环不一致都会乱码。解决我的办法是一条龙。第一建库时用 utf8mb4表和字段显式指定 utf8mb4。第二JDBC URL 加characterEncodingutf8注意驱动配置里 utf8 就映射到 utf8mb4不需要写成 utf8mb4。第三IDEA 里把 Project Encoding、File Encoding 都设成 UTF-8。第四Swing 的 JTextField 默认就是 Unicode不需要额外处理。如果还乱在连接建立后执行一次SET NAMES utf8mb4确认连接层字符集。5.3 界面卡死耗时查询堵住了事件分发线程现象点击“查询全部记录”按钮后窗口变成白屏无响应Windows 提示“未响应”等很久才恢复。数据量只有几百条时感觉不出来但记录表累积到上万条或者没有索引SELECT * 会把所有行加载到内存界面自然卡死。原因把数据库查询直接写在了按钮的 ActionListener 里SQL 执行期间 EDT 被阻塞。Swing 的界面刷新、按钮点击、键盘输入全都在 EDT 上排队一个耗时操作堵住了整个队列其他事件全部等待。解决查询放到普通线程或 SwingWorker。最简单的改造new Thread(() - { ListParkingRecord records recordDao.queryAll(); SwingUtilities.invokeLater(() - tableModel.setDataVector(records)); }).start();如果觉得手动管理线程麻烦可以用 SwingWorker它自带 doInBackground 和 done 方法适合做后台任务。我通常用普通线程加 invokeLater因为代码量少课程设计里也够用。另一个坑是查询结果集越滚越大内存溢出这个要靠 SQL 带时间条件别允许用户第一次就查全表比如默认查最近一周日期范围用 JDateChooser 控件限制。5.4 SQL注入与空值别把用户输入直接拼进SQL现象输入车牌号 or 11查询出来整个表的数据或者输入带单引号的车牌时程序直接报错。课程设计里用 Statement 字符串拼接是常见写法但这是最容易被答辩老师抓住的问题。原因SQL 注入不是黑客专属只要用户输入被当成 SQL 参与执行就会有问题。比如SELECT * FROM parking_record WHERE plate_no input 输入一个1 OR 11拼接后就变成恒真条件。另外车位进场时如果车牌号输入了特殊字符还会引起 SQL 语法错误。解决所有 SQL 都用 PreparedStatement 占位符参数用 setString/setInt 传入。另外处理空值ResultSet.getXxx 返回 null 时要处理尤其出场记录里 exit_time 和 fee 可能为 null直接调用 getTimestamp.toString 会 NPE。我一般在实体类里把基本类型改成包装类型Integer、Double、Date然后在取值时统一判空。5.5 数据一致性并发入场同一车位现象管理台有两个窗口同时对同一个车位执行入场操作都成功了车位状态却是空闲或者记录表出现两条 entry_time 几乎相同、space_id 相同的记录。原因没有加条件更新也没有事务。前台的并发在真实停车场很少发生但演示时开了两个客户端或者点按钮手速太快就会触发。数据库事务隔离级别默认是 REPEATABLE READ两个会话同时执行 UPDATE 会等锁等第一个提交后第二个再执行。问题在于如果代码是“先 SELECT status0 再 UPDATE”第二个会话在上面那条 UPDATE 阻塞期间它之前 SELECT 到的还是 0等锁释放后 UPDATE 又无条件执行就覆盖了。解决用上一章写的UPDATE ... WHERE id ? AND status 0让更新条件带乐观锁。第二个事务执行时affected rows 是 0代码返回 false 提示“该车位已被占用”从而保证一致性。另外把两步放在同一个事务里并 commit杜绝中间状态。这个思路同样适用于 mysql 主从复制环境下的并发写场景核心原则就是“更新时带上业务条件不要先查再更”。6. 让系统更像产品报表、导出与验证方法6.1 用SQL聚合做日营收报表一个可用系统不能只停留在增删改查至少加一个按日统计营收的界面。SQL 不难一条 GROUP BY 就能解决SELECT DATE(entry_time) AS day, COUNT(*) AS order_count, COALESCE(SUM(fee), 0) AS total_fee FROM parking_record WHERE exit_time IS NOT NULL GROUP BY DATE(entry_time) ORDER BY day DESC;注意这里统计的是已离场的记录入场未出场的不算营收。COALESCE 处理 SUM(fee) 为 NULL 的情况避免报表里出现 null。DATE(entry_time) 做分组会触发全表扫描数据量大时要提前按日期范围过滤比如 WHERE entry_time BETWEEN ? AND ?。6.2 把报表导出成CSV半小时写好的功能导出用 CSV 最实惠Swing 用户点击按钮后用 JFileChooser 选择保存位置然后把结果写成 csv 文件Excel 能直接打开。我一般这样写try (BufferedWriter writer new BufferedWriter(new OutputStreamWriter( new FileOutputStream(file), UTF-8))) { writer.write(日期,订单数,营收(元)); writer.newLine(); for (ReportRow row : rows) { writer.write(row.getDay() , row.getCount() , row.getFee()); writer.newLine(); } }注意 CSV 的编码坑Excel 打开中文 CSV 经常乱码是因为 Excel 默认用本地 ANSI 编码。解决方法是写文件时在开头加 BOM 头也就是先写\uFEFF这样双击打开就不会乱码。字段里如果有逗号和引号要做转义课程设计一般用不上但写明这个边界会让系统更稳健。6.3 验证方法别只点一遍就宣布完成我交付前的验证套路是插入 1000 条模拟数据跑一遍入库、出库、统计重点验证边界条件0 分钟停车、1 分钟、59 分钟、60 分钟、61 分钟、24 小时整、跨天。计费规则用现有的算完手算对一下。然后开两个客户端同时操作同一车位确认第二个被拒绝。最后查一遍 MySQL 慢查询日志看看有没有全表扫描的 SQL。这套流程下来答辩演示基本不会翻车。说一个我自己的教训第一次做这类系统时我图省事把计费用 float 算结果对账差了 0.01 元被老师挑毛病后来把金额全改成 BigDecimal。还有一次忘记给 record 表加索引演示时点查询卡了十几秒场面一度很尴尬。现在我做任何一个基于 JavaSwingMySQL 的系统都把数据库连接配置、字符集、索引、事务这四件事当成地基先打好再盖楼。这套方案不用复杂框架花两三天就能从建库跑到导出报表非常适合课程设计或小规模内部工具。你把它跑通之后再回头去看 java 面试八股文里的事务隔离、PreparedStatement、Swing EDT 这些概念都会有更具体的体感。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →