動(dòng)管理系統(tǒng)源碼詳解:從Swing界面到JDBC事務(wù)實(shí)踐)
簡(jiǎn)介這是一份基于Java的田徑運(yùn)動(dòng)管理系統(tǒng)設(shè)計(jì)源碼適合Java學(xué)習(xí)者、軟件工程課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)參考。資源共69個(gè)文件其中57個(gè)Java源文件構(gòu)成核心業(yè)務(wù)邏輯9個(gè)txt文本涵蓋比賽規(guī)則、數(shù)據(jù)庫(kù)觸發(fā)器腳本和README說(shuō)明2個(gè)pptx演示文稿展示E-R圖與系統(tǒng)設(shè)計(jì)思路另有l(wèi)icense文件壓縮包僅351KB目錄按dky6.5至dky6.9等多個(gè)版本組織便于對(duì)比研讀。系統(tǒng)覆蓋運(yùn)動(dòng)員信息管理、比賽日程安排、成績(jī)記錄與查詢等典型模塊利用數(shù)據(jù)庫(kù)觸發(fā)器維護(hù)數(shù)據(jù)一致性從實(shí)體關(guān)系設(shè)計(jì)到界面和業(yè)務(wù)代碼均有完整呈現(xiàn)。已有261人學(xué)習(xí)下載對(duì)希望快速搭建管理類項(xiàng)目框架、理解Java桌面應(yīng)用與數(shù)據(jù)庫(kù)交互的開(kāi)發(fā)者具有實(shí)用參考價(jià)值。1. 田徑運(yùn)動(dòng)管理系統(tǒng)源碼別只把它當(dāng)課設(shè)拿到這份源碼包第一眼我就覺(jué)得它不是那種臨時(shí)湊出來(lái)的課程設(shè)計(jì)。包里有 57 個(gè) Java 源文件從 Connect、Interface 到 MyFrame、HomePage還帶著 dky6.5 到 dky6.9 的迭代版本、觸發(fā)器腳本、E-R 圖 PPT。這明顯是一個(gè)跑過(guò)完整開(kāi)發(fā)周期的項(xiàng)目不是幾段代碼拼出來(lái)的演示品。它能直接回答一個(gè)很實(shí)際的問(wèn)題一個(gè)田徑管理系統(tǒng)從數(shù)據(jù)庫(kù)模型到 Swing 界面、從運(yùn)動(dòng)員注冊(cè)到成績(jī)查詢每一步到底該怎么落地。如果你正在做 Java 課設(shè)、畢業(yè)設(shè)計(jì)或者想找一份能看懂的完整業(yè)務(wù)系統(tǒng)源碼這份資源完全夠用。它好就好在代碼分層清楚界面類、業(yè)務(wù)類、數(shù)據(jù)庫(kù)連接類各自獨(dú)立你可以順著一條線把整個(gè)系統(tǒng)的骨架摸明白。對(duì)于想學(xué)的開(kāi)發(fā)者它是很好的參考對(duì)于時(shí)間緊想快速交作業(yè)的人它也能讓你少走一半彎路。別急著解壓先跟著我過(guò)一遍它的設(shè)計(jì)思路和坑點(diǎn)你會(huì)省下不少折騰的時(shí)間。2. 先把源碼讀薄從 70 個(gè)文件里抓出真正的骨架2.1 文件清單告訴你系統(tǒng)的模塊邊界在哪解壓之后先別急著打開(kāi) IDE花十分鐘看文件清單你會(huì)比直接跑代碼收獲更大。整個(gè)資源包的核心是 dky、dky6.5、dky6.6、dky6.7、dky6.8、dky6.9 幾個(gè)版本目錄每個(gè)目錄里都有一批命名高度一致的 Java 文件Interface、MyFrame、Prepare、HomePage、MyRegister、MyPtint、MyManage、MyPuery、Connect。這其實(shí)就是一個(gè) MVC 思路下的桌面應(yīng)用Connect 是模型層連數(shù)據(jù)庫(kù)的入口Interface 是視圖層主窗體MyManage 和 MyRegister 是業(yè)務(wù)操作MyPuery 和 MyPtint 是查詢與打印。版本目錄存在的意義也值得多說(shuō)一句dky6.5 里沒(méi)有 Connect.java而 dky6.6 開(kāi)始有了dky6.8 和 dky6.9 里多了 Tem.java 和 CX.java。這說(shuō)明作者是邊開(kāi)發(fā)邊保存快照到了 6.9 才把查詢邏輯拆成了 CX 類表單邏輯拆成 MyForm。如果你想讀懂這個(gè)系統(tǒng)的演進(jìn)步驟直接對(duì)比 dky 和 dky6.9 兩個(gè)目錄你能看到代碼結(jié)構(gòu)是怎么從一個(gè)雛形慢慢變清晰的。從復(fù)現(xiàn)角度我建議你直接看 dky6.9 下的代碼它是所有版本里最完整的一版。Connect.java 負(fù)責(zé)拿 JDBC 驅(qū)動(dòng)連數(shù)據(jù)庫(kù)MyFrame 負(fù)責(zé)創(chuàng)建主窗體并容納各個(gè)功能面板HomePage 是首頁(yè)展示MyRegister 處理運(yùn)動(dòng)員錄入MyManage 負(fù)責(zé)數(shù)據(jù)維護(hù)MyPuery 和 CX 管條件查詢MyPtint 管報(bào)表打印。這樣一個(gè)文件對(duì)一塊業(yè)務(wù)的映射關(guān)系比任何流程圖都直觀。2.2 從 E-R 圖和觸發(fā)器倒推開(kāi)數(shù)據(jù)庫(kù)表結(jié)構(gòu)源碼包里有兩個(gè) E-R 圖 PPT一個(gè)是 1.0一個(gè)是 2.0。你可以把它們當(dāng)成系統(tǒng)的數(shù)據(jù)地圖???E-R 圖最省力的方式是先找實(shí)體再找關(guān)系。我的判斷是這個(gè)系統(tǒng)里至少存在這樣幾張表運(yùn)動(dòng)員信息表存放姓名、性別、項(xiàng)目、成績(jī)、代表隊(duì)等、比賽項(xiàng)目表存放項(xiàng)目編號(hào)、項(xiàng)目名稱、比賽時(shí)間、比賽日程表關(guān)聯(lián)項(xiàng)目和場(chǎng)地時(shí)間以及一個(gè)用戶表對(duì)應(yīng)登錄功能。運(yùn)動(dòng)員和比賽項(xiàng)目之間是多對(duì)多的一個(gè)運(yùn)動(dòng)員可以報(bào)多個(gè)項(xiàng)目一個(gè)項(xiàng)目有多人參加所以一定有一張中間表來(lái)存報(bào)名關(guān)系。再打開(kāi)觸發(fā)器.txt你會(huì)發(fā)現(xiàn)作者在數(shù)據(jù)庫(kù)層做了不少控制。觸發(fā)器是非常典型的數(shù)據(jù)庫(kù)端約束手段比如當(dāng)成績(jī)記錄被插入時(shí)自動(dòng)比對(duì)當(dāng)前成績(jī)與歷史最好成績(jī)?nèi)绻镁妥詣?dòng)更新運(yùn)動(dòng)員的最佳成績(jī)字段。這種做法比在 Java 代碼里寫判斷要可靠因?yàn)槟愣伦×怂腥肟谀呐掠腥酥苯訉?duì)著數(shù)據(jù)庫(kù) UPDATE觸發(fā)器也會(huì)生效。你的代碼里如果還沒(méi)有類似的機(jī)制可以學(xué)這個(gè)思路把關(guān)鍵的業(yè)務(wù)規(guī)則下沉到數(shù)據(jù)庫(kù)層應(yīng)用層只負(fù)責(zé)調(diào)用。數(shù)據(jù)庫(kù)連接方面Connect.java 是重中之重。下面是這類代碼最常見(jiàn)的樣子import java.sql.*; public class Connect { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/track_field?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() { Connection conn null; try { Class.forName(DRIVER); conn DriverManager.getConnection(URL, USER, PASSWORD); } catch (ClassNotFoundException e) { System.out.println(驅(qū)動(dòng)加載失敗請(qǐng)檢查 mysql-connector.jar 是否在 classpath 中); } catch (SQLException e) { System.out.println(數(shù)據(jù)庫(kù)連接失敗請(qǐng)檢查 MySQL 服務(wù)是否已啟動(dòng)賬號(hào)密碼是否正確); } return conn; } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }這段代碼的邏輯重點(diǎn)是先加載驅(qū)動(dòng)、再建立連接、最后封裝了關(guān)閉方法。URL 里的serverTimezoneAsia/Shanghai是必須寫的MySQL 8.x 時(shí)區(qū)校驗(yàn)很嚴(yán)格不寫日期時(shí)間相關(guān)的查詢會(huì)直接報(bào)錯(cuò)。characterEncodingutf8是給中文數(shù)據(jù)用的如果你在錄入運(yùn)動(dòng)員姓名時(shí)出現(xiàn)亂碼問(wèn)題多半就出在這里。我一般會(huì)建議把 PASSWORD 從代碼里拿出來(lái)放到配置文件但在課設(shè)場(chǎng)景下先跑通是第一位。3. 把界面跑起來(lái)Swing 表單背后的人機(jī)交互設(shè)計(jì)3.1 MyFrame 與 HomePage主窗體是怎么把各個(gè)頁(yè)面掛上去的Swing 程序要跑起來(lái)核心是 JFrame。MyFrame.java 在系統(tǒng)里扮演的就是主容器角色它把登錄窗口、首頁(yè)、注冊(cè)頁(yè)面、管理頁(yè)面、查詢頁(yè)面都用卡片布局的方式裝在一個(gè)窗體里。用 CardLayout 的好處是頁(yè)面切換不會(huì)重繪整個(gè)窗口響應(yīng)速度快而且每個(gè)頁(yè)面對(duì)象可以常駐內(nèi)存數(shù)據(jù)狀態(tài)不丟失。我在自己的桌面項(xiàng)目里也喜歡用這個(gè)方案它比每次點(diǎn)擊菜單就 new 一個(gè) JFrame 要清爽得多。HomePage.java 作為登錄后的默認(rèn)頁(yè)一般會(huì)放系統(tǒng)標(biāo)題、當(dāng)前日期時(shí)間、操作快捷入口。它的代碼邏輯并不復(fù)雜關(guān)鍵是讓用戶登錄后第一眼就知道自己能干什么。你可以試著改一下 HomePage 的背景圖、歡迎語(yǔ)再給每個(gè)快捷按鈕綁定事件就能切身體會(huì)到 Swing 的事件監(jiān)聽(tīng)機(jī)制是怎么工作的按鈕加了 ActionListener點(diǎn)擊后觸發(fā) actionPerformed里面調(diào)用 CardLayout 的 show 方法切換到對(duì)應(yīng)頁(yè)面。界面這一層如果你不想深究可以直接跑起來(lái)看效果。但要提醒一句很多課設(shè)做不好就是因?yàn)榇a全堆在構(gòu)造方法里一個(gè) JFrame 里寫了上千行。你拿到這份源碼可以對(duì)比一下它的 MyFrame 只負(fù)責(zé)搭建容器和路由具體的頁(yè)面邏輯已經(jīng)拆到各自類里了這才是值得學(xué)習(xí)的地方。3.2 運(yùn)動(dòng)員注冊(cè)與成績(jī)錄入MyRegister 和 MyManage 的數(shù)據(jù)鏈路打開(kāi) MyRegister.java 你會(huì)發(fā)現(xiàn)這個(gè)類做的事情就是把你填寫的表單數(shù)據(jù)塞進(jìn) PreparedStatement 然后執(zhí)行 INSERT。但它的價(jià)值不在于那個(gè) INSERT而在于它展示了桌面應(yīng)用里一條完整的數(shù)據(jù)鏈路從文本框中取值、做非空校驗(yàn)、拼 SQL 參數(shù)、執(zhí)行、提示結(jié)果、刷新下方表格。學(xué)習(xí)這條鏈路比背十遍 JDBC API 都管用因?yàn)榇蠖鄶?shù)管理系統(tǒng)界面的底層邏輯都是這個(gè)套路。M圬Manage 則負(fù)責(zé)數(shù)據(jù)維護(hù)它的核心操作是 UPDATE 和 DELETE并且大概率會(huì)帶一個(gè)查詢條件來(lái)確定要操作的行。你寫到這種類的時(shí)候最容易犯的錯(cuò)誤是把 ID 直接拼進(jìn) SQL 字符串這是血淚教訓(xùn)。正確做法是用 PreparedStatement 的占位符或者說(shuō)白一點(diǎn)一律用?代替真實(shí)值這既能防止 SQL 注入也能避免中英文引號(hào)導(dǎo)致的語(yǔ)法錯(cuò)誤。下面是典型的注冊(cè)邏輯骨架它對(duì)應(yīng) MyRegister 里核心代碼的寫法public boolean registerAthlete(String name, String gender, String event, String school) { String sql INSERT INTO athlete (name, gender, event, school) VALUES (?, ?, ?, ?); try (Connection conn Connect.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, name); ps.setString(2, gender); ps.setString(3, event); ps.setString(4, school); return ps.executeUpdate() 0; } catch (SQLException e) { System.out.println(插入失敗 e.getMessage()); return false; } }注意我用的是 try-with-resources這樣連接和 PreparedStatement 會(huì)在方法結(jié)束時(shí)自動(dòng)關(guān)閉不需要手動(dòng)在 finally 里寫關(guān)閉邏輯。setString 的序號(hào)是從 1 開(kāi)始的順序必須和 SQL 里的?一一對(duì)應(yīng)這是新手最容易翻車的地方。setString 會(huì)自動(dòng)處理字符串中的特殊字符這就是 PreparedStatement 防注入的原理比拼字符串安全得多。4. 成績(jī)管理與查詢打印從數(shù)據(jù)庫(kù)到報(bào)表的完整路徑4.1 用查詢條件組合成績(jī)看板MyPuery 和 CX 的分工邏輯dky6.9 里把查詢單獨(dú)抽成了 CX.java配合 MyPuery.java 使用這個(gè)拆法我認(rèn)為很聰明。MyPuery 負(fù)責(zé)界面上面的條件輸入框、中間的查詢按鈕、下面的 JTable 結(jié)果區(qū)。CX 負(fù)責(zé)業(yè)務(wù)根據(jù)條件拼 SQL、執(zhí)行查詢、把 ResultSet 轉(zhuǎn)成表格模型、返回給界面顯示。這樣做的好處是界面代碼和查詢代碼互不干擾以后你增加一個(gè)查詢條件只需要改 CX 里一個(gè) if 判斷。查詢成績(jī)最常用的是按項(xiàng)目名稱查、按運(yùn)動(dòng)員姓名查、按成績(jī)排序查。我建議你把查詢語(yǔ)句做成動(dòng)態(tài)拼接的核心邏輯是先寫一個(gè)SELECT * FROM athlete WHERE 11然后按條件往里追加。WHERE 11不是多余的它是為了讓后面的條件都能直接以 AND 開(kāi)頭省去判斷是不是第一條條件的邏輯。用 StringBuilder 來(lái)拼是最常見(jiàn)做法。從這段代碼你能得到的教訓(xùn)是動(dòng)態(tài)拼 SQL 并不可怕可怕的是你不會(huì)用 PreparedStatement 去替代值。每次執(zhí)行查詢前可以用 PreparedStatement 預(yù)先編譯 SQL數(shù)據(jù)庫(kù)會(huì)緩存執(zhí)行計(jì)劃多次執(zhí)行同一句話性能會(huì)好很多。在 Swing 表格里刷新數(shù)據(jù)時(shí)記得先清空舊的 TableModel否則會(huì)出現(xiàn)重復(fù)行這種問(wèn)題讓你排查半天也找不出原因。4.2 打印功能與報(bào)表MyPtint 如何把查詢結(jié)果變成紙質(zhì)版MyPtint.java 是這套系統(tǒng)里很有特點(diǎn)的一個(gè)模塊。它把查詢結(jié)果轉(zhuǎn)換為可打印的文檔這在大部分課設(shè)項(xiàng)目里是缺失的但實(shí)際比賽場(chǎng)景中又非常需要。比賽結(jié)束要立即出成績(jī)單如果系統(tǒng)只能看屏幕不能出紙那實(shí)用性就大打折扣。MyPtint 的做法通常是接收一個(gè) ResultSet 或列表數(shù)據(jù)逐行填入打印文本的排版結(jié)構(gòu)中再通過(guò) Java 的打印 API 輸出。我自己處理打印時(shí)會(huì)額外做兩件事一是先確認(rèn)選中的打印機(jī)和頁(yè)面尺寸防止默認(rèn)打印機(jī)不是你要的那臺(tái)二是建議打印前把數(shù)據(jù)加載到預(yù)覽窗口而不是直接輸出因?yàn)橐坏c(diǎn)錯(cuò)打印機(jī)幾十張紙就白費(fèi)了。MyPtint 里如果實(shí)現(xiàn)了打印預(yù)覽你就在它的基礎(chǔ)上擴(kuò)展如果沒(méi)實(shí)現(xiàn)至少要把錯(cuò)行檢測(cè)加進(jìn)去數(shù)據(jù)為空時(shí)要提示而不是打印空白頁(yè)。這個(gè)模塊的價(jià)值不只是打一張紙它指向了一個(gè)更重要的能力以數(shù)據(jù)為中心做業(yè)務(wù)閉環(huán)。從報(bào)名到比賽到成績(jī)發(fā)布再到打印歸檔每一步都有代碼支撐這才是一個(gè)管理系統(tǒng)真正完整的樣子。5. 避坑指南把這套系統(tǒng)跑起來(lái)最容易踩的五個(gè)坑5.1 數(shù)據(jù)庫(kù)連不上界面停在登錄頁(yè)不動(dòng)的真實(shí)原因現(xiàn)象點(diǎn)擊登錄按鈕后界面沒(méi)有反應(yīng)沒(méi)有報(bào)錯(cuò)彈窗控制臺(tái)也無(wú)輸出。原因九成是 MySQL 服務(wù)沒(méi)啟動(dòng)或者是 root 密碼和 Connect.java 里寫得不一樣再有就是驅(qū)動(dòng) jar 包沒(méi)放進(jìn) classpath。解決方法是先打開(kāi)命令提示符執(zhí)行net start mysql確認(rèn)服務(wù)已啟動(dòng)然后單獨(dú)寫一個(gè)測(cè)試類調(diào)用Connect.getConnection()如果返回 null 就把用戶名密碼打印出來(lái)對(duì)比。注意 MySQL 8.x 的驅(qū)動(dòng)類名是com.mysql.cj.jdbc.Driver舊代碼里寫com.mysql.jdbc.Driver會(huì)直接報(bào) ClassNotFoundException。5.2 中文顯示亂碼運(yùn)動(dòng)員姓名變成奇怪的符號(hào)現(xiàn)象JTable 和文本框里中文全部顯示問(wèn)號(hào)或亂碼。原因是建庫(kù)時(shí)沒(méi)指定 utf8或者 JDBC URL 里少了characterEncodingutf8。解決方法是重建數(shù)據(jù)庫(kù)時(shí)加上DEFAULT CHARSETutf8mb4同時(shí)把 URL 改成包含字符集的完整地址。改完這兩處后記得先刪掉舊的連接再重跑。順帶說(shuō)一句MySQL 5.7 和 8.x 的默認(rèn)字符集不同如果你用的 8.x 還帶 emoji 需求建議直接選 utf8mb4一勞永逸。5.3 編譯時(shí)找不到符號(hào)提示 Interface 或 Connect 不存在現(xiàn)象導(dǎo)入項(xiàng)目后按編譯IDE 報(bào)錯(cuò)找不到符號(hào)。原因是源碼里可能同時(shí)存在多個(gè)版本的 Interface.javaIDE 默認(rèn)只認(rèn)一個(gè)目錄而 dky6.9 里缺少了對(duì)某個(gè)類的依賴。解決方法是把運(yùn)行目錄固定為 dky6.9把 MyPuery 里寫錯(cuò)的類名改回來(lái)如果代碼里調(diào)用了MyQuery而實(shí)際文件名是MyPuery.java那就以文件名為準(zhǔn)統(tǒng)一改。這個(gè)項(xiàng)目里有相當(dāng)多命名不統(tǒng)一的類名所以每次編譯報(bào)錯(cuò)先看文件名再檢查類名是否匹配。5.4 觸發(fā)器不生效成績(jī)播報(bào)和自動(dòng)更新沒(méi)動(dòng)靜現(xiàn)象往成績(jī)表插入數(shù)據(jù)后運(yùn)動(dòng)員最佳成績(jī)字段沒(méi)有自動(dòng)更新也不報(bào)錯(cuò)。原因是你只導(dǎo)入了觸發(fā)器腳本卻沒(méi)有執(zhí)行它或者執(zhí)行了但觸發(fā)器涉及的表名和你實(shí)際建的表名不一致。解決方法是先用SHOW TRIGGERS確認(rèn)觸發(fā)器已存在再檢查觸發(fā)器體里的表名和字段名。觸發(fā)器寫在數(shù)據(jù)庫(kù)層的確好用但調(diào)試它比調(diào)試 Java 代碼費(fèi)勁一些我一般會(huì)在觸發(fā)器里加一句信號(hào)提示來(lái)驗(yàn)證它是否被觸發(fā)。5.5 雙開(kāi)與數(shù)據(jù)不一致連著兩次登錄改同一份數(shù)據(jù)現(xiàn)象同時(shí)打開(kāi)兩個(gè)客戶端一個(gè)改了成績(jī)另一個(gè)界面還是舊數(shù)據(jù)。原因是數(shù)據(jù)刷新時(shí)機(jī)不對(duì)JTable 的數(shù)據(jù)只在你點(diǎn)擊查詢時(shí)加載不會(huì)自動(dòng)感知數(shù)據(jù)庫(kù)的變化。解決方法是給查詢按鈕的觸發(fā)方法里加一句重新查詢或者在窗口獲取焦點(diǎn)時(shí)觸發(fā)刷新。血淚經(jīng)驗(yàn)是別指望數(shù)據(jù)庫(kù)的實(shí)時(shí)性自動(dòng)推送到 Swing 界面你得給它一個(gè)刷新的機(jī)制最簡(jiǎn)單就是在每次操作寫完后重新查一次列表。6. 驗(yàn)證數(shù)據(jù)一致性的狠招給系統(tǒng)加一層事務(wù)和一條自動(dòng)校驗(yàn)?zāi)_本你能把這套系統(tǒng)跑通之后下一步最值得做的事就是給它加一層事務(wù)控制和校驗(yàn)?zāi)_本。一個(gè)管理系統(tǒng)如果連一致性都保證不了成績(jī)數(shù)據(jù)錯(cuò)一點(diǎn)比賽結(jié)果就全亂了?,F(xiàn)在代碼里很多操作是單條 SQL看起來(lái)沒(méi)問(wèn)題但一個(gè)業(yè)務(wù)往往需要多步操作。拿跨項(xiàng)目報(bào)名來(lái)說(shuō)你得先在報(bào)名表插入一條記錄然后更新賽事人數(shù)最后還要往日志表記一筆這三步任何一步失敗數(shù)據(jù)就殘缺了。正確做法是把它們包進(jìn)一個(gè)事務(wù)里要么全成功要么全回滾。下面這段代碼可以作為你改造 Connect 和 MyManage 時(shí)的參考模板public boolean registerWithTransaction(String athleteId, String eventId) { Connection conn null; try { conn Connect.getConnection(); conn.setAutoCommit(false); // 關(guān)閉自動(dòng)提交手動(dòng)控制事務(wù) String sql1 INSERT INTO registration (athlete_id, event_id, reg_time) VALUES (?, ?, NOW()); PreparedStatement ps1 conn.prepareStatement(sql1); ps1.setString(1, athleteId); ps1.setString(2, eventId); ps1.executeUpdate(); String sql2 UPDATE event SET participant_count participant_count 1 WHERE event_id ?; PreparedStatement ps2 conn.prepareStatement(sql2); ps2.setString(1, eventId); ps2.executeUpdate(); conn.commit(); // 兩步全部成功提交事務(wù) return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); // 任何一步失敗回滾到初始狀態(tài) } catch (SQLException ex) { ex.printStackTrace(); } } System.out.println(報(bào)名事務(wù)失敗數(shù)據(jù)已回滾 e.getMessage()); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }邏輯說(shuō)明先把自動(dòng)提交關(guān)掉這樣數(shù)據(jù)庫(kù)不會(huì)在每條 SQL 執(zhí)行完就落盤而是等我們手動(dòng) commit 才生效。中間任何一步拋異常rollback 把所有修改還原不會(huì)出現(xiàn)報(bào)名表多了記錄而人數(shù)沒(méi)加上的情況。注意 catch 里要判空因?yàn)楂@取連接失敗時(shí) conn 可能為 null直接調(diào)用 rollback 會(huì)二次報(bào)錯(cuò)。finally 里把 autoCommit 恢復(fù)成 true 是個(gè)好習(xí)慣因?yàn)檫B接池會(huì)復(fù)用這個(gè)連接不恢復(fù)的話后續(xù)操作全在一個(gè)未提交的事務(wù)里。參數(shù)說(shuō)明setAutoCommit(false)是事務(wù)的開(kāi)關(guān)默認(rèn)的 true 意味著每條 SQL 獨(dú)立成事務(wù)在 MySQL InnoDB 引擎下沒(méi)問(wèn)題但 MyISAM 引擎不事務(wù)表所以建表時(shí)選 InnoDB 是前提條件。事務(wù)加完我還會(huì)做另一種驗(yàn)證寫一個(gè)數(shù)據(jù)一致性校驗(yàn)?zāi)_本用來(lái)定期比對(duì)成績(jī)表、運(yùn)動(dòng)員表和比賽項(xiàng)目表的數(shù)據(jù)。你不一定非要在系統(tǒng)里做每天早上用一段帶固定 SQL 的腳本跑一遍把異常行打印出來(lái)就行。這樣的腳本不需要多復(fù)雜核心是幾條帶 LEFT JOIN 的檢測(cè)語(yǔ)句重點(diǎn)關(guān)注三種情況報(bào)名了不存在的運(yùn)動(dòng)員編號(hào)、成績(jī)表里沒(méi)有對(duì)應(yīng)運(yùn)動(dòng)員、比賽項(xiàng)目表的外鍵指向空值。從那以后我每次拿到別人寫的管理系統(tǒng)源碼第一件事就是先看它有沒(méi)有事務(wù)處理再看它有沒(méi)有配套校驗(yàn)手段這兩樣缺一樣我都不敢直接拿去給項(xiàng)目用。這兩個(gè)能力養(yǎng)成了你會(huì)發(fā)現(xiàn)很多原本要花兩小時(shí)才能排查出來(lái)的臟數(shù)據(jù)問(wèn)題在上線之前就被腳本攔住了。希望這份田徑運(yùn)動(dòng)管理系統(tǒng)源碼能幫你把 Java 桌面應(yīng)用的開(kāi)發(fā)鏈條真正走通從界面到數(shù)據(jù)庫(kù)再到事務(wù)控制每一步都心里有數(shù)。本文還有配套的精品資源點(diǎn)擊獲取