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