
簡介在Web開發學習路徑中理解底層原理往往比直接使用框架更為關鍵。JSP作為Servlet的模板化擴展本質是在服務端動態生成HTML配合Servlet處理請求分發、JDBC操作數據庫形成了一套完整且透明的請求-響應鏈路。這種經典組合能清晰展示HTTP會話管理、用戶鑒權、SQL查詢與數據渲染的每個環節非常適合課程設計、畢業設計及初學者練手。從數據庫表結構設計、連接池配置到登錄過濾器的實現再到成績統計的聚合查詢與防重復提交的冪等處理每一步都承載著Web工程的核心實踐。無論是構建學生信息管理系統還是其他業務后臺掌握JSPServletMySQL這套技術棧都能為后續學習Spring Boot等高級框架打下堅實基礎。本文以一個學生成績綜合管理系統為藍本完整拆解了從需求分析到故障排查的全過程幫助你快速搭建一個穩定可演示的Java Web項目。 做課程設計或者畢業設計的同學大概率繞不開“學生成績管理系統”這類題目尤其是當題目里明明白白寫著 JavaJSP MySQL 的時候。我身邊不少人一看到 JSP 就皺眉覺得這技術“老掉牙”了但說句實在話對于學習階段的項目、答辯演示、以及理解 Web 開發的核心鏈路來說JSP Servlet MySQL 這套組合反而是最透明的每一步請求怎么走、數據怎么查、頁面怎么渲染你都能看得一清二楚沒有任何黑盒。這個項目雖然掛著“教學系統”的名字但它其實覆蓋了 Web 項目開發從零到一的全部關鍵點——數據庫設計、JDBC 操作、Servlet 控制層、JSP 展示層、登錄鑒權、增刪改查、統計報表一個都不少。學生成績綜合管理系統最適合用來打通 Web 開發任督二脈也適合那些需要快速交付一個穩定可演示系統的同學。這篇文章我就以這個項目為藍本把我實際開發過程中踩過的坑、優化過的細節、以及那些學校文檔里不會寫的東西全部拆開來講。無論你是要用它當課程設計還是想自己動手做一個完整項目練手都能直接照著做。1. 項目整體設計與需求拆解1.1 技術選型背后的邏輯為什么 JSP 仍然是練手的最佳選擇先回答一個大家都會問的問題現在企業里都用 Spring Boot Vue 了為什么還要用 JSP我的看法是學習階段的重點不是“用最新的框架”而是“理解原理”。JSP 的本質是 Servlet它在服務端把 Java 代碼和 HTML 模板揉在一起然后編譯成 Servlet 執行。用 JSP 做項目你被迫去理解 HTTP 請求是怎么到達服務器的、Session 是怎么維持登錄狀態的、JDBC 是怎么把數據庫記錄變成 Java 對象的。這些底層概念換了 Spring Boot 也一樣重要只是框架幫你藏起來了。這個項目我采用的是經典的 JSP Servlet JavaBean 三層結構JSP 層負責頁面展示和表單收集比如登錄頁、學生列表頁、成績錄入頁Servlet 層負責接收請求、調用業務邏輯、跳轉頁面相當于路由器JavaBean / DAO 層負責封裝數據和操作數據庫JDBC 的代碼都寫在這一層。MySQL 作為數據庫是這套組合里最沒有爭議的選擇。MySQL 社區版免費、安裝方便、SQL 語法標準而且市面上教程極多遇到任何問題基本一搜就有答案。對于一個成績管理系統它的數據量級在幾千到幾萬條記錄MySQL 的性能綽綽有余。1.2 功能模塊劃分一個“綜合管理系統”到底要管什么“綜合”兩個字是這個項目的題眼。它不只是簡單的成績錄入而是把學生、教師、課程、成績、用戶權限全部串起來。我按照角色的視角把功能拆成四塊模塊主要功能涉及角色系統登錄用戶名密碼驗證、Session 管理、退出登錄管理員/教師/學生學生管理學號、姓名、班級、院系的新增/修改/刪除/查詢管理員課程管理課程編號、課程名、學分、授課教師的新增/修改/刪除管理員/教師成績管理成績錄入、修改、審核、按課程/班級/個人查詢教師/管理員統計報表平均分、最高分、最低分、及格率、分數段分布全部角色不同角色的權限差異很關鍵。學生登錄后只能看到自己的成績不能看到別人的教師登錄后能錄入和修改自己授課課程的成績但不能改其他課程管理員擁有全部權限。這個權限控制如果用 Spring Security 做配置一大堆反而看不明白用 Session Filter 手動實現你能清楚知道每一個受保護頁面的訪問是經過怎樣的校驗這種“笨辦法”對學習反而更友好。1.3 頁面流程設計從登錄到報表的完整鏈路我建議你先把整個系統的頁面跳轉圖畫出來再動手寫代碼。這個系統的核心鏈路是打開登錄頁 index.jsp → 提交表單到 LoginServlet → 查詢用戶表驗證身份 → 查詢角色類型 → 跳轉對應主頁admin.jsp / teacher.jsp / student.jsp→ 在主頁點“成績管理”進入 ScoreListServlet → JSP 列表頁展示 → 點“錄入”進入 ScoreAddServlet → 錄入成功返回列表頁。這個鏈路每一步都有對應的文件寫完以后你會對“請求-處理-響應”這套模型有非常具象的理解。我見過太多人上來就寫代碼結果寫到一半發現缺了一張表、少了一個 Servlet再回頭改數據庫非常痛苦。先把流程圖畫清楚后面寫代碼只需要照著填。2. 數據庫設計與核心細節2.1 五張核心表結構設計數據庫設計是這個項目的重中之重。很多人建表很隨意導致后面寫 SQL 的時候各種別扭。我的建議是最少建五張表t_user用戶表、t_student學生表、t_teacher教師表、t_course課程表、t_score成績表。先看建表 SQL這是我從實際項目中抽取的簡化版本字段講求實用CREATE DATABASE IF NOT EXISTS student_score_db DEFAULT CHARACTER SET utf8mb4; USE student_score_db; -- 用戶表存登錄賬號密碼和角色 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL COMMENT ADMIN/TEACHER/STUDENT, ref_id INT COMMENT 關聯學生表或教師表的主鍵學生和教師登錄用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 學生表 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 學號, name VARCHAR(50) NOT NULL, gender VARCHAR(10), class_name VARCHAR(50), major VARCHAR(50), enroll_year VARCHAR(10) COMMENT 入學年份 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 教師表 CREATE TABLE t_teacher ( id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工號, name VARCHAR(50) NOT NULL, title VARCHAR(30) COMMENT 職稱, department VARCHAR(50) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 課程表 CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, course_name VARCHAR(100) NOT NULL, credit DECIMAL(3,1) DEFAULT 2.0 COMMENT 學分, teacher_id INT COMMENT 授課教師ID ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 成績表 CREATE TABLE t_score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) COMMENT 成績, semester VARCHAR(20) COMMENT 學期如 2025-2026-1, exam_type VARCHAR(20) DEFAULT 期終 COMMENT 考試類型, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id, semester, exam_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;這里幾個設計意圖說一下。t_user 表單獨拆出來而不是在學生表和教師表里直接加 username 和 password是為了讓登錄邏輯統一不管什么角色都從 t_user 查查到了再根據 role 去對應表拿詳細信息。ref_id 字段記錄關聯的學生或教師主鍵這樣登錄后可以方便地拿到個人信息。t_score 表中我加了一個聯合唯一鍵uk_student_course (student_id, course_id, semester, exam_type)這非常關鍵。它的作用是防止同一個學生在同一學期同一門課被重復錄入成績。如果沒有這個約束程序里忘記判斷數據庫就可能出現兩條一模一樣的成績記錄統計平均分的時候直接翻車。2.2 外鍵、索引與存儲引擎的選擇關于外鍵我的建議是在表結構上不加物理外鍵而是使用邏輯關聯。這個項目里 t_score.student_id 邏輯上引用 t_student.idt_score.course_id 引用 t_course.id但我不建FOREIGN KEY約束。原因很簡單物理外鍵在刪除數據時會有各種限制比如你想刪除一個學生如果他在成績表里有記錄外鍵會阻止刪除或者需要先刪成績這在課程設計答辯演示時非常不靈活。我自己在開發中更多的做法是在應用層保證數據一致性刪除前先查一下有沒有關聯數據。物理外鍵適合數據完整性要求極高、刪除操作極少的生產系統但不適合這種交互頻繁的教學項目。索引方面除了唯一索引我給 t_score 表的 course_id 加了普通索引因為“按課程統計成績”是最常見的查詢ALTER TABLE t_score ADD INDEX idx_course (course_id);2.3 字符集與時區連接 MySQL 最容易踩的坑建庫的時候定了 utf8mb4連接的時候也得對應上。utf8mb4 和 utf8 的區別是前者支持 Emoji 表情和生僻字現在建庫無腦用 utf8mb4 就行。JDBC 連接串里要明確指定參數jdbc:mysql://localhost:3306/student_score_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8mb4是告訴驅動用 UTF-8 編碼傳輸數據serverTimezoneAsia/Shanghai是針對 MySQL 8.x 的時區設置。如果不加 serverTimezone經常會報The server time zone value ?D1ú±ê×?ê±?? is unrecognized這種亂碼報錯。這個我后面在常見問題里再詳細展開。3. 核心代碼實現與實操3.1 項目目錄結構Java Web 項目的經典布局用 IDEA 2019 及以上版本創建一個 Web 項目項目的目錄結構長這樣StudentScoreSystem/ ├── src/ │ └── com/score/ │ ├── dao/ │ │ ├── UserDao.java │ │ ├── StudentDao.java │ │ ├── TeacherDao.java │ │ ├── CourseDao.java │ │ └── ScoreDao.java │ ├── entity/ │ │ ├── User.java │ │ ├── Student.java │ │ ├── Teacher.java │ │ ├── Course.java │ │ └── Score.java │ ├── servlet/ │ │ ├── LoginServlet.java │ │ ├── LogoutServlet.java │ │ ├── StudentServlet.java │ │ ├── CourseServlet.java │ │ ├── ScoreQueryServlet.java │ │ └── ScoreAddServlet.java │ └── util/ │ └── DBUtil.java ├── web/ │ ├── index.jsp │ ├── admin/ │ │ ├── admin_home.jsp │ │ ├── student_list.jsp │ │ └── course_list.jsp │ ├── teacher/ │ │ └── teacher_home.jsp │ ├── student/ │ │ └── student_home.jsp │ └── WEB-INF/ │ ├── web.xml │ └── lib/ │ └── mysql-connector-java-8.0.30.jarentity 包里的類就是 JavaBean字段和數據庫表一一對應dao 包里的類負責 JDBC 操作servlet 包接收請求調用 dao再把結果塞進 request 或 session最后 forward 或 redirect 到 JSP 頁面。分包清晰的好處是后期改了表結構你只需要改 entity 和 daocontrol 層和視圖層基本不用動。3.2 JDBC 連接工具類 DBUtil寫一次到處用JDBC 連接的代碼很固定我把它封裝成一個工具類。這里注意一點連接信息用配置文件管理不要硬編碼在 Java 代碼里。我在 src 目錄下放一個 db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/student_score_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456然后 DBUtil 讀取這個配置文件package com.score.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class DBUtil { private static String driver; private static String url; private static String username; private static String password; static { try { InputStream in DBUtil.class.getClassLoader().getResourceAsStream(db.properties); Properties props new Properties(); props.load(in); driver props.getProperty(jdbc.driver); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }Class.forName(driver)這行代碼如果你用的是 MySQL 8.x 驅動com.mysql.cj.jdbc.Driver必須要寫MySQL 5.x 的驅動是 com.mysql.jdbc.Driver不要混。很多同學報ClassNotFoundException就是驅動類路徑寫錯或者 jar 包沒放到 WEB-INF/lib 下。注意關資源的順序是 ResultSet → Statement → Connection先開后關。我見過有人只關了 Connection不關 Statement 和 ResultSet短時間看不出問題但連接池一緊張就會報連接泄漏。3.3 登錄鑒權與 Session 管理別把密碼明文存數據庫登錄功能是整個系統的門面也是過濾器邏輯的核心。先看 LoginServlet 的核心邏輯package com.score.servlet; import com.score.dao.UserDao; import com.score.entity.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; WebServlet(/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); UserDao userDao new UserDao(); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); // 30分鐘無操作自動過期 if (ADMIN.equals(user.getRole())) { response.sendRedirect(request.getContextPath() /admin/admin_home.jsp); } else if (TEACHER.equals(user.getRole())) { response.sendRedirect(request.getContextPath() /teacher/teacher_home.jsp); } else { response.sendRedirect(request.getContextPath() /student/student_home.jsp); } } else { request.setAttribute(msg, 用戶名或密碼錯誤); request.getRequestDispatcher(/index.jsp).forward(request, response); } } }密碼存儲這里我多說兩句。課程設計里很多人直接明文存密碼圖省事但我覺得做項目還是要有基本的安全意識。正確做法是存 MD5 或 SHA-256 的哈希值甚至加鹽。舉一個簡單實現用戶注冊時用DigestUtils.md5Hex(password salt)存進去登錄校驗時把輸入密碼做同樣的哈希再比對。JDK 自帶 MessageDigest不需要引額外依賴import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5Util { public static String md5(String source) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(source.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { String hex Integer.toHexString(b 0xff); if (hex.length() 1) sb.append(0); sb.append(hex); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } } }登錄之后怎么保護頁面用 Filter 是最標準的做法。LoginFilter 攔截所有 /admin/、/teacher/、/student/* 的請求檢查 session 里有沒有 loginUser沒有就跳回登錄頁package com.score.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/admin/*) public class AdminFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /index.jsp); return; } // 這里還可以繼續判斷角色不是 ADMIN 就 403 chain.doFilter(req, resp); } }這里有個小細節request.getSession(false)而不是getSession()。傳 false 表示如果當前沒有 Session 就返回 null而不是創建一個新的。如果你傳 true攻擊者隨便訪問一個受保護頁面就能讓服務器創建 Session浪費內存也不是好習慣。3.4 成績錄入與統計報表SQL 是系統的靈魂成績查詢是整個系統最花心思的部分?;拘枨笫前凑n程查詢成績單顯示學號、姓名、班級、成績。這個時候必須用 JOIN 而不是單表查詢。看這條核心 SQLSELECT s.student_no, s.name, s.class_name, c.course_name, sc.score, sc.semester FROM t_score sc JOIN t_student s ON sc.student_id s.id JOIN t_course c ON sc.course_id c.id WHERE c.id ? ORDER BY sc.score DESC;這條 SQL 查出來的結果能直接映射到 JSP 表格里。在 ScoreQueryServlet 中執行查詢遍歷 ResultSet 放到 List再 setAttribute 到 request 轉發給 JSPListMapString, Object list new ArrayList(); while (rs.next()) { MapString, Object row new HashMap(); row.put(student_no, rs.getString(student_no)); row.put(name, rs.getString(name)); row.put(class_name, rs.getString(class_name)); row.put(course_name, rs.getString(course_name)); row.put(score, rs.getBigDecimal(score)); row.put(semester, rs.getString(semester)); list.add(row); } request.setAttribute(scoreList, list);統計報表是課程設計的加分項。我用一條 GROUP BY SQL 算出分數段分布SELECT SUM(CASE WHEN score 90 THEN 1 ELSE 0 END) AS excellent, SUM(CASE WHEN score 80 AND score 90 THEN 1 ELSE 0 END) AS good, SUM(CASE WHEN score 70 AND score 80 THEN 1 ELSE 0 END) AS medium, SUM(CASE WHEN score 60 AND score 70 THEN 1 ELSE 0 END) AS pass, SUM(CASE WHEN score 60 THEN 1 ELSE 0 END) AS fail, COUNT(*) AS total, ROUND(AVG(score), 2) AS avg_score, MAX(score) AS max_score, MIN(score) AS min_score FROM t_score WHERE course_id ?;CASE WHEN 配合 SUM 的寫法是統計報表的經典思路一行 SQL 就能算出所有分段數據不用在 Java 里循環判斷。ROUND 保留兩位小數讓平均分更美觀。這條 SQL 一寫出來直接在答辯時演示老師一定認可。3.5 成績錄入的冪等處理防止重復提交錄入成績時教師可能手快提交兩次或者同一學生被重復導入。前面我們在表結構上加了聯合唯一鍵 uk_student_course這里再用INSERT ... ON DUPLICATE KEY UPDATE來兜底INSERT INTO t_score (student_id, course_id, score, semester, exam_type) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE score VALUES(score);這句 SQL 的意思是如果唯一鍵student_id course_id semester exam_type已經存在就更新成績不存在就插入。這樣極大簡化了代碼邏輯不用先查一遍判斷是否已存在再決定 insert 還是 update。這個技巧在真實項目中非常常用值得記下來。4. 常見問題與排查技巧實錄4.1 JSP 頁面中文亂碼十個人九個會遇到亂碼問題的根源是編碼不統一。排查思路是順著“請求 → 響應 → 數據庫”三層逐一檢查。第一層JSP 頁面編碼。每個 JSP 頂部要寫% page contentTypetext/html;charsetUTF-8 languagejava %第二層Servlet 接收請求的編碼。在 doPost 方法第一行寫request.setCharacterEncoding(UTF-8);第三層數據庫連接串前面已經提到要帶characterEncodingutf8mb4。只要這三層統一成 UTF-8中文亂碼基本不會出現。如果你發現 JSP 頁面顯示正常但插入數據庫后是問號那一定是第三步沒配置好如果頁面本身出現亂碼那第一步或者第二步的優先級搞反了。4.2 數據庫連接失敗時區、驅動、jar 包三連坑數據庫連接失敗是最常見的啟動錯誤我把它整理成了一個排查清單錯誤現象可能原因解決方法ClassNotFoundException驅動 jar 沒放進 WEB-INF/lib把 mysql-connector-java-x.x.xx.jar 放到 lib 目錄Access denied for user用戶名密碼錯核對 db.propertiesThe server time zone ... unrecognized連接串缺 serverTimezone加上 serverTimezoneAsia/ShanghaiCommunications link failureMySQL 沒啟動或端口不對檢查 MySQL 服務狀態和 3306 端口檢查端口是否被占用可以用命令netstat -ano | findstr 3306Windows或者lsof -i:3306macOS/Linux。經常有同學 MySQL 服務沒啟動就在啟動 Tomcat然后報一堆 Connection refused 的錯先看服務再看代碼。4.3 IDEA 中 Web 項目部署報 404 或 500IDEA 里跑 Web 項目最煩的坑有兩個。第一個是 404。明明代碼沒錯訪問的是/admin/admin_home.jsp卻報 404。八成是 Artifact 配置不對——IDEA 的 Artifact 決定了哪些文件會被打進部署包里。檢查 Project Structure → Artifacts確保Web Resource Directory指向項目的 web 目錄Output Layout里能看到 WEB-INF 和 JSP 文件。修改后一定要 Rebuild 再重啟 Tomcat。第二個是 500 的JasperException或者ClassNotFoundException。這種情況幾乎都是依賴缺失或者編譯輸出目錄不對。確認Project Structure → Modules → Dependencies里是否有 MySQL 驅動 jar并且 Scope 選擇 Compile。JSP 是在 Tomcat 運行時動態編譯成 Servlet 的它需要能在 classpath 里找到你的 DAO 和 Entity 類。4.4 成績查詢性能問題幾千條數據也卡雖然系統數據量不大但我也遇到過查詢變慢的情況原因是 JSP 里嵌套了太多邏輯。舉個例子原本在 score_list.jsp 里為了顯示學生姓名每行都調用一次 StudentDao 查數據庫這就是經典的 N1 查詢問題——列表有 50 條成績就要額外執行 50 次學生查詢。解決辦法是用 SQL 的 JOIN 一次性把關聯數據查出來而不是在 JSP 里反復查詢。這也是我前面堅持用 JOIN SQL 的原因。JSP 里只負責遍歷展示絕不在標簽內寫 JDBC 代碼。你的 JSP 里如果出現了%加 Java 代碼做數據庫操作的痕跡就要注意了——這正是風格老派且難維護的根源。4.5 JSP 導入類失敗或語法高亮不正常有時候 IDEA 里 JSP 文件一片紅色導入的類報錯。這通常是項目沒有正確關聯 Tomcat 和 JDK。檢查 Project Structure 里是否添加了項目的 SDK并且 Tomcat 的 library 是否引入。IDEA 設置里 JSP 語法高亮也可以手動開啟Settings → Editor → File Types → 確認.jsp文件被識別為 JSP/JSPX。5. 實操過程中的一些體會整個項目做完我自己最大的感受是這種“老技術棧”的系統反而能幫你把基礎打得非常牢。寫 JSP Servlet 的過程你會被迫去思考 HTTP 協議的無狀態性、Session 的過期機制、JDBC 的資源管理、SQL 的 JOIN 和聚合函數——這些都是換了任何框架都繞不開的知識點。我在做這個項目的時候最初也犯過“一張大表搞定所有字段”的錯誤把學生、課程、成績全部塞進一張表結果查詢寫起來非常痛苦。后來拆成五張表邏輯一下就清楚了。這也算是一個經驗數據庫設計一定先想清楚實體關系學生和課程是多對多中間要有一張關聯表而成績本身就可以作為關聯表的屬性存在。最后再分享一個小技巧在答辯或演示之前提前往數據庫里插入一批像樣的測試數據比如 20 個學生、5 門課、每條成績都有合理的分數分布這樣統計報表的折線圖、柱狀圖如果做了可視化看起來才會漂亮。如果數據全是 100 分或者全是隨機數老師一眼就覺得是隨便填的。這個項目最大的價值不是那個“成績管理系統”本身而是它訓練了你從需求分析、數據庫設計、編碼實現到故障排查的完整思維鏈條。本文還有配套的精品資源點擊獲取