據(jù)庫系統(tǒng)應(yīng)用開發(fā)實(shí)例:JSP/Servlet+JDBC+MySQL源碼視頻解析)
簡(jiǎn)介Java Web數(shù)據(jù)庫系統(tǒng)應(yīng)用開發(fā)與實(shí)例是一份面向Web開發(fā)者的源碼與視頻學(xué)習(xí)包內(nèi)容圍繞Java Web和數(shù)據(jù)庫系統(tǒng)開發(fā)展開覆蓋Servlet、JSP、JDBC、Spring MVC/Struts以及SQL、事務(wù)、存儲(chǔ)過程和Hibernate/MyBatis等ORM映射技術(shù)。整個(gè)包共884個(gè)文件約222.5MB核心包括163個(gè)Java源碼、140個(gè)JSP頁面、181個(gè)編譯后Class文件、17個(gè)SQL腳本和48個(gè)AVI操作演示視頻另有JAR包、XML配置、JS/CSS等輔助資源便于對(duì)照學(xué)習(xí)與二次調(diào)試。已有470人學(xué)習(xí)下載適合希望從基礎(chǔ)到進(jìn)階掌握J(rèn)ava Web與數(shù)據(jù)庫整合開發(fā)的初學(xué)者也適合有一定經(jīng)驗(yàn)的開發(fā)者查漏補(bǔ)缺。通過源碼閱讀和視頻演示可直觀理解系統(tǒng)演示、共通Servlet封裝、第一個(gè)JSP應(yīng)用、報(bào)表打印等關(guān)鍵場(chǎng)景快速搭建數(shù)據(jù)庫驅(qū)動(dòng)的Web應(yīng)用并優(yōu)化連接、認(rèn)證與并發(fā)處理。1. Java Web數(shù)據(jù)庫系統(tǒng)應(yīng)用開發(fā)與實(shí)例源碼、視頻和課設(shè)的完整閉環(huán)如果你正在準(zhǔn)備Java Web數(shù)據(jù)庫方向的課設(shè)最讓人心態(tài)失衡的事往往不是寫代碼本身而是找不到一個(gè)“能跑起來”的參考項(xiàng)目。網(wǎng)上的Demo不少可要么頁面是靜態(tài)的要么SQL腳本導(dǎo)入就報(bào)錯(cuò)。這套《Java Web數(shù)據(jù)庫系統(tǒng)應(yīng)用開發(fā)與實(shí)例》的源碼與視頻資源解決的就是這個(gè)痛點(diǎn)它以一個(gè)賬戶信息管理Demo為載體把數(shù)據(jù)庫建表、JDBC連接、用戶登錄、增刪改查完整走了一遍。配套視頻按模塊拆開講每一步都有對(duì)應(yīng)的代碼位置。適合兩類人一是新手想照著視頻從零搭環(huán)境二是已經(jīng)寫過一些Java Web代碼的開發(fā)者想直接扒一套規(guī)范的分層代碼來改。2. 技術(shù)選型與工程結(jié)構(gòu)JSP/Servlet、JDBC、MySQL 怎么湊成一套不翻車的課設(shè)2.1 為什么還在用 JSP/Servlet課設(shè)場(chǎng)景下的務(wù)實(shí)選擇這套資源沒有追新技術(shù)棧相當(dāng)克制前端用JSP后端用Servlet數(shù)據(jù)庫訪問用JDBC數(shù)據(jù)庫用MySQL容器用Tomcat。第一反應(yīng)當(dāng)然是現(xiàn)在不都用Spring Boot嗎但如果你打開課程大綱會(huì)發(fā)現(xiàn)很多數(shù)據(jù)庫系統(tǒng)課程的核心要求仍然是“掌握Servlet生命周期”“使用JDBC完成數(shù)據(jù)庫增刪改查”。用Spring Boot超綱不說演示時(shí)被問到“Servlet里為什么不是service()方法”反而容易翻車。從這個(gè)角度看JSP/Servlet恰恰是最穩(wěn)妥的課設(shè)選擇。另一個(gè)維度是學(xué)習(xí)路徑足夠短。JSP負(fù)責(zé)頁面展示Servlet負(fù)責(zé)接收請(qǐng)求JDBC負(fù)責(zé)和MySQL交互整條鏈路可以在配套視頻里一步步看到。項(xiàng)目內(nèi)部并不是把所有邏輯堆在JSP里而是拆成了bean、dao、service、servlet、util幾個(gè)包。視頻的順序也按這個(gè)依賴關(guān)系排先裝環(huán)境再寫SQL腳本接著封裝DBUtil最后做Servlet和JSP。跟著視頻走實(shí)際就是按技術(shù)棧的依賴順序重新搭一遍。如果你之前只寫過單文件Servlet這套結(jié)構(gòu)正好幫你理解“類多了以后怎么分工”。2.2 WebRoot、src、sql 目錄先分清三塊再動(dòng)手項(xiàng)目的目錄結(jié)構(gòu)如下ProjectName/ ├─ src/ │ ├─ com/xxx/ │ │ ├─ bean/User.java │ │ ├─ dao/UserDao.java │ │ ├─ service/UserService.java │ │ ├─ servlet/LoginServlet.java │ │ └─ util/DBUtil.java ├─ WebRoot/ │ ├─ css/ │ ├─ WEB-INF/ │ │ └─ web.xml │ ├─ login.jsp │ ├─ list.jsp │ └─ index.jsp ├─ sql/ │ └─ init.sql └─ README.txtsrc下主要看五個(gè)包bean里的User對(duì)應(yīng)數(shù)據(jù)庫表字段名盡量和表列名保持一致否則ResultSet取值時(shí)容易踩坑dao就是數(shù)據(jù)訪問層只負(fù)責(zé)拼SQL、執(zhí)行SQL、把結(jié)果封裝成對(duì)象service是業(yè)務(wù)層比如登錄時(shí)候的密碼校驗(yàn)、刪除前的關(guān)聯(lián)檢查都放這里servlet負(fù)責(zé)接收瀏覽器參數(shù)調(diào)用service最后跳轉(zhuǎn)或轉(zhuǎn)發(fā)util里放DBUtil這類公共工具。WebRoot是Web應(yīng)用的根目錄部署到Tomcat后訪問路徑就是WebRoot下的文件路徑web.xml用來配置Servlet映射和歡迎頁。sql/init.sql是全項(xiàng)目最關(guān)鍵的文件。很多課設(shè)翻車的第一個(gè)原因不是代碼寫錯(cuò)而是沒先執(zhí)行init.sql。你直接啟動(dòng)項(xiàng)目DBUtil能連上MySQL但登錄查詢會(huì)報(bào)“Table doesnt exist”。所以拿到資源包以后先看sql目錄把腳本導(dǎo)入數(shù)據(jù)庫再用工具確認(rèn)表和代碼里的字段對(duì)得上。README.txt一般會(huì)寫清楚MySQL用戶名密碼和初始化步驟我第一次跑的時(shí)候沒看它直接改代碼里的密碼折騰了半小時(shí)才發(fā)現(xiàn)只是數(shù)據(jù)庫密碼寫錯(cuò)了。這套結(jié)構(gòu)是典型的“傳統(tǒng)Web項(xiàng)目”不是Maven項(xiàng)目。構(gòu)建時(shí)要把src編譯輸出到WEB-INF/classes把mysql-connector-java的jar包放到WEB-INF/lib。如果你看到的是Maven結(jié)構(gòu)那就在pom.xml里寫依賴。判斷方式很簡(jiǎn)單看工程目錄下有沒有pom.xml有就是Maven沒有就是傳統(tǒng)Web項(xiàng)目。2.3 JDBC 工具類把 DriverManager 封裝成單例省掉一半重復(fù)代碼每個(gè)DAO都要拿連接、關(guān)連接如果不封裝寫出來的代碼會(huì)有大量重復(fù)。所以項(xiàng)目里通常會(huì)有這么個(gè)DBUtil核心代碼大致如下package com.xxx.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/account_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; 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(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }URL里的參數(shù)值得逐個(gè)記一下。useUnicodetrue和characterEncodingutf8是為了讓中文能以UTF-8寫入useSSLfalse是因?yàn)楸镜亻_發(fā)不加密不加的話MySQL 8.0會(huì)一直報(bào)警告serverTimezoneAsia/Shanghai是MySQL 8.0之后才需要的時(shí)區(qū)參數(shù)少了它會(huì)在獲取連接時(shí)報(bào)時(shí)間差錯(cuò)誤。驅(qū)動(dòng)類名也有版本講究mysql-connector 8.x對(duì)應(yīng)com.mysql.cj.jdbc.Driver8.0之前是com.mysql.jdbc.Driver。視頻里如果第一次用5.x后來換8.0不少人就是在這里栽跟頭。close方法先關(guān)ResultSet再關(guān)Statement最后關(guān)Connection。這個(gè)順序不能亂因?yàn)镽esultSet依賴StatementStatement又依賴Connection。我見過有人先關(guān)Connection再讀ResultSet的時(shí)候直接空指針還以為是自己SQL寫錯(cuò)了。這套DBUtil是教學(xué)項(xiàng)目里最常見的寫法。如果以后要上生產(chǎn)建議把URL、用戶名、密碼抽到db.properties里再用ResourceBundle讀取改配置不用改代碼。課設(shè)沒這個(gè)要求但作為二次開發(fā)習(xí)慣提前做沒壞處。3. 核心模塊拆解一個(gè)登錄請(qǐng)求從表單到 Session 再到數(shù)據(jù)庫的完整鏈路3.1 登錄模塊表單提交后 Servlet 里發(fā)生了什么登錄邏輯看起來簡(jiǎn)單卻是很多問題的集中地。先看LoginServlet的doPostWebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); UserService userService new UserService(); User user userService.login(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /list); } else { request.setAttribute(errorMsg, 用戶名或密碼錯(cuò)誤); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }request.setCharacterEncoding(UTF-8)這一行必須在getParameter之前執(zhí)行。如果放在之后POST請(qǐng)求里的中文參數(shù)會(huì)按容器默認(rèn)編碼解析常見表現(xiàn)是用戶名里的中文變成亂碼。登錄成功以后把User對(duì)象放進(jìn)Session后續(xù)JSP頁面才能通過session里的loginUser判斷“是否已登錄”。這里用的是sendRedirect而不是forward好處是瀏覽器地址欄變成/list用戶按F5刷新時(shí)不會(huì)重復(fù)提交登錄表單。登錄失敗則用forward把錯(cuò)誤提示帶回login.jsprequest里的errorMsg能在頁面上顯示。很多人會(huì)問為什么Servlet里不直接寫SQL因?yàn)榇a一旦寫成Servlet里new一個(gè)UserDao再查庫后續(xù)要加“連續(xù)輸錯(cuò)三次鎖定賬號(hào)”這類規(guī)則時(shí)就得改Servlet。項(xiàng)目里用UserService包了一層登錄這種業(yè)務(wù)邏輯放在service里。UserService內(nèi)部再調(diào)用UserDao這樣Servlet只負(fù)責(zé)參數(shù)解析和頁面跳轉(zhuǎn)職責(zé)很干凈。密碼字段在視頻里是明文存儲(chǔ)這是教學(xué)項(xiàng)目為了方便演示。如果你要做成作品展示至少要改成MD5加鹽或者直接用BCrypt。我一般會(huì)在service里加一層“從庫里查出用戶對(duì)比密碼”的邏輯而不是讓SQL直接寫where username? and password?因?yàn)楹笳邲]法在代碼里做加密和登錄次數(shù)限制。3.2 用戶列表與分頁LIMIT 的參數(shù)不能靠字符串拼接列表頁是CRUD里最常改的部分。分頁查詢的UserDao實(shí)現(xiàn)大致是這個(gè)樣子public ListUser findPage(int pageNum, int pageSize) { ListUser list new ArrayList(); String sql SELECT id, username, nickname, email FROM user ORDER BY id LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); list.add(user); } } } catch (SQLException e) { e.printStackTrace(); } return list; }LIMIT ? ?是MySQL分頁的標(biāo)準(zhǔn)寫法第一個(gè)參數(shù)是偏移量第二個(gè)參數(shù)是每頁條數(shù)。pageNum從1開始所以偏移量等于(pageNum - 1) * pageSize。比如第3頁每頁10條就是從第21條開始取10條。用PreparedStatement的setInt傳參而不是拼SQL是因?yàn)檫@些參數(shù)來自頁面請(qǐng)求一旦拼SQL就存在注入風(fēng)險(xiǎn)。你在瀏覽器把pageSize改成“10 or 11”如果代碼是字符串拼接后面的查詢條件就會(huì)被改掉。這段代碼沒有寫COUNT查詢但列表頁一般要顯示“共幾頁”所以還需要一個(gè)配套方法public int count() { String sql SELECT COUNT(*) FROM user; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { e.printStackTrace(); } return 0; }總頁數(shù)的計(jì)算方式是(total pageSize - 1) / pageSize。比如總數(shù)是21每頁10這樣算出的是3頁而不是2頁。視頻里通常會(huì)單獨(dú)講這個(gè)細(xì)節(jié)因?yàn)樗钊菀自诹斜眄摲摃r(shí)出現(xiàn)最后一頁少一條的“玄學(xué)”問題。3.3 刪除用戶事務(wù)邊界和子表刪除順序刪除功能的典型場(chǎng)景是用戶主表一條記錄用戶角色關(guān)聯(lián)表里還有多條記錄。如果只刪主表外鍵約束會(huì)報(bào)錯(cuò)如果只刪關(guān)聯(lián)表主表留下孤兒數(shù)據(jù)。項(xiàng)目里的刪除邏輯通常會(huì)這樣寫public boolean deleteUser(int userId) { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String deleteRole DELETE FROM user_role WHERE user_id ?; ps conn.prepareStatement(deleteRole); ps.setInt(1, userId); ps.executeUpdate(); String deleteUser DELETE FROM user WHERE id ?; ps conn.prepareStatement(deleteUser); ps.setInt(1, userId); ps.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { e.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); } catch (SQLException ignored) {} } DBUtil.close(conn, ps, null); } }核心點(diǎn)是setAutoCommit(false)。一旦關(guān)閉自動(dòng)提交后面兩條DELETE要么一起成功要么一起回滾。如果第一條成功第二條失敗commit不會(huì)執(zhí)行rollback會(huì)把第一次刪除也撤銷避免出現(xiàn)“關(guān)聯(lián)表刪了主表還在”的臟數(shù)據(jù)。事務(wù)只包寫操作查詢不需要包因?yàn)椴樵儾淮嬖谝恢滦詥栴}。另一個(gè)細(xì)節(jié)是刪除順序。如果user_role表有外鍵指向user表必須先刪user_role再刪user否則MySQL會(huì)報(bào)外鍵約束失敗。有些項(xiàng)目為了省事把外鍵去掉這樣先刪誰都能跑但數(shù)據(jù)完整性就丟了。我建議保留外鍵讓數(shù)據(jù)庫幫你兜底演示時(shí)還可以順勢(shì)講一句“外鍵約束保證了引用的有效性”這對(duì)課設(shè)評(píng)分是加分項(xiàng)。更新操作和刪除類似但要注意executeUpdate的返回值。返回0表示影響行數(shù)為0常見原因是數(shù)據(jù)已經(jīng)被別人刪了或者你提交的id根本不存在。前端提示“更新失敗”總比悄無聲息成功要好。4. 從導(dǎo)入到跑通Tomcat和MySQL下的部署步驟與配置檢查4.1 環(huán)境準(zhǔn)備JDK、Tomcat、MySQL 版本怎么選視頻里從頭講環(huán)境搭建但你不需要完全復(fù)刻版本。JDK 1.8、Tomcat 8/9、MySQL 5.7或8.0這是教學(xué)項(xiàng)目最保險(xiǎn)的組合。如果你的機(jī)器已經(jīng)裝了JDK 11甚至17傳統(tǒng)JSP項(xiàng)目在Tomcat 9上也能跑但要注意Tomcat 10及以上把包名換成了jakarta.*老項(xiàng)目的javax.*代碼直接編譯不過。為了少折騰我建議直接用JDK 8和Tomcat 8.5。版本對(duì)照可以看這張表組件推薦版本注意點(diǎn)JDK1.8Servlet編譯兼容性最好Tomcat8.5 / 9.0WebServlet注解可用MySQL5.7 / 8.08.0需要serverTimezone參數(shù)mysql-connector-java5.1.x / 8.0.x驅(qū)動(dòng)類名不一樣MySQL 5.7的好處是省心驅(qū)動(dòng)類名用com.mysql.jdbc.Driver不需要時(shí)區(qū)參數(shù)。MySQL 8.0性能更好但sql_mode和排序規(guī)則更容易出幺蛾子。視頻里演示用的具體版本我不重復(fù)原則是跟著視頻版本走別自己混搭最新版。尤其是mysql-connector 8.0.x對(duì)MySQL 5.7也能連反過來5.1.x連8.0會(huì)報(bào)SSL握手失敗這里面坑很多。4.2 把工程導(dǎo)入 IDE傳統(tǒng)Web項(xiàng)目與 WEB-INF 檢查拿到源碼后先看是不是Maven工程。傳統(tǒng)Web項(xiàng)目用Eclipse導(dǎo)入File Import Existing Projects into WorkspaceIDEA則直接Open目錄然后在Project Structure里設(shè)置SDK和Web Facet。導(dǎo)入后最重要的一步是檢查WEB-INF/lib下有沒有mysql-connector的jar包。沒有的話啟動(dòng)后跑一次登錄就會(huì)報(bào)ClassNotFoundException。IDEA里傳統(tǒng)Web項(xiàng)目啟動(dòng)前還要設(shè)置Artifacts。常見翻車現(xiàn)場(chǎng)是Tomcat啟動(dòng)成功瀏覽器訪問login.jsp能打開但一點(diǎn)登錄就404控制臺(tái)提示Servlet沒找到。原因通常是.class文件沒有編譯輸出到WEB-INF/classes或者WebServlet注解沒有被掃描。解決方法是重新Build Artifacts并在Run Configuration的Deployment里確認(rèn)war exploded已經(jīng)加進(jìn)去了。如果是Maven工程導(dǎo)入后會(huì)自動(dòng)下載依賴。等待進(jìn)度條走完再啟動(dòng)否則JSP頁面里用了JSTL標(biāo)簽時(shí)會(huì)報(bào)“Cannot find the tag library descriptor for core”。JSTL需要jstl和standard兩個(gè)jar缺一不可。部署前建議按這個(gè)順序過一遍項(xiàng)目編譯無紅叉、WEB-INF/lib能看到驅(qū)動(dòng)、Tomcat啟動(dòng)無異常、訪問http://localhost:8080/項(xiàng)目名/login.jsp能正常顯示。4.3 執(zhí)行 init.sql初始化數(shù)據(jù)庫的三步檢查初始化數(shù)據(jù)庫不是“雙擊執(zhí)行”就完事。第一步確認(rèn)腳本里是否已經(jīng)包含CREATE DATABASE。如果包含你直接執(zhí)行就行如果不包含需要手動(dòng)創(chuàng)建數(shù)據(jù)庫再use進(jìn)入。第二步檢查腳本里的表名和UserDao里的表名是否一致。我見過一個(gè)項(xiàng)目代碼里寫的表名是user腳本里建的表名是users啟動(dòng)不報(bào)錯(cuò)但查詢永遠(yuǎn)返回空。第三步在DBUtil或db.properties里核對(duì)用戶名、密碼特別是密碼里有特殊字符時(shí)URL里偶爾還需要做URL編碼。還有一條實(shí)戰(zhàn)經(jīng)驗(yàn)數(shù)據(jù)庫連不上時(shí)先別懷疑代碼。在終端用mysql -uroot -p試一下能不能登錄能登錄說明MySQL服務(wù)正常。如果終端能連而項(xiàng)目連不上就檢查URL里的端口是否被改過。MySQL默認(rèn)3306有些機(jī)器裝了多版本MySQL端口改成了3307URL沒同步就會(huì)一直報(bào)Connection refused。這些都不算代碼邏輯問題但占了我?guī)腿伺挪閱栴}的七成時(shí)間。初始化完成后用一條最簡(jiǎn)單查詢驗(yàn)證SELECT * FROM user; 如果能查出數(shù)據(jù)再啟動(dòng)項(xiàng)目。直接跑項(xiàng)目容易把“SQL腳本沒執(zhí)行”和“代碼有bug”混在一起到最后你根本不知道是哪里出了問題。5. 常見問題與避坑404、500、中文亂碼的排查筆記這套源碼視頻在本地跑起來的過程里下面幾個(gè)問題是出現(xiàn)頻率最高的。我按“現(xiàn)象、原因、解決”三條寫方便你直接對(duì)號(hào)入座。5.1 訪問 JSP 一直 404頁面像沒部署現(xiàn)象Tomcat能啟動(dòng)瀏覽器打開login.jsp卻顯示404或者訪問一個(gè)Servlet地址下載了JSP源碼。原因項(xiàng)目沒有真正部署到Tomcat或者部署路徑和訪問路徑不一致。解決先確認(rèn)IDE里Run Configuration的Deployment中已經(jīng)添加了項(xiàng)目的war或war exploded。Application context建議設(shè)為/項(xiàng)目名訪問時(shí)用帶上下文路徑的地址。Eclipse用戶右鍵項(xiàng)目Run As Run on Server不要直接雙擊jsp文件。Tomcat的webapps目錄下也應(yīng)該看到對(duì)應(yīng)的項(xiàng)目文件夾如果沒有說明部署沒生效。這類問題我遇到最多的是IDEA多模塊工程module沒被加到Artifacts里導(dǎo)致頁面前端資源能找到但Servlet的class文件不在WEB-INF/classes下。檢查方法是看Tomcat日志里有沒有打印“Deploying web application directory”有的話再確認(rèn)WEB-INF/classes里有沒有LoginServlet.class。5.2 ClassNotFoundException驅(qū)動(dòng)類名按版本選現(xiàn)象訪問數(shù)據(jù)庫相關(guān)頁面控制臺(tái)報(bào)ClassNotFoundException后面跟著com.mysql.jdbc.Driver或com.mysql.cj.jdbc.Driver。原因一是驅(qū)動(dòng)jar沒有放到WEB-INF/lib二是驅(qū)動(dòng)類名和驅(qū)動(dòng)版本不匹配。解決先確認(rèn)mysql-connector-java的版本。8.0.x必須用com.mysql.cj.jdbc.Driver5.1.x用com.mysql.jdbc.Driver。然后把jar放到WEB-INF/lib下重新部署不要放在IDE的external libraries里因?yàn)門omcat運(yùn)行時(shí)不會(huì)去讀IDE的類路徑。Maven項(xiàng)目則要檢查pom.xml里的驅(qū)動(dòng)依賴scope如果寫成了provided或者testTomcat運(yùn)行時(shí)會(huì)找不到類。順便說一句驅(qū)動(dòng)jar包版本不要亂用最新的8.4、8.5項(xiàng)目里視頻用的是8.0.x系列就保持一致。打包部署的環(huán)境最怕“我更新了一下依賴然后突然壞了”。5.3 中文亂碼從頁面到數(shù)據(jù)庫要統(tǒng)一三處編碼現(xiàn)象表單輸入中文插入MySQL后變成問號(hào)或者頁面顯示“用戶名”變成“?”開頭的一串亂碼。原因JSP頁面編碼、Servlet請(qǐng)求編碼、JDBC連接編碼三處不一致。解決JSP第一行寫pageEncodingUTF-8Servlet在doPost開頭第一行寫request.setCharacterEncoding(UTF-8)JDBC URL加characterEncodingutf8MySQL建表時(shí)指定DEFAULT CHARACTER SET utf8mb4。三個(gè)地方缺一個(gè)都會(huì)出問題但最常見的是漏了Servlet里的setCharacterEncoding因?yàn)轫撁婧蛿?shù)據(jù)庫都改好了反而這個(gè)不起眼的一行被忽略。Tomcat 8.5之后對(duì)GET請(qǐng)求的參數(shù)默認(rèn)按UTF-8解碼所以很多人在GET請(qǐng)求里不寫setCharacterEncoding也沒事。但POST請(qǐng)求還是建議顯式設(shè)置因?yàn)閾Q成其他容器或Tomcat版本后行為可能不同。課設(shè)演示時(shí)最好三種請(qǐng)求都試一遍避免現(xiàn)場(chǎng)翻車。5.4 數(shù)據(jù)庫連接耗盡操作幾次后卡死現(xiàn)象項(xiàng)目剛啟動(dòng)能正常增刪改查翻幾頁之后越來越卡最后請(qǐng)求一直轉(zhuǎn)圈控制臺(tái)報(bào)Too many connections或者Connection refused。原因代碼里getConnection后沒有close連接泄漏MySQL連接數(shù)被占滿。解決在DAO每個(gè)方法里用try-with-resources或者finally里調(diào)用DBUtil.close。關(guān)閉順序按ResultSet、Statement、Connection來。如果用了連接池還要檢查池的最大連接數(shù)和空閑超時(shí)。排查時(shí)在MySQL執(zhí)行SHOW PROCESSLIST能看到大量Sleep的連接基本就能實(shí)錘。這里有個(gè)容易誤傷的點(diǎn)JDBC的Connection.close不是直接把進(jìn)程殺死而是把連接放回連接池或標(biāo)記為關(guān)閉。如果你在finally里關(guān)閉后后續(xù)又去操作ResultSet會(huì)報(bào)ResultSet closed。所以取值范圍只在try塊內(nèi)取完再關(guān)不要把rs拿到方法外面去用。5.5 視頻里能跑我這兒就是不行版本和緩存現(xiàn)象每一步都跟著視頻做軟件版本也一樣但啟動(dòng)后報(bào)錯(cuò)信息五花八門。原因多半是驅(qū)動(dòng)jar版本不一致、數(shù)據(jù)庫端口不對(duì)或者瀏覽器緩存了舊的JSP。解決先暫停視頻逐行對(duì)照自己的配置文件和代碼不要憑記憶寫URL。再確認(rèn)MySQL服務(wù)真的啟動(dòng)了Windows下服務(wù)列表可能顯示“已停止”。瀏覽器按CtrlF5強(qiáng)制刷新排除舊頁面緩存。最后還不行就刪除項(xiàng)目重新導(dǎo)入一次IDEA緩存導(dǎo)致的資源沒刷新問題重導(dǎo)往往能解決。這一條看著像玄學(xué)其實(shí)背后是“環(huán)境狀態(tài)不確定”。我處理這類問題時(shí)會(huì)寫一個(gè)環(huán)境檢查清單JDK版本、Tomcat版本、MySQL版本、驅(qū)動(dòng)版本、端口、字符集。逐個(gè)打勾不猜。很多時(shí)候你盯著代碼看半天最后發(fā)現(xiàn)Tomcat的CATALINA_BASE指到了另一個(gè)安裝目錄項(xiàng)目根本沒部署到你正在修改的那個(gè)Tomcat上。6. 二次開發(fā)指南把單用戶登錄改造成多角色權(quán)限控制6.1 改造思路登錄后把角色信息放進(jìn) Session這套Demo的Session里只存了User對(duì)象所有登錄用戶看到的功能都一樣。要做多角色權(quán)限第一步是擴(kuò)展表結(jié)構(gòu)user表加一個(gè)role_id字段再建role表保存角色名比如admin和user。登錄時(shí)除了查User還要通過role_id查出角色名稱。// 登錄成功后額外查詢角色并放入 Session Role role roleService.findByUserId(user.getId()); session.setAttribute(currentRole, role.getName());第二步在JSP頁面判斷角色決定是否顯示某個(gè)入口比如管理員才能看到“用戶管理”鏈接。但頁面判斷只能算展示層控制真正的控制要在Servlet入口做。6.2 驗(yàn)證方法用普通賬號(hào)試一次管理員頁面最簡(jiǎn)單可演示的權(quán)限控制是在管理員相關(guān)Servlet的doGet或doPost開頭加幾行HttpSession session request.getSession(); if (!admin.equals(session.getAttribute(currentRole))) { response.sendRedirect(request.getContextPath() /login.jsp); return; }這樣輸入普通賬號(hào)登錄后直接訪問管理員的Servlet地址會(huì)被彈回登錄頁瀏覽器地址欄也能看到變化。演示時(shí)先截一張普通賬號(hào)被攔截的圖再換成管理員賬號(hào)訪問成功就是這個(gè)功能最好的說明。如果需要更完整的攔截再考慮寫一個(gè)Filter統(tǒng)一檢查除login之外的路徑。我在第一次給類似項(xiàng)目加權(quán)限時(shí)把角色判斷直接塞進(jìn)了JSP頁面里結(jié)果別人改個(gè)頁面就能繞過。后來我強(qiáng)制要求所有權(quán)限校驗(yàn)都放在Servlet入口完成JSP只負(fù)責(zé)展示。從那以后我每次做Java Web數(shù)據(jù)庫項(xiàng)目的二次開發(fā)都會(huì)先畫一遍“請(qǐng)求從哪里進(jìn)、角色在哪里校驗(yàn)、失敗回哪里”的路徑圖再動(dòng)手。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取