:JSP+MySQL+Tomcat畢設(shè)實(shí)戰(zhàn)項(xiàng)目)
簡(jiǎn)介本資源是一份面向計(jì)算機(jī)專(zhuān)業(yè)本科生的Java Web畢業(yè)設(shè)計(jì)項(xiàng)目文檔聚焦現(xiàn)代育兒場(chǎng)景下的信息化服務(wù)需求為初學(xué)者提供可運(yùn)行、易理解的完整系統(tǒng)實(shí)現(xiàn)方案。文檔詳細(xì)闡述了基于JSPJavaMySQL技術(shù)棧構(gòu)建的育兒助手系統(tǒng)涵蓋用戶端育兒知識(shí)瀏覽、資訊獲取、論壇互動(dòng)、智能問(wèn)答、個(gè)人中心與管理端資訊/輪播/用戶/知識(shí)/論壇/問(wèn)答等全維度后臺(tái)管理雙模塊設(shè)計(jì)邏輯與功能實(shí)現(xiàn)要點(diǎn)。資源為1個(gè)3.82MB的Word文檔.doc格式內(nèi)容包含開(kāi)題背景、系統(tǒng)分析、數(shù)據(jù)庫(kù)設(shè)計(jì)、核心代碼說(shuō)明、界面截圖及中英文摘要等典型畢設(shè)要素結(jié)構(gòu)規(guī)范、排版清晰適合作為課程設(shè)計(jì)參考、答辯材料模板或二次開(kāi)發(fā)基礎(chǔ)。目前已有200人學(xué)習(xí)下載對(duì)掌握J(rèn)SP動(dòng)態(tài)網(wǎng)頁(yè)開(kāi)發(fā)、MySQL數(shù)據(jù)建模及Java后端業(yè)務(wù)整合具有較強(qiáng)實(shí)踐指導(dǎo)價(jià)值。1. 基于Web育兒助手系統(tǒng)一個(gè)能跑通、能部署、能答辯的Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)項(xiàng)目這不是一個(gè)“概念演示”或“PPT系統(tǒng)”。它是一套真實(shí)可運(yùn)行、帶完整前后端交互、含管理員后臺(tái)與用戶前臺(tái)、數(shù)據(jù)庫(kù)表結(jié)構(gòu)清晰、JSP頁(yè)面邏輯閉環(huán)、連Tomcat啟動(dòng)報(bào)錯(cuò)都能定位到具體行號(hào)的本科級(jí)Java Web畢業(yè)設(shè)計(jì)落地包。我去年幫3個(gè)同學(xué)調(diào)試過(guò)這個(gè)項(xiàng)目——有人卡在MySQL中文亂碼有人死在JSP EL表達(dá)式語(yǔ)法錯(cuò)誤還有人把web.xml里servlet-mapping路徑寫(xiě)成/login.jsp而不是/login導(dǎo)致404刷屏。它解決的不是“有沒(méi)有”而是“能不能上線”首頁(yè)輪播圖能換、育兒知識(shí)能增刪、論壇帖子能發(fā)、智能問(wèn)答能查庫(kù)返回、投訴建議能存進(jìn)MySQL并被管理員看到。技術(shù)棧干凈利落JSP非Servlet裸寫(xiě)有MVC雛形、JavaJDBC直連無(wú)Spring Boot遮掩適合答辯講清流程、MySQL 5.7建表含外鍵、索引、中文字段、B/S架構(gòu)Chrome/Firefox直輸http://localhost:8080/baby/即進(jìn)首頁(yè)。適合兩類(lèi)人一是大四趕畢設(shè) deadline 的同學(xué)需要一份不踩坑、不翻車(chē)、能本地跑通、能導(dǎo)出war包、能應(yīng)對(duì)老師現(xiàn)場(chǎng)提問(wèn)的實(shí)操資源二是剛學(xué)完JDBC和JSP想練手的初學(xué)者它比“學(xué)生選課系統(tǒng)”更貼近生活場(chǎng)景比“圖書(shū)管理系統(tǒng)”多出論壇、問(wèn)答、輪播等真實(shí)模塊代碼量適中核心JSP頁(yè)28個(gè)Java類(lèi)19個(gè)SQL腳本1份改起來(lái)不暈講起來(lái)有料。2. 技術(shù)選型與環(huán)境搭建為什么是JSPMySQLTomcat而不是Spring Boot2.1 JSP不是過(guò)時(shí)技術(shù)而是畢業(yè)設(shè)計(jì)的“可控性錨點(diǎn)”很多人看到JSP第一反應(yīng)是“淘汰了”。但對(duì)本科畢設(shè)而言JSP恰恰是最可控的技術(shù)選擇。原因有三調(diào)試鏈路極短JSP修改 → 保存 → 刷新瀏覽器錯(cuò)誤直接打在頁(yè)面上如org.apache.jasper.JasperException: Unable to compile class for JSP你能立刻看到哪行EL表達(dá)式寫(xiě)錯(cuò)、哪個(gè)JavaBean沒(méi)import而Spring Boot改個(gè)Controller要重啟、看日志、查端口沖突新手容易陷入“改了但沒(méi)生效”的玄學(xué)狀態(tài)。MVC邊界清晰本項(xiàng)目雖未用Struts但已自然分層——JSP負(fù)責(zé)展示% %取值、c:forEach遍歷、JavaBean封裝數(shù)據(jù)User.java,Post.java、Servlet處理請(qǐng)求LoginServlet.java,AddPostServlet.java、DAO操作數(shù)據(jù)庫(kù)UserDAO.java。答辯時(shí)老師問(wèn)“怎么實(shí)現(xiàn)登錄驗(yàn)證”你指著LoginServlet.doPost()里userDAO.findByUsernameAndPassword()這行代碼再講JDBC連接池配置邏輯扎實(shí)不空洞。教學(xué)資源豐富Eclipse Tomcat MySQL組合B站搜“JSP學(xué)生管理系統(tǒng)”有上千個(gè)帶語(yǔ)音的實(shí)操視頻遇到ClassNotFoundException: com.mysql.cj.jdbc.Driver你知道該去WEB-INF/lib下放mysql-connector-java-8.0.33.jar而Spring Boot的application.yml配錯(cuò)新手常卡在YAML縮進(jìn)或spring.datasource.url里時(shí)區(qū)參數(shù)漏寫(xiě)。提示本項(xiàng)目JSP使用JSTL 1.2c:if、c:forEach需在WEB-INF/lib中放入jstl-1.2.jar和standard-1.1.2.jar。若用Tomcat 9注意JSTL版本兼容性——Tomcat 9默認(rèn)支持JSP 2.3JSTL 1.2完全可用無(wú)需升級(jí)到JSTL 1.3。2.2 MySQL 5.7選它不是因?yàn)椤靶隆倍且驗(yàn)椤胺€(wěn)”和“好教”項(xiàng)目摘要里寫(xiě)“MySQL數(shù)據(jù)庫(kù)”但沒(méi)指定版本。實(shí)測(cè)發(fā)現(xiàn)MySQL 5.7.44 是本項(xiàng)目的黃金版本。原因如下字符集開(kāi)箱即用安裝時(shí)勾選utf8mb4而非舊版utf8建庫(kù)語(yǔ)句CREATE DATABASE baby CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;后續(xù)所有表自動(dòng)繼承徹底規(guī)避“育兒知識(shí)”四個(gè)字存進(jìn)數(shù)據(jù)庫(kù)變????的血淚問(wèn)題。權(quán)限管理夠用不復(fù)雜畢設(shè)只需一個(gè)baby_user賬號(hào)賦予baby.*庫(kù)的SELECT,INSERT,UPDATE,DELETE權(quán)限即可。命令就一行CREATE USER baby_userlocalhost IDENTIFIED BY baby123; GRANT SELECT,INSERT,UPDATE,DELETE ON baby.* TO baby_userlocalhost; FLUSH PRIVILEGES;不像MySQL 8.0強(qiáng)制caching_sha2_password插件新手配JDBC URL時(shí)得加?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue一長(zhǎng)串參數(shù)極易出錯(cuò)。工具鏈成熟Navicat for MySQL 15、MySQL Workbench 8.0、甚至DOS下的mysql -u root -p都能無(wú)縫連接。導(dǎo)入SQL腳本時(shí)Navicat右鍵“運(yùn)行SQL文件”比命令行source baby.sql更直觀適合答辯前快速重裝數(shù)據(jù)庫(kù)。2.3 Tomcat 8.5畢業(yè)設(shè)計(jì)的“安全區(qū)”服務(wù)器為什么不用Tomcat 10因?yàn)門(mén)omcat 10全面轉(zhuǎn)向Jakarta EE 9所有javax.*包名改為jakarta.*如javax.servlet.http.HttpServlet→jakarta.servlet.http.HttpServlet而本項(xiàng)目所有Servlet仍用javax——這是JSP規(guī)范在Java EE 8時(shí)代的標(biāo)準(zhǔn)寫(xiě)法。Tomcat 8.5.94最新8.5.x版完美兼容支持JSP 2.3、Servlet 3.1、EL 3.0覆蓋項(xiàng)目全部語(yǔ)法內(nèi)存占用低Win10下啟動(dòng)僅占300MB內(nèi)存老筆記本也能跑conf/server.xml里Connector port8080直接暴露端口無(wú)需反向代理本地測(cè)試零配置。部署時(shí)將項(xiàng)目根目錄含WEB-INF文件夾整個(gè)拖進(jìn)webapps/下重命名為baby啟動(dòng)Tomcat后訪問(wèn)http://localhost:8080/baby/index.jsp即進(jìn)首頁(yè)。若遇404先檢查webapps/baby/WEB-INF/web.xml是否存在再確認(rèn)welcome-file-list里是否為welcome-fileindex.jsp/welcome-file。2.4 B/S架構(gòu)不是“為了用而用”而是解決真實(shí)部署痛點(diǎn)本項(xiàng)目堅(jiān)持B/SBrowser/Server而非C/SClient/Server核心動(dòng)因是降低交付與演示門(mén)檻學(xué)生交畢設(shè)老師不想裝客戶端答辯現(xiàn)場(chǎng)換電腦只要瀏覽器就能打開(kāi)管理員后臺(tái)/admin/路徑無(wú)需額外登錄態(tài)維護(hù)全靠Sessionrequest.getSession().setAttribute(admin, admin)關(guān)瀏覽器即退出安全又省事所有靜態(tài)資源CSS/JS/圖片放在webapps/baby/static/下路徑硬編碼為/baby/static/css/style.css避免相對(duì)路徑../css/style.css在不同層級(jí)JSP中失效。注意B/S意味著所有業(yè)務(wù)邏輯在服務(wù)端。例如“智能問(wèn)答”模塊用戶輸入關(guān)鍵詞JSP提交到SearchServletServlet調(diào)KnowledgeDAO.searchByKeyword(keyword)查MySQL再把結(jié)果List傳回JSP用c:forEach展示——沒(méi)有前端JavaScript發(fā)AJAX沒(méi)有Vue響應(yīng)式純服務(wù)端渲染。這正是答辯時(shí)老師想聽(tīng)的“數(shù)據(jù)從哪來(lái)怎么查怎么傳怎么顯”3. 核心功能實(shí)現(xiàn)從注冊(cè)登錄到智能問(wèn)答每一步都附可運(yùn)行代碼3.1 用戶注冊(cè)與登錄Session JDBC 密碼MD5三步閉環(huán)注冊(cè)流程不是簡(jiǎn)單存表而是包含數(shù)據(jù)校驗(yàn)、密碼加密、唯一性約束三重保障。關(guān)鍵代碼在RegisterServlet.java// RegisterServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username).trim(); String password request.getParameter(password); String email request.getParameter(email).trim(); // 1. 前端校驗(yàn)JSP中已有onsubmitreturn validateForm()此處二次校驗(yàn) if (username.length() 3 || username.length() 16) { request.setAttribute(error, 用戶名長(zhǎng)度3-16位); request.getRequestDispatcher(register.jsp).forward(request, response); return; } if (!email.matches(^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\\.[a-zA-Z]{2,}$)) { request.setAttribute(error, 郵箱格式不正確); request.getRequestDispatcher(register.jsp).forward(request, response); return; } // 2. 密碼MD5加密使用commons-codec-1.15.jar String encryptedPassword DigestUtils.md5Hex(password); // 3. 調(diào)用DAO檢查用戶名是否已存在 UserDAO userDAO new UserDAO(); if (userDAO.findByUsername(username) ! null) { request.setAttribute(error, 用戶名已被注冊(cè)); request.getRequestDispatcher(register.jsp).forward(request, response); return; } // 4. 創(chuàng)建User對(duì)象并插入 User user new User(); user.setUsername(username); user.setPassword(encryptedPassword); user.setEmail(email); user.setRegTime(new Date()); userDAO.insert(user); // 執(zhí)行INSERT INTO user VALUES(...) request.setAttribute(msg, 注冊(cè)成功請(qǐng)登錄); request.getRequestDispatcher(login.jsp).forward(request, response); }邏輯說(shuō)明與參數(shù)說(shuō)明DigestUtils.md5Hex(password)使用Apache Commons Codec庫(kù)比手寫(xiě)MessageDigest簡(jiǎn)潔且commons-codec-1.15.jar已放入WEB-INF/libuserDAO.findByUsername(username)DAO層執(zhí)行SELECT * FROM user WHERE username ?查到記錄即返回User對(duì)象否則返回nullrequest.getRequestDispatcher(xxx.jsp).forward()服務(wù)端跳轉(zhuǎn)URL不變r(jià)equest域中屬性如error、msg可被目標(biāo)JSP獲取關(guān)鍵避坑點(diǎn)username.trim()防止用戶輸空格注冊(cè)email.matches(...)正則確保郵箱基礎(chǔ)格式數(shù)據(jù)庫(kù)user.username字段設(shè)為UNIQUE索引雙重防重。登錄邏輯在LoginServlet.java核心是比對(duì)MD5密碼// LoginServlet.java User user userDAO.findByUsernameAndPassword(username, DigestUtils.md5Hex(password)); if (user ! null) { // 登錄成功存入Session HttpSession session request.getSession(); session.setAttribute(user, user); // 后續(xù)所有頁(yè)面可通過(guò)${sessionScope.user.username}取值 response.sendRedirect(index.jsp); // 重定向避免F5刷新重復(fù)提交 } else { request.setAttribute(error, 用戶名或密碼錯(cuò)誤); request.getRequestDispatcher(login.jsp).forward(request, response); }提示response.sendRedirect()是重定向URL變?yōu)?baby/index.jsprequest.getRequestDispatcher().forward()是轉(zhuǎn)發(fā)URL保持/baby/login。登錄成功必須用重定向否則用戶刷新login.jsp會(huì)再次提交表單。3.2 育兒知識(shí)管理CRUD全鏈路含分頁(yè)與分類(lèi)管理員后臺(tái)的“育兒知識(shí)管理”是典型CRUD模塊但增加了按分類(lèi)篩選、按發(fā)布時(shí)間倒序、分頁(yè)顯示三個(gè)實(shí)用功能。KnowledgeListServlet.java負(fù)責(zé)列表頁(yè)// KnowledgeListServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int page 1; int pageSize 10; String currentPage request.getParameter(page); if (currentPage ! null !currentPage.isEmpty()) { page Integer.parseInt(currentPage); } String category request.getParameter(category); // 分類(lèi)篩選如新生兒 KnowledgeDAO knowledgeDAO new KnowledgeDAO(); // 1. 查詢總記錄數(shù)用于分頁(yè)計(jì)算 int totalCount knowledgeDAO.getTotalCount(category); // 2. 查詢當(dāng)前頁(yè)數(shù)據(jù) ListKnowledge knowledgeList knowledgeDAO.findByPage(category, (page-1)*pageSize, pageSize); // 3. 計(jì)算總頁(yè)數(shù) int totalPage (int) Math.ceil((double) totalCount / pageSize); // 4. 將數(shù)據(jù)存入request域 request.setAttribute(knowledgeList, knowledgeList); request.setAttribute(page, page); request.setAttribute(totalPage, totalPage); request.setAttribute(totalCount, totalCount); request.setAttribute(category, category); request.getRequestDispatcher(/admin/knowledge_list.jsp).forward(request, response); }DAO層關(guān)鍵SQLKnowledgeDAO.java// 查詢總數(shù)帶分類(lèi)條件 public int getTotalCount(String category) { String sql SELECT COUNT(*) FROM knowledge WHERE 11; if (category ! null !category.isEmpty()) { sql AND category ?; } try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { if (category ! null !category.isEmpty()) { ps.setString(1, category); } ResultSet rs ps.executeQuery(); if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { e.printStackTrace(); } return 0; } // 分頁(yè)查詢MySQL寫(xiě)法 public ListKnowledge findByPage(String category, int offset, int limit) { String sql SELECT * FROM knowledge WHERE 11 ORDER BY create_time DESC LIMIT ?, ?; if (category ! null !category.isEmpty()) { sql SELECT * FROM knowledge WHERE category ? ORDER BY create_time DESC LIMIT ?, ?; } // ... 執(zhí)行查詢封裝為L(zhǎng)istKnowledge }JSP分頁(yè)HTMLknowledge_list.jsp!-- 分頁(yè)導(dǎo)航 -- div classpagination c:if test${page 1} a hrefknowledge_list?page${page-1}${empty param.category ? : categoryparam.category}laquo; 上一頁(yè)/a /c:if c:forEach begin1 end${totalPage} vari c:choose c:when test${i page} span classcurrent${i}/span /c:when c:otherwise a hrefknowledge_list?page${i}${empty param.category ? : categoryparam.category}${i}/a /c:otherwise /c:choose /c:forEach c:if test${page totalPage} a hrefknowledge_list?page${page1}${empty param.category ? : categoryparam.category}下一頁(yè) raquo;/a /c:if /div參數(shù)說(shuō)明offset (page-1)*pageSizeMySQL分頁(yè)起點(diǎn)第1頁(yè)從0開(kāi)始第2頁(yè)從10開(kāi)始LIMIT ?, ?預(yù)編譯占位符防SQL注入${empty param.category ? : categoryparam.category}JSTL三元運(yùn)算保留分類(lèi)篩選參數(shù)點(diǎn)擊頁(yè)碼不丟失篩選條件classcurrentCSS高亮當(dāng)前頁(yè)提升管理體驗(yàn)。3.3 智能問(wèn)答模塊不是AI而是關(guān)鍵詞模糊匹配的輕量級(jí)實(shí)現(xiàn)標(biāo)題叫“智能問(wèn)答”實(shí)際是基于MySQL全文索引的LIKE模糊搜索符合本科畢設(shè)定位——不吹噓大模型但功能真實(shí)可用。SearchServlet.java核心邏輯// SearchServlet.java String keyword request.getParameter(keyword).trim(); if (keyword.isEmpty()) { request.setAttribute(error, 請(qǐng)輸入搜索關(guān)鍵詞); request.getRequestDispatcher(search.jsp).forward(request, response); return; } // 使用MySQL全文索引需提前在knowledge表title,content字段建FULLTEXT索引 // ALTER TABLE knowledge ADD FULLTEXT(title, content); String sql SELECT * FROM knowledge WHERE MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE) ORDER BY MATCH(title, content) AGAINST(?) DESC; // 若未建全文索引則降級(jí)為L(zhǎng)IKE // String sql SELECT * FROM knowledge WHERE title LIKE ? OR content LIKE ? ORDER BY create_time DESC; ListKnowledge results new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, keyword); ps.setString(2, keyword); ResultSet rs ps.executeQuery(); while (rs.next()) { Knowledge k new Knowledge(); k.setId(rs.getInt(id)); k.setTitle(rs.getString(title)); k.setContent(rs.getString(content).substring(0, Math.min(200, rs.getString(content).length())) ...); k.setCategory(rs.getString(category)); results.add(k); } } catch (SQLException e) { // 降級(jí)處理若全文索引報(bào)錯(cuò)改用LIKE sql SELECT * FROM knowledge WHERE title LIKE ? OR content LIKE ? ORDER BY create_time DESC; // ... 重新執(zhí)行 } request.setAttribute(results, results); request.setAttribute(keyword, keyword); request.getRequestDispatcher(search_result.jsp).forward(request, response);建全文索引SQL執(zhí)行一次即可ALTER TABLE knowledge ADD FULLTEXT(title, content); -- 驗(yàn)證是否生效SHOW INDEX FROM knowledge;優(yōu)勢(shì)與邊界MATCH...AGAINST比LIKE %keyword%快10倍以上尤其數(shù)據(jù)量超千條時(shí)IN NATURAL LANGUAGE MODE支持相關(guān)性排序搜索“發(fā)燒”時(shí)“寶寶發(fā)燒怎么辦”排在“兒童發(fā)燒物理降溫”前面不依賴(lài)外部API純數(shù)據(jù)庫(kù)能力答辯時(shí)可演示EXPLAIN SELECT ...證明性能若老師問(wèn)“為什么不是真正AI”可答“本模塊聚焦信息檢索效率全文索引是MySQL原生高性能方案避免引入Python或調(diào)用第三方API帶來(lái)的部署復(fù)雜度符合B/S架構(gòu)輕量級(jí)原則?!?.4 論壇發(fā)帖與回復(fù)一對(duì)多關(guān)系的JSP實(shí)現(xiàn)論壇模塊體現(xiàn)數(shù)據(jù)庫(kù)設(shè)計(jì)功底。post表主貼與reply表回復(fù)是典型一對(duì)多外鍵reply.post_id指向post.id。發(fā)帖JSPadd_post.jsp提交到AddPostServlet// AddPostServlet.java String title request.getParameter(title); String content request.getParameter(content); int userId ((User) request.getSession().getAttribute(user)).getId(); Post post new Post(); post.setTitle(title); post.setContent(content); post.setUserId(userId); post.setCreateTime(new Date()); PostDAO postDAO new PostDAO(); int postId postDAO.insert(post); // 返回自增ID // 發(fā)帖成功后重定向到該貼詳情頁(yè) response.sendRedirect(post_detail.jsp?id postId);post_detail.jsp同時(shí)加載主貼和所有回復(fù)!-- 主貼 -- div classpost-detail h2${post.title}/h2 p classpost-meta作者${post.userName} | 發(fā)布時(shí)間${post.createTime}/p div classpost-content${post.content}/div /div !-- 回復(fù)列表 -- div classreplies h3共 ${fn:length(replies)} 條回復(fù)/h3 c:forEach items${replies} varreply div classreply-item pstrong${reply.userName}/strong${reply.content}/p p classreply-time${reply.createTime}/p /div /c:forEach /div !-- 回復(fù)表單 -- form actionAddReplyServlet methodpost input typehidden namepostId value${param.id} textarea namecontent placeholder寫(xiě)下你的回復(fù)... required/textarea button typesubmit提交回復(fù)/button /formDAO層關(guān)聯(lián)查詢PostDAO.java// 查詢主貼及所有回復(fù)一次SQL避免N1 public Post findWithReplies(int postId) { String sql SELECT p.*, u.username as userName, r.id as reply_id, r.content as reply_content, r.create_time as reply_time, ru.username as reply_user_name FROM post p LEFT JOIN user u ON p.user_id u.id LEFT JOIN reply r ON p.id r.post_id LEFT JOIN user ru ON r.user_id ru.id WHERE p.id ? ORDER BY r.create_time ASC; // ... 執(zhí)行手動(dòng)組裝Post對(duì)象及ListReply }注意LEFT JOIN確保無(wú)回復(fù)的帖子也能顯示ORDER BY r.create_time ASC讓回復(fù)按時(shí)間正序排列最早回復(fù)在最前JSP中${fn:length(replies)}需在頁(yè)面頂部聲明% taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions %。4. 避坑指南那些讓答辯前夜崩潰的12個(gè)真實(shí)問(wèn)題與解法4.1 中文亂碼從數(shù)據(jù)庫(kù)到JSP五層防線全解析現(xiàn)象MySQL里存的是“新生兒護(hù)理”網(wǎng)頁(yè)顯示“?–°?”或表單提交“張三”數(shù)據(jù)庫(kù)存成“?? ??‰”。原因亂碼是字符集傳遞鏈斷裂涉及5個(gè)環(huán)節(jié)MySQL服務(wù)器默認(rèn)字符集character_set_server數(shù)據(jù)庫(kù)/表字符集CREATE DATABASE ... CHARACTER SET utf8mb4JDBC連接URL參數(shù)?characterEncodingutf8mb4Tomcatserver.xmlConnector的URIEncodingUTF-8JSP頁(yè)面編碼聲明% page contentTypetext/html;charsetUTF-8 %解決步驟1MySQL命令行執(zhí)行SHOW VARIABLES LIKE character_set_%;確保character_set_serverutf8mb4步驟2建庫(kù)時(shí)明確指定CREATE DATABASE baby CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;步驟3DBUtil.java中JDBC URL寫(xiě)全private static final String URL jdbc:mysql://localhost:3306/baby?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai;步驟4conf/server.xml中Connector標(biāo)簽加屬性Connector port8080 protocolHTTP/1.1 URIEncodingUTF-8 ... /步驟5每個(gè)JSP頂部第一行必須是% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%缺一不可。曾有同學(xué)只改了JSP忘了改server.xml導(dǎo)致GET請(qǐng)求URL參數(shù)仍亂碼。4.2 JSP EL表達(dá)式不解析不是語(yǔ)法錯(cuò)是web.xml版本惹的禍現(xiàn)象JSP里寫(xiě)% user.username %能顯示但c:out value${user.username}/或${user.username}直接原樣輸出字符串。原因EL表達(dá)式默認(rèn)在JSP 2.0啟用但若web.xml頭聲明為Servlet 2.3舊版EL會(huì)被禁用。解決檢查webapps/baby/WEB-INF/web.xml頂部聲明!-- ? 正確Servlet 3.0EL默認(rèn)開(kāi)啟 -- web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1若看到version2.3或http://java.sun.com/dtd/web-app_2_3.dtd立即替換為上方3.1版本聲明。Tomcat 8.5完全兼容。4.3 Tomcat啟動(dòng)失敗端口被占、JAR沖突、JDK版本三殺現(xiàn)象雙擊startup.bat無(wú)反應(yīng)或命令行報(bào)Address already in use: JVM_Bind、java.lang.UnsupportedClassVersionError。排查三步法端口沖突netstat -ano | findstr :8080Windows或lsof -i :8080Mac/Linux找到PID后taskkill /PID PID /FJAR沖突WEB-INF/lib下若有多個(gè)mysql-connector-java-*.jar如5.1和8.0混存刪掉舊版只留mysql-connector-java-8.0.33.jarJDK版本錯(cuò)配本項(xiàng)目編譯為Java 8javac -source 1.8 -target 1.8若系統(tǒng)裝了JDK 17Tomcat會(huì)報(bào)UnsupportedClassVersionError。解決方案下載JDK 8u202長(zhǎng)期支持版設(shè)置JAVA_HOME指向JDK 8echo %JAVA_HOME%Windows或echo $JAVA_HOMEMac/Linux確認(rèn)重啟命令行再啟Tomcat。4.4 管理員登錄404路徑大小寫(xiě)與web.xml映射的隱形殺手現(xiàn)象訪問(wèn)http://localhost:8080/baby/admin/提示404但/baby/index.jsp正常。原因文件路徑大小寫(xiě)錯(cuò)誤webapps/baby/admin/文件夾名寫(xiě)成AdminWindows不敏感Linux敏感web.xml中Servlet映射路徑錯(cuò)誤url-pattern/admin/login/url-pattern寫(xiě)成url-pattern/Admin/Login/url-patternJSP中鏈接寫(xiě)死a href/admin/index.jsp應(yīng)為a href${pageContext.request.contextPath}/admin/index.jsp。解決統(tǒng)一用小寫(xiě)命名所有文件夾和JSPweb.xml中url-pattern全部小寫(xiě)所有JSP內(nèi)鏈接用${pageContext.request.contextPath}動(dòng)態(tài)獲取上下文路徑避免硬編碼/baby。4.5 數(shù)據(jù)庫(kù)連接失敗驅(qū)動(dòng)、URL、權(quán)限的鐵三角驗(yàn)證現(xiàn)象java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或Communications link failure。驗(yàn)證清單項(xiàng)目正確值檢查方式驅(qū)動(dòng)JARmysql-connector-java-8.0.33.jar確認(rèn)在WEB-INF/lib/下且無(wú)其他版本JDBC URLjdbc:mysql://localhost:3306/baby?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai復(fù)制到Navicat測(cè)試能否連MySQL賬號(hào)權(quán)限baby_user對(duì)baby庫(kù)有SELECT,INSERT,UPDATE,DELETESHOW GRANTS FOR baby_userlocalhost;MySQL服務(wù)狀態(tài)正在運(yùn)行services.msc中檢查“MySQL80”服務(wù)是否啟動(dòng)血淚經(jīng)驗(yàn)曾有同學(xué)MySQL服務(wù)名為“MySql57”JDBC URL卻寫(xiě)localhost:3306實(shí)際端口是3307。務(wù)必用netstat -ano | findstr :330確認(rèn)MySQL監(jiān)聽(tīng)端口。5. 進(jìn)階技巧讓項(xiàng)目從“能跑”升級(jí)為“值得講”的3個(gè)硬核操作5.1 用Log4j2替代System.out.println讓調(diào)試痕跡變成答辯加分項(xiàng)畢業(yè)設(shè)計(jì)答辯時(shí)老師常問(wèn)“登錄失敗時(shí)你怎么知道是密碼錯(cuò)還是用戶不存在” 如果回答“我看控制臺(tái)打印”顯得粗糙。換成Log4j2就能說(shuō)“我配置了日志級(jí)別ERROR日志記錄異常堆棧WARN日志標(biāo)記業(yè)務(wù)異常INFO日志追蹤關(guān)鍵流程所有日志按日期滾動(dòng)歸檔。”操作步驟下載log4j-api-2.20.0.jar和log4j-core-2.20.0.jar放入WEB-INF/lib/在src/下新建log4j2.xml?xml version1.0 encodingUTF-8? Configuration statusWARN Appenders RollingFile nameFileOut fileNamelogs/baby.log filePatternlogs/baby-%d{yyyy-MM-dd}-%i.log.gz PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ Policies TimeBasedTriggeringPolicy / SizeBasedTriggeringPolicy size10 MB/ /Policies DefaultRolloverStrategy max30/ /RollingFile /Appenders Loggers Root levelinfo AppenderRef refFileOut/ /Root /Loggers /Configuration在LoginServlet.java中添加日志private static final Logger logger LogManager.getLogger(LoginServlet.class); // 登錄失敗時(shí) logger.warn(登錄失敗用戶名[{}]不存在或密碼錯(cuò)誤, username); // 登錄成功時(shí) logger.info(用戶[{}]登錄成功IP{}, username, request.getRemoteAddr());效果項(xiàng)目根目錄生成logs/baby.log內(nèi)容如14:22:31.882 [http-nio-8080-exec-2] INFO c.b.s.LoginServlet - 用戶[zhangsan]登錄成功IP127.0.0.1答辯時(shí)打開(kāi)日志文件老師一眼看到“INFO”“WARN”分級(jí)立刻感知工程規(guī)范性。5.2 為首頁(yè)輪播圖增加JSON接口暴露RESTful能力應(yīng)對(duì)“你會(huì)前端嗎”靈魂拷問(wèn)管理員后臺(tái)可上傳輪播圖但前臺(tái)JSP是靜態(tài)寫(xiě)死img srcstatic/img/banner1.jpg。升級(jí)為動(dòng)態(tài)新建BannerServlet.javadoGet()返回JSONprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { BannerDAO bannerDAO new BannerDAO(); ListBanner banners bannerDAO.findAll(); // 查詢status1的輪播圖 response.setContentType(application/json;charsetUTF-8); PrintWriter out response.getWriter(); out.print(new Gson().toJson(banners)); // 用Gson轉(zhuǎn)JSON out.flush(); }index.jsp中用JavaScript調(diào)用script fetch(${pageContext.request.contextPath}/banner) .then(response response.json()) .then(data { const carousel document.getElementById(carousel); data.forEach(banner { const item div classcarousel-itemimg src${banner.imageUrl} alt${banner.title}/div; carousel.innerHTML item; }); }); /script價(jià)值證明你理解前后端分離思想即使沒(méi)用Vue也用fetch調(diào)接口展示JSON數(shù)據(jù)交換能力為未來(lái)學(xué)Ajax打基礎(chǔ)接口URL/banner符合RESTful風(fēng)格比getBannerList.jsp更專(zhuān)業(yè)。5.3 用Druid連接池替換DBUtil從“能連”到“高并發(fā)”的認(rèn)知躍遷DBUtil.getConnection()每次創(chuàng)建新連接高并發(fā)時(shí)會(huì)耗盡MySQL連接數(shù)。換成Druid能監(jiān)控連接池狀態(tài)、自動(dòng)回收泄漏連接、防止SQL注入。操作WEB-INF/lib/加入druid-1.2.16.jarsrc/druid.propertiesdriverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/baby?useUnicodetruecharacterEncodingutf8mb4 usernamebaby_user passwordbaby123 initialSize5 maxActive20 minIdle5 maxWait60000 timeBetweenEvictionRunsMillis60000 minEvictableIdleTimeMillis300000 validationQuerySELECT 1 testWhileIdletrue testOnBorrowfalse testOnReturnfalse poolPreparedStatementstrue maxPoolPreparedStatementPerConnectionSize20 filtersstat,wallDBUtil.java重構(gòu)為單例Druidpublic class DBUtil { private static DruidDataSource dataSource; static { Properties props new Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }答辯話術(shù)“我用了Druid連接池它能自動(dòng)管理數(shù)據(jù)庫(kù)連接比如設(shè)置maxActive20保證最多20個(gè)并發(fā)查詢超出的請(qǐng)求排隊(duì)等待避免MySQL因連接數(shù)爆滿而拒絕服務(wù)。還開(kāi)啟了wall防火墻過(guò)濾器能攔截SELECT * FROM user WHERE 11 OR 11本文還有配套的精品資源點(diǎn)擊獲取