管理系統(tǒng)源碼解析與復(fù)現(xiàn)指南)
簡(jiǎn)介一套基于 jspjsservletjdbcmysql 原生界面開發(fā)的后臺(tái)管理系統(tǒng)源碼適合 Java Web 初學(xué)者、課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)參考。項(xiàng)目不依賴復(fù)雜前端框架完整實(shí)現(xiàn)登錄、注冊(cè)、用戶/產(chǎn)品增刪改查、分頁查詢、圖片上傳、退出及未登錄過濾器攔截等后臺(tái)常見功能可幫助讀者理解 servletjdbc 的請(qǐng)求處理與數(shù)據(jù)庫交互流程。壓縮包共92個(gè)文件含14個(gè)java與12個(gè)jsp源碼、28個(gè)class編譯文件以及jar依賴、xml配置、數(shù)據(jù)庫腳本和docx導(dǎo)入步驟說明整體僅3.04MB便于快速下載與本地部署。附帶的 mydb.sql 可直接建庫工程導(dǎo)入步驟與運(yùn)行必讀文檔能減少環(huán)境配置彎路。已有3371人學(xué)習(xí)下載適合需要完整可運(yùn)行后臺(tái)項(xiàng)目或希望逐模塊拆解原生 Java Web 開發(fā)的讀者。1. 這套 jspservletjdbcmysql 后臺(tái)管理系統(tǒng)老技術(shù)棧為什么還能救急去年我?guī)У囊粋€(gè)練手小組把課程設(shè)計(jì)題目定為“產(chǎn)品后臺(tái)管理系統(tǒng)”要求不許用 Spring Boot必須用 Java 原生的 jspservletjdbcmysql 把界面和邏輯寫出來。折騰兩周后他們交上來的東西和這份 xianfengProject 的結(jié)構(gòu)幾乎一樣登錄、注冊(cè)、產(chǎn)品增刪改查、分頁、圖片上傳、過濾器攔 session。我當(dāng)時(shí)把這套源碼當(dāng)參考答案發(fā)給組里新人時(shí)說了一句話把這幾條鏈路跑通再去碰框架腦子里才有“請(qǐng)求到底是怎么穿過后端”的那張地圖?,F(xiàn)在很多培訓(xùn)項(xiàng)目上來就是 Spring BootMyBatis-Plus模板一生成servlet 和 jdbc 反而成了黑匣子。這套源碼值錢的地方恰恰是它把黑匣子拆開給你看分頁用 limit 怎么算、圖片上傳怎么落盤、過濾器怎么攔未登錄用戶。適合三類人剛學(xué)完 JavaWeb 想找完整閉環(huán)案例的在校生準(zhǔn)備 java 面試想復(fù)習(xí) servlet 和 jdbc 底層細(xì)節(jié)的求職者以及需要一套老技術(shù)棧管理系統(tǒng)改改就交差的在職工程師。我照著跑通一次全程沒遇到玄學(xué)問題但確實(shí)有幾個(gè)坑后面單開一章說。2. 項(xiàng)目功能與原理解剖登錄、產(chǎn)品管理、過濾器是怎么串起來的2.1 五個(gè)功能模塊的請(qǐng)求映射關(guān)系這套系統(tǒng)的主入口是 index.jsp上面放了登錄和注冊(cè)兩個(gè)入口。登錄成功之后 session 里會(huì)寫入用戶標(biāo)識(shí)然后跳轉(zhuǎn)到產(chǎn)品管理總頁面。產(chǎn)品管理頁承載了最重的業(yè)務(wù)查詢某個(gè)產(chǎn)品、添加、修改、批量刪除、查看詳情、分頁展示。退出功能就是清 session 回主頁邏輯最簡(jiǎn)單但承載著整個(gè)權(quán)限閉環(huán)。從請(qǐng)求路徑來看整個(gè)系統(tǒng)的流轉(zhuǎn)關(guān)系大概是這樣一張映射表請(qǐng)求路徑對(duì)應(yīng)處理說明/index.jsp主頁展示未登錄時(shí)的默認(rèn)落地頁/login.doLoginServlet校驗(yàn)用戶名密碼成功則寫入 session/register.doRegisterServlet新增用戶寫入 t_user 表/product/list.doProductServlet(list)分頁查詢產(chǎn)品默認(rèn) pageIndex1/product/add.doProductServlet(add)添加產(chǎn)品涉及圖片上傳/product/update.doProductServlet(update)修改產(chǎn)品/product/batchDelete.doProductServlet(batchDelete)批量刪除/logout.doLogoutServlet清除 session 后 redirect 到 index.jsp我在實(shí)際復(fù)現(xiàn)時(shí)會(huì)先畫這張表再去對(duì)照源碼里的 servlet 映射。因?yàn)樵?servlet 項(xiàng)目沒有框架里那種注解路由或者只在 servlet 上用 WebServlet通讀一遍 web.xml 或注解整個(gè)功能邊界就清楚了。2.2 目錄結(jié)構(gòu)里藏著的老工程痕跡壓縮包解開后的內(nèi)容很有意思它同時(shí)保留了 .idea 和 .mymetadata、.project、.classpath說明這份源碼在 IntelliJ IDEA 和 MyEclipse 里都被打開過。xianfengProject.iml 是 IDEA 的模塊文件out 目錄是 IDEA 編譯輸出目錄WebRoot 是 web 根目錄。這些細(xì)節(jié)對(duì)復(fù)現(xiàn)有用用 IDEA 導(dǎo)入時(shí)直接 Open 這個(gè)目錄IDEA 會(huì)讀取 .iml 恢復(fù)模塊結(jié)構(gòu)用 Eclipse/MyEclipse 導(dǎo)入時(shí)則看 .project 和 .classpath。src 目錄放 Java 源碼servlet、dao、bean、utilWebRoot 目錄放 jsp 頁面、WEB-INF/web.xml、js、css、上傳圖片目錄壓縮包內(nèi)附帶的 mydb.sql 是初始化腳本工程導(dǎo)入步驟.docx 和運(yùn)行必讀.txt 是作者寫的操作說明我一般建議先打開運(yùn)行必讀.txt 再動(dòng)手因?yàn)檫@種老工程最容易翻車的就是環(huán)境版本不匹配。正文里寫了 jdk1.7、tomcat7、mysql5.5/5.6/5.7這三個(gè)版本必須對(duì)齊別腦子一熱裝個(gè) tomcat10servlet 包名都變了代碼直接編譯不過。2.3 mydb.sql 里的核心表結(jié)構(gòu)設(shè)計(jì)這套系統(tǒng)只需要兩張核心表用戶表 t_user 和產(chǎn)品表 t_product。產(chǎn)品表要存圖片路徑所以單獨(dú)留了一個(gè) image_path 字段價(jià)格用 DECIMAL 而不是 FLOAT避免精度問題。下面是按常規(guī)設(shè)計(jì)補(bǔ)全的建表 SQL和源碼里 mydb.sql 的思路一致CREATE DATABASE IF NOT EXISTS xianfeng_db DEFAULT CHARSET utf8mb4; USE xianfeng_db; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, description TEXT, image_path VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO t_user(username, password) VALUES(admin, 123456);這里有兩個(gè)細(xì)節(jié)值得注意。第一字符集要 utf8mb4 而不是 utf8否則插入 emoji 或生僻字時(shí) jdbc 連接 mysql 會(huì)直接報(bào) Incorrect string value。第二密碼字段設(shè)計(jì)成 VARCHAR(50) 是課程設(shè)計(jì)常見做法生產(chǎn)環(huán)境絕不這么干至少得加鹽哈希。我復(fù)現(xiàn)時(shí)只是為了跑通流程所以沿用原設(shè)計(jì)的明文存儲(chǔ)但如果你要拿這套資源改造成真正的工程密碼存儲(chǔ)是第一件必須改的事。3. 從壓縮包到跑通環(huán)境、導(dǎo)入、建庫、部署四步走3.1 環(huán)境版本對(duì)齊少一步后期就是玄學(xué)現(xiàn)場(chǎng)老工程最怕的就是版本漂移。項(xiàng)目說明明確寫了 jdk1.7、tomcat7、mysql5.5/5.6/5.7。我本地用的組合是 jdk1.7 tomcat7.0.94 mysql5.7.44一次跑通。如果你電腦上已經(jīng)裝了高版本 JDK比如 JDK8 或 JDK11編譯目標(biāo)可以調(diào)成 1.7 來兼容但 tomcat 建議老老實(shí)實(shí)用 7.x。tomcat8.5 以上對(duì)老式 jsp 的隱式對(duì)象處理有細(xì)微差異雖說大概率沒問題但沒必要為了一個(gè)復(fù)現(xiàn)去賭兼容性。還有 jdbc 驅(qū)動(dòng)版本mysql5.7 用 mysql-connector-java-5.1.49.jar 最常見驅(qū)動(dòng)類名是 com.mysql.jdbc.Driver。如果你手頭是 8.x 的驅(qū)動(dòng)包驅(qū)動(dòng)類名要換成 com.mysql.cj.jdbc.Driver連接串還要額外加 serverTimezone 參數(shù)。這一步是 jdbc 連接 mysql 的經(jīng)典坑后面避坑章細(xì)說。3.2 用 IDEA 導(dǎo)入工程源碼自帶 .idea 目錄和 iml 文件所以用 IntelliJ IDEA 打開最省事。不是新建工程而是 File - Open 直接選中 xianfengProject 目錄IDEA 會(huì)根據(jù) iml 恢復(fù)模塊配置。打開后第一件事是檢查 Project StructureProject SDK: 選 1.7 或按需調(diào)成 1.8編譯級(jí)別 Level 7 Project language level: 7 - Diamonds, ARM, multi-catch 等 Modules - Dependencies: 確認(rèn) lib 下的 mysql-connector 等 jar 已加入之后配置 TomcatRun - Edit Configurations - 左上角 - Tomcat Server - Local在 Deployment 頁簽里添加 ArtifactApplication context 填 /xianfengProject。這個(gè) context path 決定了你訪問地址是 http://localhost:8080/xianfengProject/index.jsp。邏輯說明老式 web 工程在 IDEA 里的部署方式是 Exploded解壓目錄不是 war 包。這樣做的好處是 jsp 修改后刷新頁面就能看到不用重新打包。你在 Deployment 里看到的 artifact 類型應(yīng)該是 Web Application: Exploded。3.3 初始化數(shù)據(jù)庫與賬號(hào)配置打開命令行進(jìn)入 mysql 的 bin 目錄執(zhí)行mysql -u root -p mydb.sqlmydb.sql 在壓縮包根目錄建議先用記事本打開看一眼確認(rèn)里面的庫名和表名。如果腳本里沒有 CREATE DATABASE你需要手動(dòng)先建庫再使用。導(dǎo)入成功后在 mysql 命令行執(zhí)行 SHOW TABLES; 能看到 t_user 和 t_product 就說明數(shù)據(jù)庫這邊齊活了。然后打開源碼里的 jdbc 配置文件一般是 src 下名為 db.properties 或 jdbc.properties 的文件改成你本地的賬號(hào)密碼jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/xianfeng_db?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password你的數(shù)據(jù)庫密碼參數(shù)說明useUnicodetrue 和 characterEncodingutf8 這兩個(gè)參數(shù)建議保留否則從 mysql 讀中文再寫進(jìn) jsp 顯示時(shí)容易出現(xiàn)亂碼。端口 3306 如果你本地改過也要同步換。這條連接串是 jdbc 連接 mysql 最經(jīng)典的寫法很多框架的底層數(shù)據(jù)源本質(zhì)上也在拼這么一串。3.4 啟動(dòng) Tomcat 并跑通第一個(gè)用例啟動(dòng)前先確認(rèn)四件事tomcat 端口沒被占、jdk 版本匹配、artifact 已經(jīng)部署、mysql 服務(wù)是啟動(dòng)狀態(tài)。然后點(diǎn) IDEA 里的 Run 啟動(dòng) Tomcat控制臺(tái)出現(xiàn) Server startup 日志后瀏覽器訪問http://localhost:8080/xianfengProject/index.jsp首次訪問能看到主頁說明 web 應(yīng)用沒問題。然后用 admin/123456 登錄如果能進(jìn)產(chǎn)品管理頁這就算跑通了。接下來建議立刻做兩件事驗(yàn)證功能完整性隨便添加一個(gè)帶圖片的產(chǎn)品然后在前臺(tái)翻幾頁看分頁是否正常。第一輪復(fù)現(xiàn)只要這兩條鏈路通了這套源碼的核心功能就全部驗(yàn)證到位了。4. 源碼里值得一行行讀的三個(gè)實(shí)現(xiàn)分頁、圖片上傳、過濾器4.1 分頁查詢limit 參數(shù)與頁腳邏輯原生 jdbc 做分頁思路比 MyBatis-Plus 的 IPage 要直接得多。核心就是 SQL 拼 limit前端把當(dāng)前頁碼傳給 servletservlet 通過 request.getParameter 拿參數(shù)dao 層計(jì)算偏移量后執(zhí)行查詢。下面是 ProductDao 分頁方法的核心代碼public ListProduct findByPage(int pageIndex, int pageSize) { ListProduct list new ArrayListProduct(); String sql SELECT id, name, price, description, image_path FROM t_product ORDER BY id DESC LIMIT ?, ?; int start (pageIndex - 1) * pageSize; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, start); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); p.setDescription(rs.getString(description)); p.setImagePath(rs.getString(image_path)); list.add(p); } } } catch (SQLException e) { e.printStackTrace(); } return list; }代碼邏輯start 計(jì)算的是跳過的記錄數(shù)pageIndex 從 1 開始第一頁就是 limit 0, 10。ORDER BY id DESC 表示新品在前后來我在改造時(shí)加了“默認(rèn)按添加時(shí)間倒序”的需求邏輯同理。參數(shù)說明pageSize 是每頁行數(shù)一般在 servlet 里寫死成常量或者從前端下拉框傳入這里用 PreparedStatement 預(yù)編譯也順便防了 SQL 注入是原生 jdbc 寫法的標(biāo)準(zhǔn)姿勢(shì)。配套的還要有查詢總記錄數(shù)的 count 方法頁腳才能顯示共 X 頁public int count() { String sql SELECT COUNT(*) FROM t_product; 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; }前端頁腳計(jì)算總頁數(shù)時(shí)用 (total pageSize - 1) / pageSize 代替除法向上取整避免浮點(diǎn)誤差。這是原生分頁里最后一步很多新手在這里翻車。4.2 圖片上傳servlet3.0 的 Part 接口圖片上傳是整套系統(tǒng)里最有技術(shù)含量的環(huán)節(jié)。摘要里寫了圖片上傳功能實(shí)現(xiàn)上在 tomcat7 環(huán)境可以直接用 servlet3.0 的 Part 接口不需要 commons-fileupload。product 表單里 input 標(biāo)簽要加 enctypemultipart/form-data這是第一個(gè)必須注意的點(diǎn)。WebServlet(/product/add.do) public class ProductAddServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(utf-8); String name request.getParameter(name); Part part request.getPart(image); String fileName getFileName(part); String uploadPath getServletContext().getRealPath(/) upload; File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } part.write(uploadPath File.separator fileName); String price request.getParameter(price); String desc request.getParameter(description); // 組裝 Product 對(duì)象調(diào)用 dao 層插入 t_product // image_path 字段存 upload/文件名方便頁面拼接展示 response.sendRedirect(list.do?pageIndex1); } private String getFileName(Part part) { String header part.getHeader(content-disposition); String[] arr header.split(;); for (String s : arr) { if (s.trim().startsWith(filename)) { return s.substring(s.indexOf() 2, s.length() - 1); } } return default.png; } }這段代碼有幾個(gè)關(guān)鍵細(xì)節(jié)。getRealPath(/) 返回的是 webapp 部署后的磁盤絕對(duì)路徑upload 目錄要在第一次上傳前創(chuàng)建否則 part.write 會(huì)報(bào) FileNotFoundException。getFileName 這個(gè)方法不能省Part 對(duì)象沒有直接提供原文件名方法只能從 content-disposition 頭里截取。圖片存儲(chǔ)路徑建議存相對(duì)路徑 upload/文件名頁面 標(biāo)簽直接寫 srcupload/xxx.png 就能展示這也是摘要里提到“jsp 圖片如何定位”的常見解法。4.3 過濾器session 驗(yàn)證與放行路徑過濾器是這套系統(tǒng)安全機(jī)制的核心。如果用戶沒登錄直接訪問產(chǎn)品管理頁面必須被攔回 index.jsp。實(shí)現(xiàn)就是實(shí)現(xiàn) javax.servlet.Filter 接口在 doFilter 里檢查 sessionWebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); String ctx request.getContextPath(); String path uri.substring(ctx.length()); boolean isStatic path.startsWith(/js/) || path.startsWith(/css/) || path.startsWith(/upload/) || path.endsWith(.js) || path.endsWith(.css); boolean isPublic path.equals(/index.jsp) || path.equals(/login.do) || path.equals(/register.do) || path.equals(/logout.do); HttpSession session request.getSession(false); boolean loggedIn session ! null session.getAttribute(user) ! null; if (loggedIn || isStatic || isPublic) { chain.doFilter(req, resp); } else { response.sendRedirect(ctx /index.jsp); } } }這個(gè)過濾器的邏輯要仔細(xì)理解放行三類請(qǐng)求——已登錄用戶的請(qǐng)求、靜態(tài)資源、公開頁面。沒有登錄且不滿足放行條件的重定向回 index.jsp。我見過很多新手寫的過濾器只判斷了 session結(jié)果把登錄頁面和靜態(tài) JS 全都攔了頁面樣式全丟或者死循環(huán)重定向。注意 getSession(false) 不要寫成 getSession()帶參 false 不會(huì)自動(dòng)創(chuàng)建 session避免未登錄用戶每次訪問都被強(qiáng)制生成 session這是個(gè)資源浪費(fèi)的隱形坑。5. 避坑指南復(fù)現(xiàn)這套源碼最容易翻車的五個(gè)位置5.1 環(huán)境部署與啟動(dòng)過程中的典型問題現(xiàn)象一執(zhí)行 net start mysql 時(shí)提示服務(wù)名無效或者服務(wù)正在啟動(dòng)后立刻停止。我遇到這臺(tái)機(jī)器裝的是 mysql5.7.44之前用安裝版裝過一次后來又解壓了一份免安裝版注冊(cè)表里的服務(wù)名根本沒對(duì)上。原因服務(wù)名不是默認(rèn)的 mysql而是你安裝時(shí)指定的名稱可能是 MySQL57 或自定義的名字。解決用管理員身份打開 CMD執(zhí)行 sc query 或直接到服務(wù)管理器里看服務(wù)名再執(zhí)行 net start 服務(wù)名如果服務(wù)列表里沒有就用 mysqld --install 先注冊(cè)服務(wù)再啟動(dòng)?,F(xiàn)象二本地裝了 JDK8但源碼在編譯時(shí)報(bào)錯(cuò)看不到 API 或語法級(jí)別不對(duì)。這套工程注釋里寫的是 jdk1.7如果直接用 IDEA 默認(rèn) SDK 打開編譯級(jí)別可能帶著 1.8 語法servlet 代碼沒問題但某些老 lib 會(huì)和 JDK8 產(chǎn)生 bytecode 版本沖突。解決Project Structure 里把 Project SDK 定義為 1.7沒有就先裝一個(gè)Language level 選 7Modules 里的 SDK 也同步改。如果你機(jī)器上只有 JDK8 不想再裝舊的把編譯 target 調(diào)成 1.7 也能緩解大部分問題。現(xiàn)象三Tomcat 啟動(dòng)時(shí)端口被占用報(bào) SEVERE 級(jí)別的 Socket bind 失敗。原因多半是上次 IDEA 非正常關(guān)閉java.exe 進(jìn)程還占著 8080。解決CMD 執(zhí)行 netstat -ano | findstr :8080 查到 PID再 taskkill /PID 你的PID /F殺完重啟就好。血淚經(jīng)驗(yàn)不要一上來就把 tomcat 的 server.xml 端口改成 8081治標(biāo)不治本而且 session 里的回跳地址不會(huì)自動(dòng)適配新端口?,F(xiàn)象四頁面訪問 404顯示源服務(wù)器未能找到目標(biāo)資源的表示。原因一般是 artifact 沒部署成功或者 context path 不是 /xianfengProject。Tomcat 的部署機(jī)制里war 或 exploded artifact 要掛在指定 context 下訪問路徑是 ip:port/context/資源路徑少一層或多一層都會(huì) 404。解決檢查 Run Configuration 的 Deployment 頁簽確認(rèn) artifact 已經(jīng)添加且 Application context 寫的 /xianfengProject然后重啟 Tomcat 重新部署?,F(xiàn)象五數(shù)據(jù)庫連接報(bào) java.sql.SQLException: Unknown database。使用 mysql -u root -p mydb.sql 導(dǎo)入前如果 mydb.sql 里沒有 CREATE DATABASE或者當(dāng)前用戶沒權(quán)限建庫表就落在系統(tǒng)庫里甚至沒落進(jìn)去但 JDBC URL 里卻寫了 xianfeng_db結(jié)果就是連接報(bào) Unknown database。解決先手動(dòng)執(zhí)行 CREATE DATABASE xianfeng_db DEFAULT CHARSET utf8mb4再進(jìn)入該庫導(dǎo)入腳本或者直接在 mysql 命令行用 source 方式導(dǎo)入 mydb.sql。5.2 代碼運(yùn)行與數(shù)據(jù)層面的常見踩坑點(diǎn)現(xiàn)象一前端中文亂碼界面顯示“錕斤拷”。原因有三個(gè)位置必須同時(shí)設(shè)置 UTF-8JSP 頁面頂部的 pageEncoding、servlet 里 request.setCharacterEncoding(utf-8)、jdbc 連接串里的 characterEncodingutf8。少一個(gè)就亂這是老項(xiàng)目的連環(huán)坑。解決三個(gè)位置全部補(bǔ)齊product 添加頁面和注冊(cè)頁面的表單都過一遍?,F(xiàn)象二圖片上傳后 標(biāo)簽顯示裂圖刷新后圖片丟失。原因開發(fā)環(huán)境用 getRealPath 把圖片寫到 tomcat 部署目錄下的 upload 里但 IDEA 重啟或 clean 后整個(gè)部署目錄被重建上傳的文件隨之被刪除。這是 tomcat explode 部署的經(jīng)典副作用。解決把上傳路徑改成工程外目錄比如 D:/upload然后通過映射或單獨(dú) servlet 提供圖片訪問。如果只是跑課程設(shè)計(jì)接受重啟丟圖片的現(xiàn)實(shí)也行但心里要清楚這個(gè)邊界。現(xiàn)象三過濾器把登錄頁的 css/js 也攔截了頁面雖然能打開但是光禿禿的沒有樣式。原因WebFilter(/*) 匹配所有請(qǐng)求css 和 js 也在攔截范圍內(nèi)session 為空時(shí)全部重定向到 index.jsp最終瀏覽器加載靜態(tài)資源時(shí)循環(huán)重定向。我在 4.3 節(jié)里已經(jīng)寫了放行邏輯的完整代碼把路徑判斷提前到 session 判斷之前問題就解決了?,F(xiàn)象四分頁點(diǎn)下一頁時(shí)說 pageIndex 為 null 的 NumberFormatException或者總頁數(shù)算出來是 1 但數(shù)據(jù)有 50 條。原因servlet 里直接 Integer.parseInt(request.getParameter(pageIndex))第一頁時(shí)鏈接沒帶參數(shù)就報(bào)空指針總頁數(shù)用除法直接取整10 條數(shù)據(jù) pageSize10 時(shí)總頁數(shù)算成 1恰好第 11 條就消失了。解決parseInt 前先判空并給默認(rèn)值 1總頁數(shù)用 (total pageSize - 1) / pageSize 計(jì)算。6. 進(jìn)階改造把 jdbc 直連換成 Druid 連接池順便解決連接資源釋放問題如果你準(zhǔn)備把這份源碼改造成能拿去實(shí)習(xí)面試?yán)镏v的工程第一個(gè)要?jiǎng)拥兜牡胤浇^對(duì)不是界面美化而是數(shù)據(jù)源。原生 jdbc 最尷尬的場(chǎng)景就是每次請(qǐng)求都 DriverManager.getConnection用 complete 關(guān) Connection、Statement、ResultSet代碼丑且容易漏關(guān)。我的習(xí)慣是引入 Druid把數(shù)據(jù)源初始化擠到 static 塊里讓整個(gè) webapp 只維護(hù)一個(gè)連接池。先在 lib 目錄放 druid-1.1.24.jar然后在 src 下加一個(gè) DBUtil 改造版import com.alibaba.druid.pool.DruidDataSource; public class DBUtil { private static DruidDataSource dataSource; static { dataSource new DruidDataSource(); dataSource.setDriverClassName(com.mysql.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/xianfeng_db?useUnicodetruecharacterEncodingutf8); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(3); dataSource.setMaxActive(10); dataSource.setMaxWait(3000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }邏輯說明Druid 的 setInitialSize 是啟動(dòng)時(shí)預(yù)建的連接數(shù)setMaxActive 是池中最多同時(shí)活躍的連接數(shù)setMaxWait 是拿不到連接時(shí)最長(zhǎng)等待毫秒數(shù)。這樣就省去了每個(gè) dao 方法里 DriverManager.getConnection 的重復(fù)代碼連接獲取速度也快了一個(gè)數(shù)量級(jí)。原有 dao 層的 try-with-resources 不用動(dòng)因?yàn)?Druid 的 getConnection 返回的是代理連接調(diào)用 close 只是歸還池子不會(huì)真的斷開。dao 層代碼里所有 Class.forName 加載驅(qū)動(dòng)的語句都可以刪掉了數(shù)據(jù)源初始化時(shí)已經(jīng)完成。接下來把這段改動(dòng)跑一遍回歸登錄、添加產(chǎn)品、上傳圖片、分頁瀏覽、退出五個(gè)鏈路全部過一遍重點(diǎn)觀察 Tomcat 控制臺(tái)有沒有連接泄漏提示。改完你會(huì)發(fā)現(xiàn)兩件事第一頁面響應(yīng)速度在連續(xù)點(diǎn)擊分頁時(shí)明顯變快因?yàn)檫B接復(fù)用而不是每次新建第二Tomcat 重啟時(shí)再也不會(huì)因?yàn)檫B接未釋放報(bào)警。從那以后我每次拿到這種課程設(shè)計(jì)源碼都會(huì)強(qiáng)制先做一遍數(shù)據(jù)源改造再談功能順手也會(huì)把過濾器放行路徑完整走查一遍。這套方法希望你也能用上幫你少走彎路。本文還有配套的精品資源點(diǎn)擊獲取