资讯动态

Java Swing + SQL Server 停车场管理系统开发全流程详解

发布时间:2026/10/21 23:07:09 来源:尧图企业网站定制
聊一个让我又爱又恨的项目Eclipse Java Swing SQL Server 实现停车场管理系统。说它常见到什么程度随便翻一份计算机相关专业的课程设计清单基本都能看到这道题。但它又是一个典型的“看起来简单、做起来容易翻车”的项目。我一个学期里见过太多同学一开始信心满满最后不是卡在数据库连不上就是数据死活不显示再不然就是计费逻辑怎么算都不对。这篇文章我不打算给你堆一份完整代码而是把从需求梳理、数据表设计、Swing 界面搭建、核心业务代码到常见坑位的完整思路讲清楚。你不需要特别强的框架经验只要会 Java 基础语法、懂一点 SQL照着这套思路走就能把项目从零到一跑起来而且跑得明白、答辩问不倒。1. 技术选型Eclipse Swing SQL Server 这套组合为什么值得做1.1 语言与工具老组合也有新思路很多同学选这个题目的时候心里是犯嘀咕的现在前端框架都卷成什么样了为什么还要写 Java Swing这个疑惑很正常但放在课程设计和中小型桌面系统的场景里Swing 反而是非常务实的选择。Swing 是 Java 自带的 GUI 工具包不需要额外安装运行时环境开发环境里有 JDK 就能直接跑。它对电脑配置的要求极低不像 Electron 那类方案动辄占用几百兆内存在实验室那种老旧电脑上一样流畅。对学习阶段来说Swing 帮你把“界面”和“业务逻辑”拆分的思路保留得很纯粹——窗体是窗体监听器是监听器数据库操作是数据库操作每一层都能看得清清楚楚这比那些把界面和逻辑混在一起的框架更适合理解。用 Eclipse 做 IDE 的原因更简单它对 Swing 的可视化编辑支持很成熟WindowBuilder 插件可以直接拖拽组件生成代码即便你手写界面代码Eclipse 的自动补全和重构能力也够用。有人会问为什么不用 IntelliJ IDEA个人习惯是一方面另一方面是这类题目在很多课程里就是基于 Eclipse 教学链路展开的你选 IDE 尽量跟着实验环境和答疑老师的节奏走能少踩不少沟通成本。SQL Server 作为数据库在这类项目里主要是看中它的图形化管理工具和教学普及度。相比 MySQLSQL Server 的 Management Studio 可以很直观地看表结构、跑查询、调试存储过程对初学者来说更容易定位问题。而且它是 Windows 环境中配置最方便的数据库之一课程实验普遍使用跟 JDBC 的连接方式也很稳定。1.2 功能模块拆分先定范围再动手做项目最怕一上来就写代码。停车场的业务看着简单但如果没有把范围定清楚很容易写着写着就膨胀成“ERP 系统”。我建议先把功能边界画出来按角色和操作划分成几个核心模块登录与权限管理员登录系统用户名密码校验可扩展角色区分。车辆入场登记车牌号、车辆类型、车位编号写入入场时间车位状态同步置为“占用”。车辆出场根据车牌号匹配在场记录计算停车时长和费用更新车位状态为“空闲”。车位管理查看所有车位的编号、区域、类型、状态空闲/占用支持按空闲状态筛选。在场车辆查询以车牌号或车位维度查询当前未出场车辆支持模糊搜索。收费记录统计展示历史收费记录、当日总收费和累计收费。这是整个系统的功能骨架也直接对应你后面要建的数据表。把模块拆清楚之后你才能明确每个功能的输入是什么、输出是什么、要操作哪几张表这比拿到题目就闷头敲代码高效得多。2. 数据库打底SQL Server 建表与连接配置2.1 数据表设计与收费规则停车场管理系统最核心的是三张表车位表、车辆在场记录表、收费历史表。不要为了省事把所有字段塞在一张表里否则出场后历史记录被覆盖你就没法统计以前的收费数据了。先看车位表用来维护停车场里每个车位的状态CREATE TABLE parking_space ( space_id INT PRIMARY KEY IDENTITY(1,1), space_no VARCHAR(10) NOT NULL UNIQUE, area VARCHAR(20) NOT NULL, space_type VARCHAR(10) NOT NULL DEFAULT 小型车, status TINYINT NOT NULL DEFAULT 0 -- 0表示空闲1表示占用 );这里有几个细节需要注意。space_no是用户直观看到的车位编号它跟自增主键space_id要区分开因为实际项目里车位编号往往有业务规则比如“A-01”“负二楼-03”靠自增主键表达不了这种语义。status用数字类型比直接用字符串更省存储也方便后续逻辑判断。然后是车辆记录表。我采用了“一表到底”的思路也就是把入场和出场记录放在同一张表里通过out_time是否为 NULL 来判断车辆是否在场CREATE TABLE vehicle_record ( record_id BIGINT PRIMARY KEY IDENTITY(1,1), plate_no VARCHAR(12) NOT NULL, vehicle_type VARCHAR(10) NOT NULL, space_id INT NOT NULL, in_time DATETIME2 NOT NULL DEFAULT SYSDATETIME(), out_time DATETIME2 NULL, fee DECIMAL(10,2) NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1, -- 1表示在场0表示已离场 CONSTRAINT fk_space FOREIGN KEY (space_id) REFERENCES parking_space(space_id) );注意我把record_id用成了BIGINT而不是INT。原因很实在停车场每天产生的记录上千条时间久了INT很容易达到上限虽然课程设计里未必跑得到那么多数据但这个习惯对以后做真实项目很重要。时间字段我推荐用DATETIME2精度比DATETIME高默认值SYSDATETIME()是当前时间省得在 Java 端手动传时间。收费计算不能只看in_time和out_time的差值还要考虑不同车型的单价差异和免费时段规则。我先把一套通用规则放进系统的常量配置里车辆类型免费时长首小时费用超时每小时费用24小时封顶小型车15分钟5元3元30元大型车15分钟8元5元50元这套规则不一定跟哪个真实停车场完全一致但用来演示计费逻辑已经足够。你可以在后面的业务代码里用BigDecimal精确计算而不是用double去做金额运算这个原因后面会重点说。2.2 JDBC 驱动的正确打开方式建好表之后Java 端第一步不是写界面而是先把数据库连通。很多同学挂在这上面半天没反应最后发现是驱动 jar 没有正确导入。Eclipse 项目里需要引入 SQL Server 的 JDBC 驱动文件新版本通常叫mssql-jdbc-xxx.jar老版本叫sqljdbc4.jar。操作步骤是在项目根目录建一个lib文件夹把 jar 放进去然后右键项目选择 Build Path通过 Add JARs 把它加进来。这一步漏了运行时会直接报ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver。连接代码建议统一封装到一个工具类里不要在每个访问数据库的地方都写一遍连接逻辑public class DBUtil { private static final String URL jdbc:sqlserver://127.0.0.1:1433;DatabaseNameParkingDB;useUnicodetrue;characterEncodingUTF-8; private static final String USER sa; private static final String PASSWORD your_password; static { try { Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码看起来简单但有几个非常容易踩的点。第一URL 里的DatabaseNameParkingDB必须是数据库实际名称大小写不敏感但拼写不能错。第二使用 SQL Server 身份验证时需要在数据库服务器上开启混合验证模式否则用sa登录会报无法登录。第三如果 SQL Server 默认实例名后面带了命名实例URL 需要写成localhost\\实例名那个反斜杠在 Java 字符串里要转义成\\\\。驱动配置这部分我强烈建议你单独写一个测试入口先验证连接成功再继续做后面功能千万不要等界面写完再测连接不然会很难判断问题出在界面层还是数据库层。3. Swing 界面搭建从空窗体到能点能看的主界面3.1 主窗体布局分区决定使用体验Swing 界面设计的关键不是“好看”而是“功能对应关系清晰”。一个停车场管理系统的主窗体我建议采用上下左右分区的结构这样每个区域的功能一目了然。顶部放查询栏车牌输入框加查询按钮负责按车牌查在场车辆。左侧放车位状态面板用面板加按钮展示每个车位空闲绿色文字、占用红色文字方便管理员一眼看到哪些位置可用。中间是车辆记录表格区用JTable展示入场时间、车牌号、车型、车位号、状态等信息。底部是操作按钮区域入场登记、出场收费、刷新列表、收费统计。主窗体的布局代码大致是这个形态JFrame frame new JFrame(停车场管理系统); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(1024, 700); frame.setLocationRelativeTo(null); // 顶部查询栏 JPanel topPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); JLabel lblPlate new JLabel(车牌号); JTextField txtPlate new JTextField(12); JButton btnQuery new JButton(查询); topPanel.add(lblPlate); topPanel.add(txtPlate); topPanel.add(btnQuery); // 中部表格 DefaultTableModel model new DefaultTableModel(); model.setColumnIdentifiers(new Object[]{记录ID, 车牌号, 车辆类型, 车位编号, 入场时间, 状态}); JTable table new JTable(model); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane scrollPane new JScrollPane(table); frame.add(topPanel, BorderLayout.NORTH); frame.add(scrollPane, BorderLayout.CENTER); frame.setVisible(true);这里特别说一下JTable的用法。表格数据是由TableModel管理的不是直接往JTable里塞数据。上面用DefaultTableModel是最快捷的方式适合中小型项目。当你查询结果更新时不要反复new一个 JTable而是清空原 model 再重新填充数据这样界面才会跟着刷新这个机制后面还会讲到。3.2 事件监听与数据刷新机制Swing 是事件驱动的 UI 框架每个按钮的点击、表格行的选择都通过监听器处理。按钮监听最常用的方式是addActionListener可以直接用匿名内部类也可以写成 Lambda看你 Java 基础更喜欢哪种btnQuery.addActionListener(e - { String plateNo txtPlate.getText().trim(); if (plateNo.isEmpty()) { JOptionPane.showMessageDialog(frame, 请输入车牌号); return; } ListVehicleRecordVO list vehicleDao.queryByPlate(plateNo); refreshTableData(list); });这中间的一步是数据刷新机制。所谓刷新本质上是把数据库的最新查询结果重新绑定到 JTable 的 Model 上并且让界面感知到变化。热心同学最常犯的错误是查询成功后发现表格没变化就以为是界面代码写错了。其实问题往往出在 model 没有通知组件数据已更新。用DefaultTableModel时要先setRowCount(0)清空旧数据再逐行添加或者调用fireTableDataChanged()通知监听数据变化的组件。界面刷新频率也要控制好。比如车辆入场后车位面板的状态需要同步变化你不能只改数据库而不刷新左侧车位按钮。最省事的做法是封装一个refreshAll()方法把车辆表数据、车位状态、剩余统计三块一起刷新每次执行完增删改操作后统一调用一次。4. 核心业务代码把停车场逻辑写明白4.1 车辆入场登记流程车辆入场是第一步也是最容易写乱的地方。它的完整流程是用户输入车牌号选择车辆类型和空闲车位点击入场按钮后系统先检查这辆车是否已经在场内再校验车位是否空闲最后完成“更新车位状态 插入车辆记录”这两个操作。检查车牌是否在场内的语句要放在所有操作之前SELECT COUNT(*) FROM vehicle_record WHERE plate_no ? AND status 1;这里用COUNT(*)而不是SELECT *从数据库层面减少不必要的数据传输而且判断逻辑更清晰只要数量大于 0说明该车还没出场直接提示“该车辆已在场内请勿重复入场”。车位校验和插入记录需要配合在一起做。虽然课程设计里并发不高但养成好习惯可以避免以后出事故。建议把两步操作放进一个事务里执行任一一步失败就整体回滚Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 1. 把车位状态置为占用 String updateSpaceSql UPDATE parking_space SET status 1 WHERE space_id ? AND status 0; PreparedStatement ps1 conn.prepareStatement(updateSpaceSql); ps1.setInt(1, spaceId); int count ps1.executeUpdate(); if (count 0) { throw new SQLException(车位已被占用请重新选择); } // 2. 插入车辆入场记录 String insertRecordSql INSERT INTO vehicle_record (plate_no, vehicle_type, space_id) VALUES (?, ?, ?); PreparedStatement ps2 conn.prepareStatement(insertRecordSql); ps2.setString(1, plateNo); ps2.setString(2, vehicleType); ps2.setInt(3, spaceId); ps2.executeUpdate(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }你也许注意到我更新车位状态的 SQL 里带了AND status 0条件。这是一个非常实用的小技巧让数据库帮你在更新时复查状态如果已经有人抢占了车位executeUpdate返回 0你直接在事务里抛出异常并回滚避免了先查再改之间的时间差。这种“乐观锁”思想用在简单系统里既省事又可靠。4.2 出场计费与金额计算车辆出场要做的动作正好反过来读取该车入场记录 → 计算时长和费用 → 更新这条记录的出场时间和费用 → 把车位状态改回空闲。金额计算是这类题目的灵魂所在。你如果用double去做等用户停个 3 小时 05 分你能算出来 14.99999999 元到时候存进数据库显示出来非常难看。金额这类十进制精确计算必须用BigDecimal。我这里的计费方法按免费 15 分钟、首小时固定收费、超时部分按小时计费来处理public BigDecimal calcFee(Timestamp inTime, Timestamp outTime, BigDecimal firstHourFee, BigDecimal extraHourFee) { long minutes (outTime.getTime() - inTime.getTime()) / (1000 * 60); long freeMinutes 15; if (minutes freeMinutes) { return BigDecimal.ZERO.setScale(2); } long billableMinutes minutes - freeMinutes; BigDecimal hours BigDecimal.valueOf(billableMinutes / 60.0); BigDecimal totalFee firstHourFee; if (hours.compareTo(BigDecimal.ONE) 0) { BigDecimal extraHours hours.subtract(BigDecimal.ONE).setScale(0, RoundingMode.CEILING); totalFee totalFee.add(extraHours.multiply(extraHourFee)); } return totalFee.setScale(2, RoundingMode.HALF_UP); }我来解释一下这个逻辑的意图。停满 15 分钟以内免费这是给临时停靠的车主点甜头。超过 15 分钟后首小时固定收费已经包含了第一个 60 分钟所以超出的部分用剩余分钟数除以 60 后向上取整比如停了 1 小时 20 分钟扣除免费 15 分钟后剩余 1 小时 05 分超时部分按 2 小时算这样规则明确项目里也好跟老师解释。setScale保留两位小数RoundingMode.HALF_UP就是四舍五入大家平时不会纠结一分钱怎么舍对吧。另外出场操作同样建议用事务包起来。先根据车牌查在场记录再更新记录再释放车位。只要一个失败数据就会错乱钱收了但车位没释放或者车出去了但记录还在场。4.3 剩余车位统计与状态联动实时统计剩余车位是停车场系统特别直观的一个功能。主界面底部的状态栏如果没有“当前空闲车位A区 10 个B区 5 个”这类信息就少了一个很大的存在感。统计语句也不复杂SELECT area, COUNT(*) AS total, SUM(CASE WHEN status 0 THEN 1 ELSE 0 END) AS free_count FROM parking_space GROUP BY area;用一条 SQL 就能拿到每个区域的总车位和空闲车位数。这里巧妙的地方在于SUM(CASE WHEN ... THEN 1 ELSE 0 END)在 SQL Server 里相当于条件计数比查出所有车位后到 Java 里循环统计快得多代码也更精简。拿到统计结果后把它渲染到底部状态栏或左侧车位面板上。左面板的每个车位按钮要根据状态变化改变背景色或文字颜色我建议你写一个refreshParkingPanel()方法遍历车位列表逐个设置按钮颜色和提示信息。入场、出场、手动刷新都调用这个方法就能保证界面数据和数据库始终一致。5. 常见问题与避坑实录5.1 连接 SQL Server 时的经典报错这一块我遇到的频率最高每届都有同学栽进去。列举几个高频故障和排查顺序报错现象主要原因处理方式ClassNotFoundException驱动 jar 没导入或导入位置不对检查 Build Path 是否包含驱动包Connection refusedSQL Server 未启用 TCP/IP 协议打开 SQL Server 配置管理器启用 TCP/IP重启服务Login failed for user sa未开启混合验证模式或密码错误用 Management Studio 检查服务器身份验证模式重置密码Cannot open database ParkingDB数据库名和连接字符串不匹配核对 URL 中的DatabaseNameSQL Server 允许远程连接时默认端口是 1433但很多同学安装时用了命名实例导致动态端口每次变化。这时候最好把 SQL Server 配置成固定端口否则你的连接地址会出现“找不到端口”的怪问题。还有个容易被忽略的注意点连接信息不要写死在.java文件里以后想换数据库环境就要重新编译。简单项目里可以先用常量但你要知道更合适的做法是放到.properties配置文件里用java.util.Properties读取。5.2 中文乱码与 JTable 不刷新的处理中文乱码问题在 Swing 和 SQL Server 组合里很典型。它可能发生在两个层面一个是界面层按钮和标签上的中文字符显示成问号另一个是数据库层插入的中文数据读出来是乱码。界面层乱码通常是 Eclipse 编辑器的默认编码问题解决办法是把工作空间的编码改成 UTF-8再重新编译运行。数据库层乱码则要检查两个方面数据库排序规则是否支持中文一般安装时选默认规则就行JDBC 连接串是否带了characterEncodingUTF-8。有些环境下 SQL Server 的 JDBC 驱动需要用characterEncodingGBK才能正确显示中文这个没有绝对标准需要你根据实际环境试。我第一次做就吃了亏把数据从数据库直接查询是正常的但经过 Java 显示到界面上就是乱码最后发现是连接串缺少编码参数。JTable 不刷新是另一个高频问题。表现是操作完成后表格内容没有变化必须重启程序才能看到新数据。前面已经提过要用model.setRowCount(0)清空后再重新填充。还有一个隐藏坑如果你在数据刷新前手动修改了单元格内容模型会进入“已编辑”状态这时候直接setRowCount(0)可能异常稳妥做法是重新构造一个全新的DefaultTableModel再把列头设置回去。虽然代价是丢失选择状态但胜在简单干净。5.3 提前规避的几件小事最后想聊几个经常被忽略但影响很大的细节这些都是我在实际开发里慢慢悟出来的第一除非数据库表里已有成品计费存储过程否则别把计费公式写到 SQL 里。计费规则会变变的时候改 SQL 就要改数据库风险很大。把计费逻辑放在 Java 业务层至少出问题时你能在程序里设断点一步步看。第二JDBC 资源用完后记得关闭。顺手写一个close(Connection, PreparedStatement, ResultSet)工具方法别在每段代码末尾都写三行try/catch/finally。用 try-with-resources 语法也能达到同样效果而且代码更干净。第三车牌输入要做过滤。车牌的格式不统一有人输入小写有人输入带空格别人查询时用不同大小写就匹配不上。入场和查询时都统一把字符串trim()再toUpperCase()能避免很多无谓的“查不到”Bug。第四不要忽略空指针。JTable 行选择后再点出场按钮如果没选中行就操作要把空对象判断放在最前面用JOptionPane.showMessageDialog提示用户选择一条记录。这种小体验虽然代码里就一两行但能显著提升系统的完整度。第五给系统预留一点扩展空间。比如车辆记录表里加一个operator_name字段记录谁做的操作。课程设计阶段看起来用不上但答辩时老师一问你“系统如何跟踪操作员”这条扩展字段能帮你加分不止一点。做这个项目的过程中我最大的体会是把范围控制好比写出炫酷代码重要得多。一个功能老老实实跑通数据在界面上真实可见比十个页面点了没反应强太多。如果你正卡在某个环节别盯着代码看先从数据库里的数据入手用查询语句确认数据状态再顺着界面一层层找问题。最后再分享一个我自己的小习惯每天开始写代码前先把昨天跑通的功能测试一遍再做新功能。稳扎稳打推进而不是攒一堆问题到最后一起爆发。停车场管理系统难度并不高真正拉开差距的就是对这些细节的掌控力。希望这篇经验能让你少走几步弯路顺利把这套项目拿下。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑