:數(shù)據(jù)庫設(shè)計與部署避坑全解析)
簡介針對JavaWeb方向?qū)W習(xí)與畢業(yè)設(shè)計人群這份運動與健康管理系統(tǒng)完整源代碼與數(shù)據(jù)庫資源可用于快速掌握用戶管理、運動記錄、健康監(jiān)測、數(shù)據(jù)分析等典型模塊的開發(fā)流程也可作為課程設(shè)計的起始模板。壓縮包共707個文件約9.59MB整體以jsp頁面與java類承載后端業(yè)務(wù)邏輯js與css負責(zé)前端交互與樣式gif、jpg提供界面演示素材sql為數(shù)據(jù)庫初始化腳本jar為依賴庫另有html、xml等輔助配置目錄結(jié)構(gòu)清晰便于按模塊對照學(xué)習(xí)。已有2019人學(xué)習(xí)查看適用于需要參考完整項目實現(xiàn)、理解表結(jié)構(gòu)設(shè)計與Servlet/JSP開發(fā)思路的讀者。通過源碼可看到用戶表、運動記錄表、健康指標表的關(guān)聯(lián)方式同時附帶的項目配置與靜態(tài)資源能幫助還原運行環(huán)境是JavaWeb綜合項目實踐的一份實用參考資料。1. JavaWeb 運動與健康管理系統(tǒng)為什么值得下載并且跑通它如果你搜過「javawe 運動與健康管理系統(tǒng) 源代碼」——沒錯后綴少個 b 是常見拼寫——大概率看到一大排壓縮包鏈接。我拆過好幾套之后有個反直覺的結(jié)論這類系統(tǒng)真正值錢的不是「能登錄、能打卡」的 CRUD 殼子而是數(shù)據(jù)庫表結(jié)構(gòu)和后端分層寫法能不能經(jīng)得起你往里面加需求。這套基于 JSP Servlet MySQL 的經(jīng)典 JavaWeb 架構(gòu)幾乎覆蓋了課設(shè)和畢業(yè)設(shè)計里所有要求用戶端運動打卡、個人健康檔案、管理員后臺、數(shù)據(jù)統(tǒng)計預(yù)覽外加完整數(shù)據(jù)庫腳本。適合正在做 JavaWeb 課設(shè)的學(xué)生、想練手 Servlet 全流程的開發(fā)者也想用現(xiàn)成源碼二次改造成內(nèi)部健康管理小工具的人。下面我按拆包順序講先立住數(shù)據(jù)模型再講功能實現(xiàn)然后把部署時最容易翻車的三個坎鋪開說。2. 從 ER 圖到建表腳本運動打卡數(shù)據(jù)怎么存才不返工2.1 四張核心表用戶、運動記錄、健康指標、管理員拆開數(shù)據(jù)庫腳本一般是一個.sql文件項目解壓后通常放在db/或sql/目錄第一件事不是雙擊導(dǎo)入而是先打開看建表結(jié)構(gòu)。這套系統(tǒng)的核心表有四個用戶表、運動記錄表、健康指標表、管理員表。健康指標表是很多人做設(shè)計時候遺漏的一張表——如果沒有它「個人健康檔案」頁面里的體重、心率、睡眠時長根本沒有獨立的數(shù)據(jù)載體最后會塞進用戶表變成一堆冗余字段。四張表的核心字段如下表名核心字段關(guān)鍵設(shè)計t_userid、username、password、age、gender、height、weight、create_time密碼建議 MD5 處理身高體重冗余存儲方便首頁直接讀t_exercise_recordid、user_id、exercise_type、duration_minutes、calories、record_date、create_time日期用 DATE 類型熱量允許小數(shù)t_health_metricid、user_id、weight、heart_rate、sleep_hours、metric_date、create_time同一天可有多次記錄但預(yù)警統(tǒng)計時取最新一條t_adminid、username、password、real_name、role與業(yè)務(wù)表完全解耦登錄入口獨立為什么運動記錄表要單獨拆出來而不是合并進用戶表因為「運動打卡」在業(yè)務(wù)上天然是一對多的一個用戶一天可以打卡多次一次跑步一次拉伸是兩條記錄。如果把這些塞進用戶表后面寫統(tǒng)計 SQL 時每查一次運動時長都要做字符串切割那才是真正的返工地獄。健康指標表同理體重和心率是會變化的量必須按日期累積不然趨勢圖沒數(shù)據(jù)可畫。2.2 建表腳本主鍵、時間戳、唯一索引缺一不可不要直接在 Navicat 里手動畫表把 SQL 文件拖進命令行或客戶端執(zhí)行一遍更穩(wěn)妥。這套系統(tǒng)的核心建表語句大概是這樣的結(jié)構(gòu)CREATE TABLE t_exercise_record ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT 主鍵, user_id INT NOT NULL COMMENT 關(guān)聯(lián) t_user.id, exercise_type VARCHAR(32) NOT NULL COMMENT 運動類型跑步/游泳/騎行/拉伸, duration_minutes INT NOT NULL COMMENT 運動時長單位分鐘, calories DECIMAL(8,2) DEFAULT 0 COMMENT 預(yù)估消耗熱量單位千卡, record_date DATE NOT NULL COMMENT 打卡日期, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 記錄創(chuàng)建時間, UNIQUE KEY uk_user_date (user_id, record_date, exercise_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT運動打卡記錄表;這段腳本里值得留意三個細節(jié)。第一AUTO_INCREMENT主鍵是常規(guī)選擇刪除記錄后主鍵不會復(fù)用統(tǒng)計口徑不會被過往數(shù)據(jù)污染。第二UNIQUE KEY uk_user_date是我拆這套系統(tǒng)時最想夸的地方——它防止了「同一天同一種運動重復(fù)打卡」這個約束放在應(yīng)用層做要多寫一堆查詢放在數(shù)據(jù)庫層一行就堵死了。第三DECIMAL(8,2)而不是FLOAT熱量計算要的是精確兩位小數(shù)浮點數(shù)在累計求和時會抖出 0.0000001 的誤差雖然肉眼看不出來但報表對不上時會讓人懷疑人生。2.3 數(shù)據(jù)類型選型為什么熱量和體重都用 DECIMAL建表腳本里另一個值得說的是類型選型邏輯。duration_minutes用 INT因為運動時長是整數(shù)分鐘calories用 DECIMAL(8,2)因為熱量計算涉及運動系數(shù)結(jié)果可能是小數(shù)gmt_create這類時間字段統(tǒng)一 DATETIME不要用 TIMESTAMP因為 TIMESTAMP 的范圍只到 2038 年而且受時區(qū)影響DATETIME 更省心。這里有個真實發(fā)生過的教訓(xùn)某開發(fā)者在二次開發(fā)時把體重字段改成了DOUBLE理由是「JSP 頁面上顯示體重后端接個字符串就轉(zhuǎn)了無所謂」。后面做月度體重曲線圖時發(fā)現(xiàn)AVG(weight)出來一串 60.00000000001前端圖表點尾數(shù)全是 1。從那以后我對建表腳本的原則就是凡是展示給用戶看、參與計算或者聚合的數(shù)值一律 DECIMALFLOAT/DOUBLE只允許出現(xiàn)在科學(xué)計算這種不追求精確的場景。3. 用戶端四個功能模塊登錄、打卡、報表、個人中心的后端實現(xiàn)3.1 從 JSP 到 Servlet 的請求流轉(zhuǎn)一次打卡請求怎么走完整條鏈路這套系統(tǒng)是典型的 MVC 結(jié)構(gòu)JSP 負責(zé)渲染Servlet 負責(zé)接請求調(diào)業(yè)務(wù)DAO 負責(zé)和數(shù)據(jù)庫打交道。很多新手把 JSP 直接寫上 Java 代碼頁面里塞一堆%那不是不能跑但后面改需求時會變得沒法看。這套系統(tǒng)的分層拆得比較干凈——雖然代碼不是最新式框架但「JSP 只負責(zé)收參數(shù)、Servlet 只負責(zé)轉(zhuǎn)方向、DAO 只負責(zé)數(shù)據(jù)庫」這條線是清楚的。登錄請求的流轉(zhuǎn)大概是這樣的用戶在login.jsp輸入用戶名密碼表單methodpost提交到LoginServlet。Servlet 調(diào)用UserDao.findByUsernameAndPassword()查到了就把user對象放進 Session跳轉(zhuǎn)index.jsp查不到就request.setAttribute(error, 用戶名或密碼錯誤)轉(zhuǎn)發(fā)回登錄頁。核心代碼是這樣WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(error, 用戶名或密碼錯誤); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }這里有個關(guān)鍵參數(shù)容易被忽略request.setCharacterEncoding(UTF-8)必須放在getParameter()調(diào)用之前。如果這一行漏了或放后面中文用戶名和密碼會亂碼查庫直接失敗下次登錄會顯示「用戶名或密碼錯誤」而實際上密碼是對的——這個坑后面避坑章還會詳細說。getParameter拿到的全是字符串DAO 層做密碼校驗后返回User對象或null這只是項目里最常見的寫法不是唯一寫法。3.2 打卡核心DAO 層插入前先做「重復(fù)判斷」運動打卡是用戶端最核心的操作。前端表單收集運動類型、時長、日期提交到AddExerciseServletServlet 調(diào)ExerciseRecordDao.insert(). 真正寫代碼時要處理的業(yè)務(wù)邏輯是同一天同一種運動不能重復(fù)打卡。數(shù)據(jù)庫那層UNIQUE KEY是兜底但在應(yīng)用層最好也先查一次因為數(shù)據(jù)庫報DuplicateKeyException時異常不夠友好頁面直接白屏。public boolean insert(ExerciseRecord record) { String checkSql SELECT id FROM t_exercise_record WHERE user_id ? AND record_date ? AND exercise_type ?; String insertSql INSERT INTO t_exercise_record (user_id, exercise_type, duration_minutes, calories, record_date) VALUES (?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement checkPs conn.prepareStatement(checkSql); PreparedStatement insertPs conn.prepareStatement(insertSql)) { checkPs.setInt(1, record.getUserId()); checkPs.setDate(2, new java.sql.Date(record.getRecordDate().getTime())); checkPs.setString(3, record.getExerciseType()); ResultSet rs checkPs.executeQuery(); if (rs.next()) { return false; // 當天同類型已存在返回 false 由 Servlet 提示用戶 } insertPs.setInt(1, record.getUserId()); insertPs.setString(2, record.getExerciseType()); insertPs.setInt(3, record.getDurationMinutes()); insertPs.setBigDecimal(4, record.getCalories()); insertPs.setDate(5, new java.sql.Date(record.getRecordDate().getTime())); return insertPs.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }這段代碼用了三重嵌套的try-with-resources連接和語句都會自動關(guān)閉這在 JDK 7 之后的支持很好課設(shè)代碼里常見老式finally手動關(guān)連接但那樣一旦中間某個語句異常連接就泄漏了。PreparedStatement用占位符?傳參避免拼接 SQL 導(dǎo)致注入——這是這類項目必須做扎實的點因為運動健康系統(tǒng)涉及用戶隱私數(shù)據(jù)登錄接口如果用拼接字符串被注入一次就全表淪陷。3.3 個人中心把體重、心率、睡眠聚合成一張數(shù)據(jù)卡個人中心頁面展示用戶最近一次體重、心率以及近 7 天運動匯總。頁面組裝的 SQL 邏輯比 Java 循環(huán)聚合方便得多。近 7 天匯總的典型寫法是在 SQL 層按日期分組SELECT record_date, SUM(duration_minutes) AS total_minutes, SUM(calories) AS total_calories FROM t_exercise_record WHERE user_id ? AND record_date DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY record_date ORDER BY record_date ASC;這段 SQL 里DATE_SUB(CURDATE(), INTERVAL 7 DAY)是計算「7 天前」的日期CURDATE()只取當前日期不取時間避免當天凌晨打卡被日期邊界問題干擾。用GROUP BY record_date按天聚合返回的結(jié)果直接就是前端圖表想要的數(shù)據(jù)結(jié)構(gòu)Java 層只需要while(rs.next())循環(huán)塞進 List不需要在內(nèi)存里做二次求和。這里不要用SELECT *再在 Java 里算總和——數(shù)據(jù)庫做聚合比 Java 循環(huán)快得多而且代碼量少一半。這套系統(tǒng)里所有涉及「按時間統(tǒng)計」的地方基本都是這個套路理解了這一條后面管理后臺的統(tǒng)計圖表你基本也能看懂。4. 管理后臺與統(tǒng)計視圖讓運動數(shù)據(jù)變成健康畫像4.1 用戶管理不靠「刪除」靠「狀態(tài)位」管理后臺的用戶管理模塊常見的課設(shè)實現(xiàn)是「點刪除直接 DELETE FROM t_user」這套系統(tǒng)用的是軟刪除t_user表里有一個status字段0正常、1禁用。管理員點擊「禁用」時執(zhí)行的 SQL 不是刪除而是更新狀態(tài)位UPDATE t_user SET status 1 WHERE id ?為什么這樣做因為用戶的運動記錄表t_exercise_record.user_id關(guān)聯(lián)了用戶表主鍵如果直接物理刪除用戶記錄表里的歷史數(shù)據(jù)就成了孤行——統(tǒng)計報表會出現(xiàn)「查不到用戶名只有一串數(shù)字」的臟數(shù)據(jù)。軟刪除之后登錄校驗會自動加一個status 0的條件被禁用的用戶雖然還在表里但登錄不進去歷史數(shù)據(jù)仍然可以被后臺統(tǒng)計。這里有兩點補充。第一刪運動記錄和刪用戶是兩回事如果只是為了騰空間刪的是記錄表數(shù)據(jù)如果是封號動的是用戶表狀態(tài)位。第二如果你在二次開發(fā)時要同時干凈清除某個用戶的所有數(shù)據(jù)那要先把t_exercise_record、t_health_metric里的關(guān)聯(lián)記錄刪掉最后再刪t_user順序反過來外鍵會報錯。4.2 健康預(yù)警一條 SQL 揪出「久坐高危用戶」這套系統(tǒng)后臺有個健康預(yù)警模塊邏輯很簡單最近 7 天運動總時長低于 30 分鐘的用戶會被標為「運動不足」。這個模塊的價值不在算法多高級而在 SQL 怎么寫——只用一條關(guān)聯(lián)子查詢就能掃出全表高危用戶。SELECT u.id, u.username, IFNULL(SUM(e.duration_minutes), 0) AS total_minutes FROM t_user u LEFT JOIN t_exercise_record e ON e.user_id u.id AND e.record_date DATE_SUB(CURDATE(), INTERVAL 7 DAY) WHERE u.status 0 GROUP BY u.id HAVING total_minutes 30;注意這里用的是LEFT JOIN而不是INNER JOIN因為一個用戶如果在過去 7 天壓根沒運動他不會有任何記錄行用 INNER JOIN 會把他漏掉LEFT JOIN保證每個用戶都出現(xiàn)在結(jié)果集里IFNULL(SUM(...), 0)把沒有記錄的用戶的匯總值填補成 0。最后HAVING total_minutes 30是在分組后過濾WHERE做不到這一點——這是新手寫統(tǒng)計 SQL 最常見的翻車點WHERE在分組前執(zhí)行它不認識聚合列加上HAVING才能對分組后的聚合結(jié)果過濾。4.3 統(tǒng)計圖表的數(shù)據(jù)接口聚合查詢怎么寫才不難看管理后臺的首頁通常有「運動類型占比」「每日活躍用戶數(shù)」等圖表。這套系統(tǒng)的圖表數(shù)據(jù)走的是 JSON 接口由一個StatisticServlet返回。典型的查詢是統(tǒng)計每種運動類型的累計時長 TOP5SELECT exercise_type, SUM(duration_minutes) AS total_minutes FROM t_exercise_record WHERE record_date DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY exercise_type ORDER BY total_minutes DESC LIMIT 5;執(zhí)行完這個查詢后后臺把每行結(jié)果轉(zhuǎn)成 JSON 數(shù)組格式大概是[[跑步, 320], [騎行, 180]]前端圖表庫直接拿數(shù)組渲染餅圖或柱狀圖。這個數(shù)據(jù)接口有兩個常見問題一是LIMIT 5會截斷數(shù)據(jù)餅圖的「其他」類別需要 Java 層手動把第 6 名以后的數(shù)據(jù)合并成一行「其他」二是日期范圍 30 天是常量如果想讓管理員在頁面上自定義日期范圍把這個常量替換成參數(shù)即可——StatisticServlet里加一個receiveParam(days)默認 30接口就變通用了。圖表聯(lián)動這塊這套系統(tǒng)的實現(xiàn)不算復(fù)雜但架構(gòu)是通的前端定時請求/statistic/recent拿到上次時間戳之后的新增打卡記錄直接更新圖表數(shù)據(jù)點。如果你要把它改成實時大屏追加推送通道就行不用動后端查詢接口。5. 部署避坑實錄JDK 版本、字符集、連接池這幾個坎最常翻車5.1 JDK 版本與 MySQL 驅(qū)動不匹配ClassNotFoundException 不是驅(qū)動沒放現(xiàn)象Tomcat 啟動后訪問登錄頁一切正常一提交表單直接白屏控制臺拋ClassNotFoundException: com.mysql.cj.jdbc.Driver。很多人第一反應(yīng)是「缺 jar」把驅(qū)動包反復(fù)拷進WEB-INF/lib還是報錯。原因這個項目如果數(shù)據(jù)庫腳本是用 MySQL 5.x 寫的而本機裝的是 MySQL 8.x舊版驅(qū)動com.mysql.jdbc.Driver在 MySQL 8 連接串下已經(jīng)不適用了。更隱蔽的是如果本機用的是 JDK 8 但驅(qū)動包是新版也會因為編譯版本問題拋UnsupportedClassVersionError。解決先確認 MySQL 版本。MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 用com.mysql.cj.jdbc.Driver同時要把連接串加參數(shù)serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingUTF-8。改完之后重啟 Tomcat如果還拋Public Key Retrieval is not allowed再在連接串加allowPublicKeyRetrievaltrue。這三個參數(shù)是這類項目里最常見的「差一個就死」配置。5.2 中文亂碼三個地方字符集不一致表單提交就變問號現(xiàn)象登錄頁中文用戶名輸入「張三」點擊登錄后提示「用戶名或密碼錯誤」打開數(shù)據(jù)庫查到username存的值是????。頁面顯示正常一寫庫就亂。原因字符集有三層任何一層不一致都會翻車——JSP 頁面編碼、Servlet 接收請求的編碼、MySQL 表結(jié)構(gòu)的編碼。很多時候建表腳本用的utf8實際是 MySQL 的 utf8mb3而 JDBC 連接串沒帶characterEncodingUTF-8數(shù)據(jù)庫層就把 Java 傳來的字符串按默認字符集轉(zhuǎn)換中文全部變成問號。解決逐層核對。把建表腳本的DEFAULT CHARSETutf8mb4統(tǒng)一寫死JDBC 連接串帶useUnicodetruecharacterEncodingUTF-8每個 Servlet 的doPost第一行加request.setCharacterEncoding(UTF-8)如果頁面是動態(tài)渲染還要檢查 JSP 頂部contentTypetext/html; charsetUTF-8是否漏了。這一套走完中文亂碼基本根治。5.3 數(shù)據(jù)庫連不上本地能跑換臺機器就報 Connection refused現(xiàn)象源碼在自己電腦上跑得好好的發(fā)給某同事或換了一臺電腦啟動后訪問列表頁直接報Communications link failure或者Access denied for user。原因這類項目的數(shù)據(jù)庫配置通常寫在src/jdbc.properties或db.properties里常見寫法是jdbc.urljdbc:mysql://localhost:3306/health。換機器后如果目標機器的 MySQL 沒建同名數(shù)據(jù)庫、用戶名密碼不一致或者本機 MySQL 端口不是 3306直接連不上是必然的。更隱蔽的是配置里用了絕對路徑加載配置文件導(dǎo)致別人那邊資源加載失敗。解決拆包后第一件事就是打開jdbc.properties或DBUtil.java里的連接常量確認庫名、用戶名、密碼和當前環(huán)境一致。導(dǎo)入數(shù)據(jù)庫前先確認庫是否建好、腳本是否完整跑完、navicat 里能否用同樣的賬號密碼直接連上。如果jdbc.properties路徑有問題建議把配置文件直接放到src根目錄用類路徑加載避免FileNotFoundException。5.4 Tomcat 9 下 EL 表達式不生效頁面輸出 ${user.username}現(xiàn)象JSP 頁面里寫了${user.username}出來不是值而是原樣顯示字符串“${user.username}”。頁面不上數(shù)據(jù)也不報錯數(shù)據(jù)在 Servlet 端明明查到了。原因Tomcat 對 JSP 的 EL 支持默認分版本Servlet 3.0 / JSP 2.2 以上才默認開啟 EL但項目如果用了老的 web.xml 頭聲明比如web-app版本是 2.3 或 2.4EL 會被當成普通文本。很多課設(shè)項目的 web.xml 是多年前從網(wǎng)上抄的頭聲明版本過低就等于關(guān)閉了 EL 求值。解決把web.xml頭改成 Servlet 3.1Tomcat 8/9 都支持或者干脆把web-app根元素版本聲明升級到 3.0 以上。如果不想動 web.xml更省事的做法是把${user.username}改成% ((User)request.getSession().getAttribute(loginUser)).getUsername() %先跑通功能再回頭收拾版本號。6. 把課設(shè)升級成真實項目的第一步把 JDBC 直連接成 Druid 連接池6.1 為什么第一步要換連接池高并發(fā)只是順帶的穩(wěn)定才是剛需課設(shè)代碼里的DBUtil.getConnection()常見寫法是DriverManager.getConnection(url, user, password)每次請求都新建物理連接。單機演示沒問題一旦頁面里多個圖表接口同時請求MySQL 的連接數(shù)會被打滿報Too many connections。Druid 連接池的核心價值不是「變快」而是「連接復(fù)用 自動回收」讓應(yīng)用負載上來時不會瞬間把數(shù)據(jù)庫壓垮。這個改造的復(fù)雜度并不高因為它只動DBUtil一個類。6.2 改造步驟DruidUtil 替換 DriverManager在src目錄加一個druid.propertiesdriverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/health?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20 maxWait10000 testWhileIdletrue validationQuerySELECT 1然后新建一個DruidUtil.javapublic class DruidUtil { private static DataSource dataSource; static { try (InputStream in DruidUtil.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }改完之后原來調(diào)DBUtil.getConnection()的地方只需要把類名換成DruidUtilDAO 代碼一行都不用動。initialSize5是啟動時預(yù)先創(chuàng)建 5 個連接maxActive20是連接池上限超過這個數(shù)新請求會進入maxWait等待隊列testWhileIdletrue會在連接閑置時用SELECT 1探活避免拿到已被數(shù)據(jù)庫掐斷的死連接。這幾個參數(shù)里validationQuerySELECT 1是這套系統(tǒng)最值得折騰的一行——MySQL 8 默認連接超時是 8 小時如果晚上掛服務(wù)器第二天早上訪問報連接斷十有八九是沒加探活語句。6.3 驗證改造有沒有生效改完后用如下方式自查正常啟動項目多刷新幾個圖表頁然后打開 MySQL 命令行執(zhí)行SHOW STATUS LIKE Threads_connected;。如果連接池生效這個值應(yīng)該穩(wěn)定在個位數(shù)而不是隨著訪問量增長連續(xù)刷新 50 次頁面后再看連接數(shù)不會猛增。如果報Too many connections八成是maxActive配得太高而 MySQL 的max_connections默認才 151把maxActive調(diào)到 20 以下基本不會再碰到。從那以后我每次拿到新源碼不管是課設(shè)還是畢設(shè)第一件事就是先打開數(shù)據(jù)庫配置文件和連接工具類配合當前環(huán)境把連接池、字符集、驅(qū)動版本一次配齊再啟動項目。這套運動健康系統(tǒng)斷斷續(xù)續(xù)解剖了幾天數(shù)據(jù)庫腳本從頭到尾過了一遍該藏的坑基本都翻出來了。希望幫到你。本文還有配套的精品資源點擊獲取