
簡介包含完整源代碼、數據庫腳本與部署視頻的圖書借閱管理系統畢業設計資料包面向需要完成Java Web課程設計或畢業設計的本專科學生。系統涵蓋圖書管理、用戶管理、借閱記錄等核心模塊涉及Java、MVC模式、Spring、Hibernate、Servlet/JSP及前端等主流技術能幫助學習者從零搭建并理解一個可運行的企業級項目。壓縮包共6個文件包含3個mp4演示與部署視頻、1個rar項目源碼、1個sql數據庫腳本和1個docx部署文檔整體約105.85MB。視頻分別演示項目運行效果、部署步驟及數據庫表結構文檔則對系統架構、環境配置和操作流程做了詳細說明。目前已有1800人學習下載適合作為畢業設計參考、項目實戰練手或技術面試準備材料能夠有效縮短從需求分析到部署上線的學習路徑。1. 為什么基于 Java 的圖書借閱管理系統是畢業設計里最耐問的選題每年畢業設計選題表里“基于 Java 的圖書借閱管理系統”都會占據一席。它不新潮但足夠穩圖書、讀者、借閱記錄能干凈地映射成數據庫表Java 代碼剛好覆蓋增刪改查、事務、權限控制這幾條核心鏈路工作量適合一個人獨立完成答辯時每個設計點都能講出因果。真正拿到中途接手這個題目時多數同學面對的不只是“寫代碼”而是把一份“源代碼數據庫部署文檔部署視頻”從解壓到能跑、能演示、能回答老師追問的完整過程。這篇文章不只講 CRUD而是把數據模型怎么設計、JDBC 連接怎么配置、借閱歸還事務怎么保證一致、部署視頻沒提到的坑怎么填全部按順序講清照著做可以復現也能在答辯現場解釋清楚每個步驟的取舍。2. 從 ER 圖到建表圖書借閱管理系統的數據模型設計2.1 圖書、讀者、借閱記錄三張核心表怎么拆不管界面用什么框架底層一定是關系型數據庫。常見的最小可用模型有四張表讀者表t_reader、圖書表t_book、分類表t_category、借閱記錄表t_borrow。分類表看起來可以不做但加上之后圖書檢索和統計會順手很多畫 ER 圖時也多一層關系數據庫課程設計說明能寫得更完整。CREATE DATABASE IF NOT EXISTS book_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE book_manager; CREATE TABLE t_reader ( id INT PRIMARY KEY AUTO_INCREMENT, reader_no VARCHAR(20) NOT NULL UNIQUE COMMENT 讀者編號如 R2024001, reader_name VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL COMMENT 存 MD5 或 SHA-256 后的值, role TINYINT NOT NULL DEFAULT 0 COMMENT 0讀者1管理員, max_borrow INT NOT NULL DEFAULT 5 COMMENT 最大可借數量, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL UNIQUE ); CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), category_id INT, total_count INT NOT NULL DEFAULT 1, available_count INT NOT NULL DEFAULT 1, FOREIGN KEY (category_id) REFERENCES t_category(id) ); CREATE TABLE t_borrow ( id INT PRIMARY KEY AUTO_INCREMENT, borrow_no VARCHAR(32) NOT NULL UNIQUE, reader_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, due_time DATETIME NOT NULL, return_time DATETIME DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0借出1已還2超期未還, FOREIGN KEY (reader_id) REFERENCES t_reader(id), FOREIGN KEY (book_id) REFERENCES t_book(id) );建表語句里值得注意的參數有三個。第一字符集用utf8mb4不是utf8否則生僻字或表情符號寫入時報錯或者變成問號。第二t_book把total_count和available_count分開存是為了在圖書列表頁直接展示“館藏/剩余”不用每次用借閱記錄反查算庫存。第三t_borrow里的due_time設為NOT NULL借出時就確定應還日期避免后面判斷超期時出現空值。status字段用 0/1/2 表示三種狀態而不是直接刪除借閱記錄這樣歷史留痕超期統計也簡單。四張表的關系整理成表結構摘要方便答辯時講表名作用關鍵字段關聯關系t_reader讀者與登錄賬號合表reader_no、role、max_borrow一對多借閱t_category圖書分類category_name一對多圖書t_book圖書與庫存total_count、available_count多對一分類t_borrow借閱流水due_time、return_time、status多對一讀者與圖書2.2 借閱記錄里做冗余字段還是每次都 join 三張表軟件工程課程會反復強調范式但課程設計代碼追求的是在可維護的前提下少寫幾層 join。借閱列表頁要顯示“讀者姓名、書名、借出日期、狀態”如果嚴格三范式每次查詢都要 joint_reader和t_bookSQL 長索引消耗也大。更常見、也更適合這個體量的做法是在t_borrow里冗余兩個快照字段reader_name和book_name在插入借閱記錄時從當前讀者、圖書信息里帶過來。這兩個冗余字段帶來的收益很實際借閱列表查詢從三表 join 降成單表查詢報表統計也快讀者或圖書改名后歷史借閱記錄保留的是借出時刻的現場反而更符合“當時誰借了哪本書”的事實。代價是如果做了“讀者修改姓名”功能歷史記錄不會跟著變。這個取舍在答辯時正好能講出理由日志型數據保留的是歷史快照不以最新數據為準。相比“完全遵守范式但代碼到處 join”或者“完全不建外鍵”這種中間態是課程設計里最常見的可靠方案。如果你看到原始包里的t_borrow沒有冗余字段也可以用視圖解決CREATE VIEW v_borrow_list AS SELECT b.id, r.reader_name, bk.book_name, b.borrow_time, b.due_time, b.status FROM t_borrow b JOIN t_reader r ON b.reader_id r.id JOIN t_book bk ON b.book_id bk.id;視圖在映射工具不拆開 SQL 的情況下能少改 Java 代碼。但它不是物理表適合展示不適合在 where 里再疊復雜條件。2.3 初始化腳本與字符集參數從 SQL 文件到 MySQL 實例拿到數據庫腳本后第一步不是雙擊而是用命令行導入并確認字符集。推薦的方式是mysql -u root -p --default-character-setutf8mb4 -e source /path/to/book_manager.sql這條命令的關鍵點是--default-character-setutf8mb4。很多部署文檔只寫source結果腳本里中文注釋或初始化的中文數據變成亂碼。如果腳本文件本身就包含CREATE DATABASE用source方式沒問題如果腳本只有CREATE TABLE就得先用CREATE DATABASE book_manager建庫再USE。兩種方式在部署視頻里經常被含糊帶過實際差別就在報錯ERROR 1046 (3D000): No database selected時能否反應過來。如果手頭只有 MySQL Workbench可以用 Server 菜單里的 Data Import但導入前要把目標 schema 選對。初始化完成后做個快速檢查USE book_manager; SHOW TABLES; SELECT COUNT(*) AS category_count FROM t_category;SHOW TABLES確認四張表都建齊COUNT(*)確認初始分類數據沒有因為字符集問題丟失。到這一步數據庫層已經立住了再接 Java 就只是連接細節。3. Java 代碼落點登錄校驗、圖書增刪改查與借閱歸還事務3.1 JDBC 連接封裝驅動類名、時區參數與釋放資源Java 連接 MySQL 最常見的報錯來自三處驅動 jar 沒放進 lib、URL 里缺少時區參數、資源沒關閉導致連接耗盡。以 MySQL 8.x 為例最小可用的連接工具類是這樣import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DbUtil { private static final String URL jdbc:mysql://localhost:3306/book_manager?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD root; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception e) { e.printStackTrace(); } } } } }連接串里的serverTimezoneAsia/Shanghai不能省MySQL 8.x 驅動默認要求顯式時區否則直接拋The server time zone value異常。characterEncodingutf8與數據庫端utf8mb4配合保證中文寫入不亂碼。驅動類的全限定名要嚴格匹配 jar 版本MySQL Connector/J 8.x 是com.mysql.cj.jdbc.Driver5.x 是com.mysql.jdbc.Driver這個問題在換 jar 時最容易踩。close(AutoCloseable...)用可變參數接收Connection、Statement、ResultSet調用時按順序傳入即可。3.2 登錄校驗PreparedStatement 參數綁定而不是拼接字符串圖書借閱系統的登錄入口通常只校驗讀者編號和密碼但這一處恰恰是 SQL 注入的重災區。課程設計階段養成參數綁定習慣答辯被問“怎么防止 SQL 注入”時就能直接回答。推薦的做法是用PreparedStatement占位符public boolean checkLogin(String readerNo, String password) throws SQLException { String sql SELECT id FROM t_reader WHERE reader_no ? AND password ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, readerNo); ps.setString(2, encrypt(password)); try (ResultSet rs ps.executeQuery()) { return rs.next(); } } }這段代碼的關鍵在ps.setString(1, readerNo)和ps.setString(2, encrypt(password))。占位符里的值由驅動做轉義用戶輸入里的單引號不會破壞 SQL 結構。密碼不能存明文encrypt方法常見做法是 MD5但課程設計如果想要加分可以用 SHA-256 加鹽private static String encrypt(String raw) throws Exception { MessageDigest digest MessageDigest.getInstance(SHA-256); byte[] hash digest.digest((raw book_manager_salt).getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : hash) { sb.append(String.format(%02x, b)); } return sb.toString(); }注意登錄校驗通過后要把role字段同時查出來放進 session 或內存對象后續圖書管理功能靠這個字段判斷當前操作者是管理員還是普通讀者。如果只返回id界面上還得再查一次數據庫。3.3 借閱與歸還事務原子扣減庫存兩條 SQL 要同時成功借書操作涉及兩處寫操作t_book.available_count減一t_borrow插入一條借閱記錄。如果庫存減了但流水沒插上數據就不一致反過來也不行。解決辦法是放在同一個數據庫事務里public void borrow(int bookId, int readerId) throws SQLException { Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); String updateSql UPDATE t_book SET available_count available_count - 1 WHERE id ? AND available_count 0; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, bookId); if (ps.executeUpdate() 0) { throw new RuntimeException(庫存不足扣減失敗); } } String insertSql INSERT INTO t_borrow(borrow_no, reader_id, book_id, due_time) VALUES(?,?,?,DATE_ADD(NOW(), INTERVAL 30 DAY)); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setString(1, B System.currentTimeMillis()); ps.setInt(2, readerId); ps.setInt(3, bookId); ps.executeUpdate(); } conn.commit(); } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw e; } finally { if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }這段代碼里最值得講的是UPDATE ... WHERE available_count 0。它把“查詢庫存是否充足”和“庫存減一”合并成一條原子 SQL避免先 SELECT 再 UPDATE 造成的并發超借。setAutoCommit(false)之后必須手動conn.commit()任何一個環節拋異常就conn.rollback()。DATE_ADD(NOW(), INTERVAL 30 DAY)把應還日期定在借出后 30 天這個 30 可以從參數配置不要寫死在 SQL 里。歸還操作是同一套路先UPDATE t_borrow SET status 1, return_time NOW()再UPDATE t_book SET available_count available_count 1。歸還時判斷是否超期只用在更新狀態前比較due_time與當前時間把超期部分也放進同一個事務即可。3.4 圖書查詢的模糊匹配LIKE 占位符的坑圖書檢索通常按書名模糊匹配很多初寫代碼的同學會這樣寫String sql SELECT * FROM t_book WHERE book_name LIKE %?%;這行執行時一定報參數數量錯誤因為?被寫進了字符串字面量PreparedStatement 認為沒有參數。正確寫法有兩種一種是用CONCAT拼接參數String sql SELECT id, book_name, author, available_count FROM t_book WHERE book_name LIKE CONCAT(%, ?, %); PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, keyword);另一種是查詢條件里先拼好帶百分號的字符串再傳參。兩者都能用但CONCAT寫法更清晰索引行為上兩者一致前置通配符都不走最優索引。搜索框如果允許輸入書名、作者、ISBN 三個條件可以在 Java 側動態拼接 WHEREStringBuilder sql new StringBuilder(SELECT * FROM t_book WHERE 11 ); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append(AND (book_name LIKE CONCAT(%, ?, %) OR author LIKE CONCAT(%, ?, %)) ); params.add(keyword); params.add(keyword); }WHERE 11是一種習慣寫法方便動態拼接條件而不必判斷是否要加 AND。參數按出現順序放入List循環綁定能減少大量重復代碼。注意模糊查詢結果集如果直接展示給用戶最好限制返回條數在 SQL 尾部加LIMIT 100防止SELECT *掃出全表導致 Swing 界面卡頓。錯誤寫法報錯現象正確寫法LIKE %?%PreparedStatement 參數不匹配LIKE CONCAT(%, ?, %)Statement字符串拼接中文內容易注入且轉義麻煩PreparedStatement參數綁定不帶LIMIT全表掃描界面卡頓加LIMIT 100或分頁4. 部署這一關環境變量、MySQL 導入與部署文檔沒寫透的細節4.1 Java 環境變量配置與 JDK 版本對齊拿到壓縮包第一步先確認本機 Java 環境。在命令行里執行java -version javac -version兩個版本必須一致。常見問題是java -version顯示 JDK 17javac -version顯示 JDK 8原因是 PATH 里前面某個目錄的 java.exe 和后面某個目錄的 javac.exe 不是同一套。Windows 下部署文檔里最常用的設置方式set JAVA_HOMEC:\Program Files\Java\jdk1.8.0_202 set PATH%JAVA_HOME%\bin;%PATH%JAVA_HOME是給 Maven、Tomcat、IDEA 用的PATH讓命令行直接認識java和javac。這里容易踩的坑是環境變量配好后必須重開命令行窗口才生效因為環境變量只在進程啟動時讀取一次。課程設計通常不需要太高版本如果項目里沒有用到高版本新特性JDK 8 和 JDK 11 選一個穩定的即可。項目里如果用了var、List.of這類語法就必須按對應版本的 JDK 編譯。4.2 MySQL 導入數據庫腳本與字符集對齊部署文檔里最含糊的一句話往往是“導入數據庫”。實際導入時推薦完整操作mysql -u root -p --default-character-setutf8mb4 source /path/to/book_manager.sql如果腳本文件里已經有CREATE DATABASE book_manager那么導入完成后執行USE book_manager;再查表如果腳本里只有建表語句需要先手動建庫mysql -u root -p -e CREATE DATABASE book_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;導入過程中出現ERROR 1366 (HY000): Incorrect string value就是這個表或連接設置的字符集不對優先檢查連接命令有沒有加--default-character-setutf8mb4。導入完成后做一個快速驗證mysql -u root -p -e USE book_manager; SHOW TABLES; SELECT COUNT(*) FROM t_book;SHOW TABLES應該看到四張表SELECT COUNT(*)的結果應該和腳本里的初始數據數一致。如果這里結果不對后續 Java 登錄一定報“表不存在”或空結果問題在數據庫層先別改代碼。4.3 打包可執行 jar依賴 jar 要一起打進去Java 項目交付時不能只交 class 文件或源碼最常見的是打成可執行 jar。在 IDEA 里通過 File 菜單的 Project Structure 做 Artifacts或者用 Maven 打包mvn clean package java -jar target/book-manager.jar關鍵點在于 javase 項目里 mysql 驅動是第三方依賴普通 jar 打包默認不會把依賴打進去。運行時報ClassNotFoundException: com.mysql.cj.jdbc.Driver就是缺依賴。兩個解決辦法把 jar 放進項目同級的lib目錄用java -cp lib/*;book-manager.jar com.bookmanager.Main啟動或者用 Maven 的maven-assembly-plugin打進一個 fat jar。部署文檔里如果只寫了java -jar大概率是已經打過 fat jar 的版本。工作目錄也要注意。如果代碼里有相對路徑讀取配置文件或數據庫初始化文件比如./conf/db.properties那么 jar 放在哪個目錄運行時的相對路徑就基于哪個目錄不一定是 jar 所在的目錄。這就是很多用戶把 jar 復制到桌面運行后突然連不上數據庫的原因。可以這樣做cd /path/to/deploy java -jar book-manager.jar -Ddb.config./conf/db.properties先把所有外部配置明確放進conf目錄再用-D指定部署時只要整個目錄一起拷貝。4.4 部署視頻里經常被跳過的三個隱藏坑現象原因處理MySQL 啟動失敗3306 端口被占用netstat -ano | findstr 3306查占用進程連接報 Public Key Retrieval is not allowedMySQL 8 的 caching_sha2_password 插件URL 加allowPublicKeyRetrievaltrue調試顯示“當前不會命中斷點”運行的是打包產物而非編譯輸出目錄改用 Debug 模式啟動確認 Artifacts 輸出目錄allowPublicKeyRetrievaltrue對本地開發和課程設計是安全的但部署到別人電腦上時建議在連接串里同時保留useSSLfalse避免協議協商階段報出無關警告。端口占用問題在 Windows 上最常見的元兇是之前裝的另一個 MySQL 服務沒停或者mysqld以服務形式自動啟動視頻里不會說排查時先看服務列表。5. 答辯演示順序與一個可復現的功能自檢清單5.1 按角色路徑演示10 分鐘講完答辯現場不要漫無目的地亂點界面按兩條路徑演示先普通讀者登錄執行圖書查詢、借書、還書再管理員登錄新增圖書、修改庫存、查詢借閱流水。數據庫表結構與剛才講的增刪改查動作一一對應演示時順手打開 MySQL 客戶端查一次t_book.available_count在借書后的變化這個操作比口頭解釋更有說服力。演示環節操作動作對應代碼/表管理員登錄輸入 admin 賬號t_reader.role 1新增圖書填 ISBN、書名、庫存t_book INSERT讀者借書掃碼借出t_borrow INSERT available_count - 1讀者還書確認歸還t_borrow UPDATE available_count 1借閱查詢按讀者編號查流水t_borrow SELECT 按 reader_id5.2 用 EXPLAIN 驗證查詢與一個 10 分鐘自檢腳本答辯被問“數據庫有沒有優化”時不要只回答“建了索引”可以用 EXPLAIN 展示一個實際執行計劃EXPLAIN SELECT * FROM t_borrow WHERE reader_id 1 AND status 0;關注輸出里的type列和possible_keys。如果type是ALL說明全表掃描建了idx_reader_status(reader_id, status)之后再看type會變成ref這就是聯合索引的直觀效果。部署交付前跑一遍下面的自檢腳本可以避開 80% 的現場事故echo 1. Java 版本 java -version echo 2. 數據庫連接 mysql -u root -p -e SELECT 1 echo 3. 數據表完整性 mysql -u root -p -e USE book_manager; SHOW TABLES; echo 4. 依賴 jar 完整性 jar tf book-manager.jar | grep -i mysql腳本第 4 步的jar tf是查看 jar 包內部文件列表的命令如果輸出里沒有 mysql 驅動相關類名說明這個 jar 不是 fat jar換一臺沒裝驅動的機器就會跑不起來。自檢通過后再錄部署視頻視頻里每一步對應的就是上面這幾個命令用戶照著走不會遇到“文檔說點下一步但我這里報錯”的情況。本文還有配套的精品資源點擊獲取