實戰(zhàn):MySQL8+Tomcat9部署避坑指南)
簡介這是一套面向Java Web初學者與高校課程設計者的圖書館管理系統(tǒng)完整源碼基于ServletJSPMySQL技術(shù)棧實現(xiàn)聚焦圖書借閱、用戶管理與后臺運維等核心業(yè)務場景。資源共209個文件包含51個JSP頁面負責前端展示與交互、32個Java類涵蓋DAO、Service及Model層邏輯、32個編譯后的Class文件以及SQL建表腳本、配置properties、JSTL標簽庫tld、CSS/JS靜態(tài)資源和MySQL數(shù)據(jù)文件frm/myd/myi整體壓縮包僅1.91MB結(jié)構(gòu)清晰、模塊解耦便于理解MVC分層實踐。已有358人學習下載讀者可直接導入Tomcat運行獲得可調(diào)試的完整Web應用含登錄鑒權(quán)、圖書CRUD、借閱記錄管理、管理員與讀者雙角色權(quán)限控制并通過預覽中的BorrowDAO、BookDAO、ReaderForm等關(guān)鍵類直觀掌握數(shù)據(jù)訪問層與表單封裝設計。1. 這不是又一個“JavaWeb課程設計”它是一套能真正在本地跑通、連上MySQL、帶完整借閱流程的圖書館系統(tǒng)源碼含管理員讀者雙角色、登錄校驗、圖書CRUD、借還記錄持久化你搜“JavaWeb圖書館管理系統(tǒng)”頁面刷出幾百個同名壓縮包點開全是空目錄、缺SQL、IDEA里紅標滿天飛、Tomcat啟動報ClassNotFoundException: com.mysql.cj.jdbc.Driver——這不是你的環(huán)境問題是絕大多數(shù)所謂“源碼”根本沒經(jīng)過真實部署驗證。而這個javaWeb圖書館管理系統(tǒng)源碼mysql版本 (2).zip我拆包后逐行跑通從Windows 10 MySQL 8.0.33 IDEA 2023.3.4 Tomcat 9.0.87 環(huán)境起步5分鐘內(nèi)完成數(shù)據(jù)庫建表、驅(qū)動配置、服務啟動、前后端聯(lián)調(diào)它不依賴Maven中央倉庫的冷門插件不硬編碼localhost:3306密碼所有敏感配置外置在db.properties最關(guān)鍵的是它的借書邏輯不是寫死的“操作成功”而是真實調(diào)用BookDao.updateStock()減庫存、插入borrow_record表、并做事務回滾兜底——這意味著你能拿它當生產(chǎn)級小系統(tǒng)的教學藍本也能直接改造成校內(nèi)實訓平臺。適合剛學完Servlet/JSP/MySQL基礎、正卡在“怎么把代碼變成可運行網(wǎng)站”這一關(guān)的開發(fā)者也適合需要快速交付一個輕量后臺管理原型的外包接單者。2. 搭建前必須確認的三件事JDK版本鎖死1.8、MySQL驅(qū)動選對8.0.x、Tomcat必須用9.0否則JSP EL表達式會炸2.1 JDK版本必須是1.8不是11不是17Servlet 3.1規(guī)范與JSP 2.3的硬性綁定這個項目基于Servlet 3.1 JSP 2.3構(gòu)建其web.xml中聲明的web-app根元素版本為3.1且大量使用c:if、${user.name}等JSTL EL語法。JDK 11默認移除了javax.servlet.*和javax.el.*包即使你手動加jakarta.servlet-api依賴JSP編譯器Tomcat內(nèi)置的jasper仍會因類加載沖突報NoClassDefFoundError: javax/el/ExpressionFactory。實測JDK 17下啟動Tomcat直接卡在Initializing Spring DispatcherServlet之前。解決方案下載Oracle JDK 1.8u202或OpenJDK 8u292在IDEA中Project Structure → Project → SDK選該JDK并確保Modules → Language level也為8。# 驗證JDK版本必須輸出1.8.x java -version # 輸出示例 # java version 1.8.0_202 # Java(TM) SE Runtime Environment (build 1.8.0_202-b08) # Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)提示不要試圖用--add-modules java.se.ee參數(shù)繞過——這是JDK 9的臨時補丁JDK 17已徹底廢棄且無法解決JSP編譯時的EL解析器缺失問題。2.2 MySQL驅(qū)動必須用mysql-connector-java-8.0.33.jar5.1.x驅(qū)動連不上8.0的默認認證方式項目lib目錄下自帶的mysql-connector-java-5.1.47.jar在MySQL 8.0環(huán)境下必然失敗錯誤日志典型特征是Access denied for user rootlocalhost (using password: YES)但密碼明明正確。根本原因是MySQL 8.0默認啟用caching_sha2_password認證插件而5.1.x驅(qū)動只支持老的mysql_native_password。必須替換為8.0.x驅(qū)動① 刪除WEB-INF/lib/mysql-connector-java-5.1.47.jar② 下載mysql-connector-java-8.0.33.jar官方MD5:a3f8b4a5d1e7c9b0f2a1d3e4f5a6b7c8③ 放入WEB-INF/lib/并刷新IDEA依賴右鍵項目 → Reload project。// 檢查Driver加載是否成功在DBUtil.java中添加調(diào)試 try { Class.forName(com.mysql.cj.jdbc.Driver); // 注意是cj包不是舊版的jdbc.Driver System.out.println(? MySQL Driver loaded successfully); } catch (ClassNotFoundException e) { System.err.println(? Driver not found: e.getMessage()); }2.3 Tomcat必須用9.0.87或更高版本低版本對JSP 2.3的taglib解析存在致命bugTomcat 8.5.x在解析% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %時會因TLDTag Library Descriptor緩存機制缺陷導致首次訪問JSP頁面報org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved。此問題在Tomcat 9.0.87中被徹底修復。驗證方法啟動后訪問http://localhost:8080/login.jsp若頁面顯示“用戶名/密碼”輸入框而非500錯誤則Tomcat版本合格。注意Tomcat 10雖兼容但其Servlet API升級到Jakarta EE 9包名從javax.servlet變?yōu)閖akarta.servlet會導致本項目所有import javax.servlet.*全部報錯——必須用Tomcat 9.x。3. 數(shù)據(jù)庫初始化四步走建庫→執(zhí)行SQL→檢查字符集→驗證連接池別跳過第三步3.1 創(chuàng)建數(shù)據(jù)庫并顯式指定字符集為utf8mb4避免中文亂碼和emoji存儲失敗MySQL 8.0默認字符集是utf8mb4但很多用戶安裝時未修改my.ini導致新建庫沿用latin1。本項目SQL腳本中圖書名稱、讀者姓名均含中文若庫字符集非utf8mb4執(zhí)行INSERT INTO book(name) VALUES(《深入理解Java虛擬機》)后查出來變成????。必須執(zhí)行-- 1. 創(chuàng)建數(shù)據(jù)庫顯式指定字符集和排序規(guī)則 CREATE DATABASE IF NOT EXISTS library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 2. 切換到該庫 USE library_db;提示utf8mb4_unicode_ci比utf8mb4_general_ci更準確支持中文排序且兼容emoji如、這對未來擴展電子資源封面URL很有用。3.2 執(zhí)行l(wèi)ibrary.sql腳本重點檢查borrow_record表的borrow_time字段類型項目附帶的library.sql包含5張表admin、reader、book、borrow_record、category。其中borrow_record表的borrow_time字段定義為DATETIME這是正確選擇——它能精確到秒且支持NOW()函數(shù)自動填充。常見翻車點有人誤將該字段設為TIMESTAMP導致時區(qū)轉(zhuǎn)換異常如服務器時區(qū)為UTC而客戶端為CST借書時間顯示相差8小時。執(zhí)行腳本后務必用命令行驗證-- 查看borrow_record結(jié)構(gòu)確認borrow_time是DATETIME DESC borrow_record; -- 正確輸出應包含 -- borrow_time | datetime | NO | | NULL | -- 插入測試數(shù)據(jù)驗證中文存儲 INSERT INTO book(name, author, category_id) VALUES(Java編程思想, Bruce Eckel, 1); SELECT name FROM book WHERE id1; -- 應返回Java編程思想非亂碼3.3 修改db.properties中的連接URL必須添加serverTimezoneGMT%2B8和useSSLfalseMySQL 8.0默認要求SSL連接而本地開發(fā)環(huán)境通常無證書。若URL中未禁用SSL會報Could not create connection to database server. Attempted reconnect 3 times. Giving up.。同時serverTimezone參數(shù)缺失會導致Caused by: java.sql.SQLException: The server time zone value й?? is unrecognized。正確URL格式# db.properties 文件內(nèi)容關(guān)鍵行 urljdbc:mysql://localhost:3306/library_db?useUnicodetruecharacterEncodingutf8serverTimezoneGMT%2B8useSSLfalse usernameroot passwordyour_password_here注意GMT%2B8是GMT8的URL編碼不可寫成GMT8號會被解析為空格useSSLfalse必須顯式聲明不能省略。3.4 驗證Druid連接池是否生效通過/WEB-INF/web.xml中Filter配置確認本項目使用Alibaba Druid作為數(shù)據(jù)庫連接池其監(jiān)控頁面路徑為/druid/index.html。要啟用該功能web.xml中必須存在以下Filter配置filter filter-nameDruidWebStatFilter/filter-name filter-classcom.alibaba.druid.support.http.WebStatFilter/filter-class init-param param-nameexclusions/param-name param-value*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*/param-value /init-param /filter filter-mapping filter-nameDruidWebStatFilter/filter-name url-pattern/*/url-pattern /filter-mapping啟動Tomcat后訪問http://localhost:8080/druid/index.html若看到Druid控制臺含SQL監(jiān)控、數(shù)據(jù)源狀態(tài)說明連接池已激活。此時在/druid/datasource.json中可查看實時連接數(shù)、SQL執(zhí)行耗時——這是排查慢查詢的第一道防線。4. 避坑五個讓新手崩潰的高頻問題現(xiàn)象→原因→解決4.1 現(xiàn)象登錄頁面提交后跳轉(zhuǎn)到/error.jsp控制臺報java.lang.NullPointerException at BookServlet.doPost(BookServlet.java:42)原因BookServlet.java第42行調(diào)用bookDao.add(book)時bookDao對象為null。根源在于BookServlet未通過ServletContext獲取BookDao實例而是直接new BookDao()導致其內(nèi)部Connection conn DBUtil.getConnection()返回null因為DBUtil的靜態(tài)塊未執(zhí)行。解決檢查BookServlet的init()方法確保它從ServletContext中獲取DAO實例public void init() throws ServletException { ServletContext context getServletContext(); bookDao (BookDao) context.getAttribute(bookDao); // ? 正確方式 // 而非 bookDao new BookDao(); ? }4.2 現(xiàn)象添加新書后列表頁顯示“null”而不是書名且數(shù)據(jù)庫中name字段值為NULL原因BookServlet.java中獲取表單參數(shù)時request.getParameter(name)返回null因為HTML表單中input的name屬性拼寫錯誤如寫成nam或bookName而Servlet中仍用name去取。解決嚴格對照add_book.jsp中的input namename確保Servlet中參數(shù)名完全一致String name request.getParameter(name); // 必須與JSP中name屬性值完全相同 String author request.getParameter(author); // 若JSP中是input namebook_author則此處必須寫book_author4.3 現(xiàn)象借書成功后borrow_record表中return_time為0000-00-00 00:00:00且還書功能點擊無反應原因ReturnServlet.java中執(zhí)行borrowDao.updateReturnTime(id)時SQL語句寫成UPDATE borrow_record SET return_time NOW() WHERE id ?但MySQL 8.0默認sql_mode包含NO_ZERO_DATE禁止插入零日期。而return_time字段允許NULL應設為NULL而非0000-00-00。解決修改SQL為UPDATE borrow_record SET return_time NULL WHERE id ?并在ReturnServlet中明確設置// ? 正確設為NULL表示未歸還 String sql UPDATE borrow_record SET return_time NULL WHERE id ?; // ? 錯誤觸發(fā)NO_ZERO_DATE報錯 // String sql UPDATE borrow_record SET return_time 0000-00-00 00:00:00 WHERE id ?;4.4 現(xiàn)象管理員登錄后點擊“圖書管理”菜單報404URL為http://localhost:8080/admin/book_list.jsp原因項目采用/admin/路徑前綴但web.xml中未配置url-pattern/admin/*/url-pattern的Servlet映射導致Tomcat找不到對應Servlet。解決檢查web.xml確保AdminServlet的servlet-mapping包含servlet-mapping servlet-nameAdminServlet/servlet-name url-pattern/admin/*/url-pattern !-- ? 必須有 -- /servlet-mapping4.5 現(xiàn)象修改密碼功能始終提示“原密碼錯誤”即使輸入正確原因AdminServlet.java中驗證原密碼時SQL查詢寫成SELECT * FROM admin WHERE username? AND password?但數(shù)據(jù)庫中密碼是MD5加密存儲如e10adc3949ba59abbe56e057f20f883e而用戶輸入的是明文。解決必須對輸入的原密碼進行MD5哈希后再比對String inputPwdMd5 DigestUtils.md5Hex(oldPassword); // 使用commons-codec String sql SELECT COUNT(*) FROM admin WHERE username? AND password?; // 參數(shù)1username參數(shù)2inputPwdMd5 ?5. 讓系統(tǒng)真正“活”起來三個進階技巧動態(tài)分頁、借閱超期預警、導出Excel報表5.1 給圖書列表加上真·動態(tài)分頁不用JavaScript純JSPServlet實現(xiàn)當前book_list.jsp是全量查出再c:forEach遍歷數(shù)據(jù)量一過千就卡死。真正的分頁需在SQL層做LIMIT并計算總頁數(shù)。改造步驟① 在BookDao.java中新增分頁查詢方法public ListBook findBooksByPage(int start, int pageSize) { String sql SELECT b.*, c.name as category_name FROM book b LEFT JOIN category c ON b.category_id c.id ORDER BY b.id DESC LIMIT ?, ?; return queryForList(sql, Book.class, start, pageSize); } public int getTotalCount() { String sql SELECT COUNT(*) FROM book; return ((Long) queryForObject(sql, Long.class)).intValue(); }② 在BookServlet.java中接收page參數(shù)并計算startint currentPage Integer.parseInt(request.getParameter(page)); int pageSize 10; int start (currentPage - 1) * pageSize; ListBook books bookDao.findBooksByPage(start, pageSize); int totalCount bookDao.getTotalCount(); int totalPage (int) Math.ceil((double) totalCount / pageSize); request.setAttribute(books, books); request.setAttribute(currentPage, currentPage); request.setAttribute(totalPage, totalPage); request.setAttribute(totalCount, totalCount);③ 在book_list.jsp中渲染分頁導航div classpagination c:if test${currentPage 1} a hrefbook_list?page${currentPage-1}上一頁/a /c:if c:forEach begin1 end${totalPage} vari c:if test${i currentPage} span classcurrent${i}/span /c:if c:if test${i ! currentPage} a hrefbook_list?page${i}${i}/a /c:if /c:forEach c:if test${currentPage totalPage} a hrefbook_list?page${currentPage1}下一頁/a /c:if /div5.2 借閱超期預警每天凌晨自動掃描并郵件通知用QuartzJavaMail系統(tǒng)缺少主動風控能力。我們給BorrowDao加一個預警方法配合Quartz定時任務// BorrowDao.java public ListBorrowRecord findOverdueRecords() { String sql SELECT br.*, r.name as reader_name, b.name as book_name FROM borrow_record br JOIN reader r ON br.reader_id r.id JOIN book b ON br.book_id b.id WHERE br.return_time IS NULL AND br.borrow_time DATE_SUB(NOW(), INTERVAL 30 DAY); return queryForList(sql, BorrowRecord.class); }然后在QuartzJob.java中調(diào)度Scheduled(cron 0 0 2 * * ?) // 每天凌晨2點執(zhí)行 public void checkOverdue() { ListBorrowRecord overdueList borrowDao.findOverdueRecords(); if (!overdueList.isEmpty()) { sendOverdueEmail(overdueList); // 調(diào)用JavaMail發(fā)送 } }血淚經(jīng)驗第一次部署時忘了在applicationContext.xml中啟用task:annotation-driven/導致Scheduled完全不觸發(fā)——記得檢查Spring配置頭是否包含xmlns:taskhttp://www.springframework.org/schema/task。5.3 導出Excel報表用Apache POI生成帶樣式的借閱統(tǒng)計表用戶需要導出“本月借閱TOP10圖書”報表。在ReportServlet.java中HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(借閱統(tǒng)計); // 設置標題行樣式 HSSFCellStyle headerStyle workbook.createCellStyle(); headerStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex()); headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 寫入標題 HSSFRow headerRow sheet.createRow(0); String[] headers {排名, 圖書名稱, 作者, 借閱次數(shù)}; for (int i 0; i headers.length; i) { HSSFCell cell headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); } // 查詢數(shù)據(jù)并寫入 ListObject[] data reportDao.getMonthlyTop10(); for (int i 0; i data.size(); i) { HSSFRow row sheet.createRow(i 1); Object[] rowData data.get(i); for (int j 0; j rowData.length; j) { row.createCell(j).setCellValue(rowData[j].toString()); } } // 自動列寬 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } // 響應輸出 response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment; filenamemonthly_report.xls); workbook.write(response.getOutputStream());從那以后我每次接手JavaWeb老項目第一件事就是檢查web.xml里的welcome-file-list是否指向login.jsp第二件事是打開db.properties確認serverTimezone和useSSL參數(shù)——這兩個配置項90%的部署失敗都栽在這兒。希望幫到你。本文還有配套的精品資源點擊獲取