設計與實現(xiàn))
簡介這是一份基于Java Swing與MySQL的停車場管理系統(tǒng)完整源碼包適合Java初學者、課程設計或畢業(yè)設計人群覆蓋車輛出入場、計費、用戶注冊充值、信息查詢與后臺管理等典型業(yè)務場景。資源共80個文件zip壓縮包大小約2.11MB主要包含25個Java源文件、47個class編譯文件及1個sql數(shù)據庫腳本另有項目配置文件、運行截圖與jar依賴庫可直接導入開發(fā)環(huán)境運行調試。配套SQL腳本提供charger、park、station、users等數(shù)據表結構便于快速初始化數(shù)據庫。系統(tǒng)按用戶與管理員雙角色劃分功能模塊登錄后分別支持車位查詢、入場出場操作、計費標準維護、歷史記錄查看等界面基于Swing實現(xiàn)適合用于理解桌面端管理系統(tǒng)的分層設計與MySQL交互邏輯。已有728人學習下載適合需要參考完整代碼結構、數(shù)據庫設計或快速搭建同類管理系統(tǒng)的人員使用。1. 基于 Java Swing MySQL 的停車場管理系統(tǒng)把課程設計做成能演示的完整項目如果說“管理系統(tǒng)”是 Java 課程設計里的常青題那“停車場管理”又是其中最考驗數(shù)據一致性的一個。很多人界面畫得挺好一算費用就對不上賬一查在場車輛又卡死。這套基于 Java Swing MySQL 的停車場管理系統(tǒng)恰好把業(yè)務鏈路補全了登錄區(qū)分用戶和管理員兩個角色車輛入場、出場結算、歷史查詢、用戶充值、計費標準管理都串在 users、charger、park、station 四張表上是典型的 java 課程設計案例源碼 應有的完整度。適合兩類人一種是準備交 Java 課設、需要一套能跑通全流程的參考代碼的另一種是想弄清 Swing 事件和 JDBC 事務怎么銜接的開發(fā)者。資源里除了代碼還帶運行截圖和 sql 腳本照著配置就能在新環(huán)境里跑起來。2. 先拆數(shù)據庫四張表怎么撐起停車場的狀態(tài)管理2.1 表與表之間的關系四個表名背后的業(yè)務分工拿到壓縮包以后我建議先別急著在 IDEA 里點運行先把 sql 目錄下的腳本打開看一遍。很多課設項目看起來功能齊全一查數(shù)據庫只有兩張表所有邏輯都堆在內存里關掉窗口就全丟了。這個項目和那種不同users、station、park、charger 四張表各管一段組合起來才叫“停車場管理系統(tǒng)”。四張表的分工大概是這樣的表名承擔的職責常見字段設計users用戶和管理員共用一張賬號表用角色字段區(qū)分身份id、username、password、role、balance、create_timestation停車場里的區(qū)域或車位位置信息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 關聯(lián) users通過 station_no 關聯(lián) station用 status 標識在場還是已出場charger 獨立于另外三張表只負責提供費率配置。為什么要把用戶和管理員放在同一張 users 表因為系統(tǒng)里“用戶”和“管理員”的動作高度相似都要登錄、都要改口令管理員只是多了車場管理和計費標準維護。用 role 字段區(qū)分能省掉一套幾乎重復的賬號表登錄時根據角色跳轉到不同主界面邏輯更干凈。2.2 當前可用車位一條 SQL 統(tǒng)計出實時數(shù)字系統(tǒng)功能里有個“當前可用車位”這是最容易做成假功能的地方。有些實現(xiàn)是建一個 int 字段存車位剩余數(shù)入場減一、出場加一聽起來合理但一旦程序異常退出這個數(shù)字就再也不會被修正。更穩(wěn)的做法是實時統(tǒng)計 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 表只統(tǒng)計 status 為 IN 的在場記錄然后用 total_space 減去在場數(shù)量得到可用數(shù)。LEFT JOIN 保證了即使某個 station 一條停車記錄都沒有也會出現(xiàn)在結果里。這里有兩點值得注意。第一不要用 DELETE 去刪除出場記錄出場時應該 UPDATE status 為 OUT這樣用戶查歷史信息、管理員看歷史車流都有數(shù)據第二如果停車記錄量大要給 park 表加一個聯(lián)合索引 (status, station_no)這條統(tǒng)計 SQL 就是走索引的簡單覆蓋查詢不會拖慢界面。2.3 計費標準先定義“怎么算錢”再寫代碼系統(tǒng)里計費標準單獨放在 charger 表說明設計者是有意識把“費率配置”和“計費動作”拆開的。這比把價格寫死在代碼里強得多——管理員在“計費標準管理”界面上改價格出場結算那邊不用改代碼。常見的計費規(guī)則是普通車輛按小時收費免費停車時長內不收費超過免費時長后按小時向上取整累加如果單日上限就用 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(); }這段代碼里兩個細節(jié)決定對賬對不對。一是(billableMinutes 59) / 60這是把“不足一小時按一小時算”用整除實現(xiàn)比 Math.ceil 更直接二是封頂判斷要放在最后因為先封頂再乘單價會把結果算錯。如果管理界面允許配置不同車型費率就在 charger 表里加 type 字段區(qū)分查詢時帶上類型條件。提示如果實際 sql 腳本里 chargter 表用的是“每小時價格半小時精度”這類配置把這個計算方法里的 60 改成對應分鐘粒度即可大頭邏輯不用動。3. 功能實現(xiàn)登錄、出入場、充值這些模塊怎么連起來3.1 雙角色登錄登錄按鈕背后不只是 SELECT系統(tǒng)的入口是登錄窗口用戶端和管理員端共用同一套賬號體系。Swing 里的實現(xiàn)套路是 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 直接接收是常見寫法但如果你對安全敏感用完這個字符串后可以及時置空引用。課設場景一般不會追究到這個深度但面試官問起來你能答出來就是加分項。另一個細節(jié)是 admin 賬號的管理員注冊入口在“系統(tǒng)管理”里普通用戶不能直接注冊管理員。這是通過 role 字段控制的登錄后所有需要權限的動作都要判斷當前 User 對象的角色而不是只在登錄界面攔一次。3.2 車輛入場往 park 表寫一條記錄同時扣減車位車輛入場功能在“車場管理”菜單下。管理員選擇或輸入 station_no填入車牌號系統(tǒng)要完成兩件事往 park 表插入一條入場記錄并把 station 表的可用車位減一。兩件事必須在一個事務里完成否則就會出現(xiàn)“車進去了車位沒減”的數(shù)據不一致。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。如果沒有這個條件兩個管理員同時給同一個區(qū)域入場時可能出現(xiàn)車位數(shù)減成負數(shù)。加上條件后執(zhí)行行數(shù)為 0 就回滾從數(shù)據庫層面攔住超賣。提示in_time 用數(shù)據庫的 NOW() 而不是 Java 的 System.currentTimeMillis()是為了保證后續(xù)出場計算時時間基準和設備一致也避免應用服務器和數(shù)據庫服務器時間偏差造成的計費爭議。3.3 車輛出場先算費再扣余額最后更新記錄出場邏輯比入場復雜。管理員輸入車牌號后系統(tǒng)要做的動作是查出該車在場記錄按 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 里先查余額再扣款要安全。并發(fā)場景下兩個請求同時讀到余額足夠的舊值就可能扣成負數(shù)。把余額判斷放進 WHERE 條件執(zhí)行行數(shù)為 0 就說明余額不足直接回滾。出場完成后再刷新“當前在場信息”和“當前可用車位”兩個界面實際上就是把 2.2 那條統(tǒng)計 SQL 重新跑一次。3.4 用戶充值、口令修改和計費標準維護這三個功能看上去獨立其實都在同一個原則下凡是涉及金額和密碼的操作都不要用簡單的 replace 更新整行。充值功能的核心 SQL 是UPDATE users SET balance balance ? WHERE id ?注意用的是相對更新不是先查出余額再加。相對更新在并發(fā)下不會互相覆蓋而且省一次查詢。金額校驗放哪一層都行至少要在 DAO 入口判斷充值金額為正數(shù)??诹钚薷牡暮诵膯栴}是“舊密碼校驗”。有些實現(xiàn)直接拿用戶輸入的新密碼 update 覆蓋等于一個能登錄的人改任何人的密碼。至少應該先校驗舊密碼public boolean changePassword(Integer userId, String oldPwd, String newPwd) { String sql UPDATE users SET password? WHERE id? AND password?; }計費標準管理則是 charger 表的增改操作。需要注意的坑是不要刪舊費率再插新費率因為歷史停車記錄的費用是已經算好的不依賴當前費率改動費率前應確認舊在場車輛在出場時用哪一檔費率通常做法是出場時查 is_active1 的新費率這會導致早前進場的老車按新價格結算實不實現(xiàn)“入場鎖定費率”就看課設要求了。4. 從資源到運行JDK 1.8 MySQL 8.0.13 的環(huán)境配置4.1 版本選擇的理由與改動邊界這個項目的環(huán)境鎖定在 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 時會報認證協(xié)議不兼容。這個項目表述里明確環(huán)境是 MySQL 8.0.13driver 用 8.0.13 對應的 connector 是穩(wěn)妥的。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;執(zhí)行后應該能看到 users、charger、park、station 四張表。如果 SHOW TABLES 只看到三張或一張都沒有十有八九是 SOURCE 時的路徑寫錯了Linux 下相對路徑容易出問題用絕對路徑更穩(wěn)。這里順便說明為什么建庫語句要單獨跑。有些 sql 腳本本身沒有 CREATE DATABASE 語句直接導入到默認庫會導致表落在錯誤位置。先建庫、指定字符集再 SOURCE是從根上規(guī)避中文亂碼。4.3 項目的 JDBC 連接配置改三處就能跑代碼里連數(shù)據庫的位置集中在 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連接串里的參數(shù)不是隨便加的。serverTimezone 必須配MySQL 8 的驅動默認要求指定時區(qū)不配的話啟動會直接拋異常useSSLfalse 是讓本機調試不折騰證書allowPublicKeyRetrievaltrue 解決 caching_sha2_password 認證方式下首次連接的密鑰交換問題characterEncodingUTF-8 配合 utf8mb4 庫是中文數(shù)據不亂碼的前提。如果你的 MySQL 密碼不是 123456只改 jdbc.password 一處就可以。不要在代碼里new String(password)去硬編碼優(yōu)先保證所有連接都走 DBUtil 的同一個配置入口。4.4 導入 IDE 并啟動對照運行截圖驗證界面在 IDEA 里導入這個項目時建議選擇直接打開 ParkingSwing 根目錄讓它識別為 Eclipse 項目。導入成功后src 目錄下的結構應該是標準的包名類名組合關鍵類包括各個 DAO、實體類、登錄窗體和主窗體。運行前對照檢查一遍檢查項說明lib 目錄 jar 是否被引入IDEA 里確認 mysql-connector 被加進 Libraries數(shù)據庫名與 jdbc.url 一致url 里寫 parking就建 parking 庫賬號表是否有初始記錄用 SQL 查 users 表沒有就手動插入管理員記錄賬號初始記錄是個隱藏坑。如果 sql 腳本里沒有 INSERT 管理員數(shù)據登錄界面會卡在“賬號密碼錯誤”。手動跑一條插入命令最簡單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 驅動把時區(qū)校驗做成了硬要求不像 5.x 版本能容忍缺省值。解決方式是在連接串后面追加serverTimezoneAsia/Shanghai參數(shù)值別用 GMT8 這種寫法直接寫 Asia/Shanghai 最穩(wěn)妥。5.2 界面中文全部顯示為問號現(xiàn)象是按鈕文字、表格表頭、下拉框里的中文全變成 ??英文字母正常。這個坑分兩層建庫時字符集不是 utf8mb4或者項目文件本身編碼混亂。排查順序是先看數(shù)據庫字符集SHOW CREATE DATABASE parking;如果發(fā)現(xiàn) DEFAULT CHARACTER SET 不是 utf8mb4重新按第 4.2 節(jié)的方式建庫。如果庫沒問題就看啟動參數(shù)在 IDEA 的 VM options 里加-Dfile.encodingUTF-8并把項目文件的編碼統(tǒng)一設成 UTF-8。注意 Eclipse 導出的項目可能默認是 GBK導入 IDEA 后也要改。5.3 點擊查詢按鈕后窗口假死現(xiàn)象是點擊“查詢歷史信息”或“查看當前可用車位”后整個窗口拖不動、按鈕沒反應過好幾秒才恢復。這是 Swing 單線程模型的典型翻車JDBC 查詢是耗時操作如果直接寫在按鈕事件里它會把事件分發(fā)線程 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現(xiàn)象是程序剛啟動正常點幾次查詢后所有操作都報“Data source rejected establishment of connection... Too many connections”。原因是 DAO 里無論是 Connection、Statement 還是 ResultSet用完后沒關。MySQL 默認連接數(shù)有限泄漏到上限就整體罷工。解決方式是把每次取連接、執(zhí)行、釋放收口到一個統(tǒng)一工具類用 try-with-resources 保證關閉try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 處理結果 }這個寫法里流式資源自動關閉不需要在 finally 里手工寫三行關閉代碼。如果項目里已經有 DBUtil建議檢查它的 closeQuietly 方法是否真的釋放了所有資源。5.5 數(shù)據庫表字段名與代碼里的大小寫不一致現(xiàn)象是查詢不報錯但拿不到數(shù)據或者 UPDATE 時報 Unknown column。原因很直接sql 腳本里的字段名是car_no代碼里寫的是carNoMySQL 在 Windows 下不區(qū)分大小寫換到 Linux 或云端數(shù)據庫就嚴格區(qū)分了。解決方式不是去改那么多代碼而是固定一個標準寫一個 SQL 字段映射的實體類或者干脆所有 SQL 都手寫列名不依賴 MyBatis 的自動駝峰轉換。課設資源常用原生 JDBC只要 DAO 里每個 ResultSet.getString 后面的列名和表字段保持一致就行。提示如果部署到 Linux 服務器的 MySQL導入 sql 腳本時注意表名也區(qū)分大小寫我一般把數(shù)據庫名、表名、字段名統(tǒng)一寫成小寫下劃線風格能在根源上避開這類問題。6. 驗證跑通與改造方向從能啟動到能交付6.1 一條閉環(huán)走查路徑項目能啟動不等于功能閉環(huán)。為了確認系統(tǒng)真的可用建議按下面這條路徑完整走一遍步驟操作預期結果1管理員登錄進入管理員主界面車場管理菜單可點2注冊一個普通用戶并充值 50 元users 表 balance 變?yōu)?503錄入一輛車入場選擇車位區(qū)域park 多一條 IN 記錄可用車位減 14用戶視角查詢在場信息能看到剛才入場車輛且狀態(tài)為在場5執(zhí)行出場彈出本次費用余額按費率扣減6查詢歷史信息出場記錄 fee 有值時間正確顯示7修改計費標準后再次入場出場費用按新費率計算如果每一步的結果和預期相符說明數(shù)據庫鏈接、表設計、業(yè)務邏輯三層是通的。第 5 步尤其要算一遍賬用筆算金額和系統(tǒng)彈出金額對得上才不算白跑。6.2 三個值得做的低成本改造第一給可用車位增加定時刷新?,F(xiàn)階段的可用車位是點擊查詢才更新改造方式是加一個 Swing Timer每隔 30 秒重新跑 2.2 節(jié)的統(tǒng)計 SQL 并刷新 JTable讓界面更像“實時系統(tǒng)”。第二把計費標準管理改成下拉選擇生效?,F(xiàn)在的 charger 表如果有多個費率配置出場邏輯要注意讀的是哪一檔。改造方向是讓管理員設置“當前啟用費率”標識出場時只查 is_active1 的那條。第三增加出入場小票打印。Swing 里可以用 java.awt.print 包直接打印一張簡易單據把車牌、入庫時間、出場時間、費用打出來演示效果遠好于干巴巴的彈窗。最后說一個我個人的習慣從那以后我拿到任何一個課設項目第一件事一定不是看代碼而先看 sql 腳本和連接配置先確認表結構符合業(yè)務、連接參數(shù)能對上再往下讀 DAO 才有意義。這個順序幫我避開過不少“代碼沒問題但跑不起來”的局面也希望這次關于停車場管理系統(tǒng)的拆解能幫到你。本文還有配套的精品資源點擊獲取