news 2026/10/7 1:01:38

学生成绩管理系统V2实战:Servlet+JSP+MySQL+Bootstrap整合

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
学生成绩管理系统V2实战:Servlet+JSP+MySQL+Bootstrap整合

简介:这是一套基于 Java 技术栈实现的学生成绩管理系统,整合 Servlet、JSP、Bootstrap 与 MySQL,适合 Java Web 初学者、毕业设计人员及需要快速搭建后台管理系统的开发者参考。系统覆盖学生信息管理、成绩录入、按姓名/课程/学期查询、统计分析和数据备份恢复等功能模块,并采用 MVC 思想组织代码,便于理解分层结构与后期扩展。资源共 575 个文件,包体约 9.17MB,包含 85 个 Java 源文件、74 个 JSP 页面、170 个 class 文件、122 个 JS、56 个 CSS 以及 jar 包、SQL 脚本和配置文件,可覆盖从数据库设计、后端逻辑到前端页面的完整学习链路。已有 3118 人学习下载,适合对照项目梳理 Servlet 请求处理、JSP 渲染、Bootstrap 响应式布局与 MySQL 操作的协作方式,也可作为课程设计或入门实战参考。

1. 学生成绩管理系统迭代到 V2:Servlet + JSP + Bootstrap 组合到底解决了什么问题

学生成绩管理系统在 Java 毕业设计里几乎年年上榜,Servlet、JSP、Bootstrap、MySQL 这四个词一拼,基本就暴露了这是经典业务型项目。标题末尾带-2,我按这个系统的第二次迭代来理解:第一版能加学生、录成绩,跑是跑得起来,但乱码靠运气、页面靠原生表格、改个查询条件要动三四个 JSP 文件。V2 要解决的,就是把这套技术栈重新拆成能交付的样子——Servlet 只做路由和控制,JSP 只做渲染,Bootstrap 负责把界面撑起来,MySQL 这边把表设计和连接参数理顺。这篇笔记按我落地这类项目的习惯顺序来写:先定数据库,再搭工程骨架,然后写核心 CRUD,最后集中讲那几个最隐蔽的坑。适合正在做课设、准备 Java 岗位面试,或者要在一堆老 Servlet 项目上做二次开发的读者。

2. 数据库与连接层设计:先把学生、课程、成绩三张表的关系理顺

很多同学写管理系统习惯“先写代码后建表”,做到一半发现成绩表和课程表对不上,再去改表结构,牵一发动全身。V2 从一开始就按下述三张表设计,后面所有 Servlet 和 JSP 都是围绕它们转。

2.1 三张基础表:学生表、课程表、成绩表的字段边界

学生信息和成绩信息必须拆开,不要让成绩表里塞一堆班级、学号、课程名这种冗余字段。冗余一时爽,等你要统计“某课程平均分”或者“某班总排名”的时候,冗余数据不一致就是灾难。成绩表只存student_id和course_id,其他信息通过关联去查。

成绩表要支持同一门课在不同学期、不同考试类型下重复出现,所以“学生 + 课程 + 学期 + 考试类型”四个字段必须联合唯一,防止重复录入同一份成绩。这是我第一版踩过的坑:没有唯一键,老师手抖点了两次提交,同一条成绩录了两遍,期末排名怎么算都多出来一个人。

表名核心字段说明
studentstudent_no、student_name、class_name学号做唯一键,班级单独存
coursecourse_name、credit课程名唯一,学分保留一位小数
scorestudent_id、course_id、semester、exam_type、score联合唯一键防重复,外键关联前两张表

2.2 初始化 SQL:utf8mb4 字符集、联合唯一键、外键级联

建库脚本是项目的地基,我的习惯是直接写成init.sql,每次重新部署都能一键还原。

CREATE DATABASE IF NOT EXISTS student_scores DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE student_scores; CREATE TABLE student ( student_id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL COMMENT '学号', student_name VARCHAR(50) NOT NULL COMMENT '姓名', class_name VARCHAR(50) DEFAULT '未分班' COMMENT '班级名称', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_no (student_no) ) ENGINE=InnoDB COMMENT='学生表'; CREATE TABLE course ( course_id INT AUTO_INCREMENT PRIMARY KEY, course_name VARCHAR(50) NOT NULL COMMENT '课程名称', credit DECIMAL(3,1) DEFAULT 0.0 COMMENT '学分', UNIQUE KEY uk_course_name (course_name) ) ENGINE=InnoDB COMMENT='课程表'; CREATE TABLE score ( score_id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, semester VARCHAR(20) NOT NULL COMMENT '学期,如 2024-2025-1', exam_type ENUM('midterm', 'final') DEFAULT 'final' COMMENT '考试类型', score DECIMAL(5,2) NOT NULL COMMENT '成绩,保留两位小数', exam_time DATE DEFAULT NULL, UNIQUE KEY uk_student_course_semester (student_id, course_id, semester, exam_type), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE RESTRICT, INDEX idx_semester (semester) ) ENGINE=InnoDB COMMENT='成绩表';

这段 SQL 有几个参数值得展开说。utf8mb4而不是utf8,是因为 MySQL 的utf8最多存三个字节,一些特殊符号存不进去,会直接报错或者存成乱码;utf8mb4_unicode_ci是通用排序规则,中文排序基本符合直觉。score字段用DECIMAL(5,2),范围是 0.00 到 999.99,百分制成绩完全够用,如果只是给学生存数据库,可以让ENUM('midterm', 'final')无脑选择。

外键的ON DELETE CASCADE和ON DELETE RESTRICT是两种相反的删法:删除学生时,成绩跟着删,符合业务直觉;删除课程时,如果成绩表里已经引用了这门课,直接拒绝删除,避免历史成绩变成孤儿数据。如果以后要保留“误删”这种后悔药,可以把 CASCADE 改成 RESTRICT,在业务层做逻辑删除。

2.3 JDBC 连接参数:MySQL 8 的驱动和各种连接参数

不管用不用框架,JDBC 连接是绕不开的一层。V2 里没有引 HikariCP 之类的连接池,核心原因是课设和内部小项目用DriverManager足够,少一个依赖就少一个踩坑点。参数写在db.properties里,方便换环境改配置。

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/student_scores?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=your_password_here

对应 Java 侧的工具类:

public class DbUtil { private static final Properties props = new Properties(); static { try (InputStream in = DbUtil.class.getClassLoader() .getResourceAsStream("db.properties")) { props.load(in); Class.forName(props.getProperty("jdbc.driver")); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection( props.getProperty("jdbc.url"), props.getProperty("jdbc.username"), props.getProperty("jdbc.password") ); } }

characterEncoding=utf8必须显式写上,不写的话,即使数据库和页面的字符集都对,连接层也有可能在传输过程中把中文搞成问号。serverTimezone=Asia/Shanghai解决的是新版驱动读取数据库时间时的时区报错。allowPublicKeyRetrieval=true是 MySQL 8 的caching_sha2_password认证插件相关,主要解决启动时报“Public Key Retrieval is not allowed”。useSSL=false则是因为本机开发环境没有证书,不开 SSL 握手会更快。

3. 搭建 Servlet + JSP 骨架:Maven 依赖、包结构、web.xml 配置

数据库就绪之后,下一步是工程骨架。我最怕看到一上来就建 JSP、把大量 Java 代码堆在页面里。V2 的工程骨架分三层:controller放 Servlet,dao放 JDBC 数据访问,model放实体类,JSP 统一放到WEB-INF/jsp下面。

3.1 Maven 的 pom.xml:Servlet、JSTL、MySQL 驱动三个依赖够了

Maven 项目的好处是依赖不用手动复制 jar 包。这里只加三个运行期需要的依赖,其他都交给 Maven 传递。

<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency> </dependencies>

javax.servlet-api的 scope 只能是provided,编译时需要,运行时由 Tomcat 提供,不加这个 scope 会导致版本冲突。jstl 1.2是 Tomcat 9 及之前版本配套的命名空间,后面避坑章会专门讲 Tomcat 10 的兼容问题。mysql-connector-java 8.0.33对应 MySQL 8.x,如果数据库是 5.7,可以降到 5.1.49,但新版驱动基本兼容旧版 MySQL,问题不大。

3.2 用 Maven 直接管理 Tomcat 插件,省去手动部署

手动把 war 包复制到 Tomcat 的 webapps 目录不算难,但每次改代码都要重启 Tomcat,很影响调试节奏。V2 的工程里加了tomcat7-maven-plugin,名字虽然带 tomcat7,但通过调整 Tomcat 版本可以实现对应的容器支持。

<build> <finalName>student-score-system</finalName> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/student-score-system</path> <uriEncoding>UTF-8</uriEncoding> </configuration> </plugin> </plugins> </build>

port是启动端口,path是访问路径,等于部署后的上下文路径,比如访问首页就是http://localhost:8080/student-score-system/。uriEncoding=UTF-8必须配,至少 JSP 中文参数的中文乱码要靠它处理。执行mvn tomcat7:run就能在本地跑起来,改 Java 代码重新编译后重启,改 JSP 直接刷新页面。

3.3 实体类与 DAO 的包边界:model 层别写业务逻辑

实体类是最容易写飞的一层,有人把校验、格式化和拼接 SQL 都往里塞。实际上 model 层就是数据搬运工,字段和表字段一一对应即可。

public class Student { private Integer studentId; private String studentNo; private String studentName; private String className; public Student() { } public Student(Integer studentId, String studentNo, String studentName, String className) { this.studentId = studentId; this.studentNo = studentNo; this.studentName = studentName; this.className = className; } // getter/setter 省略,实际代码里必须补全 }

这里有两个约定俗成的点:所有包装类字段用Integer而不是int,因为数据库INT NULL在 Java 里映射成null时,int会直接 NPE。所有字段名必须和表字段的驼峰映射一致,比如student_no在 Java 里就叫studentNo,这样后面从结果集填值的时候不容易手滑。

我一般还会在 DAO 上方定义一个findAll、findById、insert、update、deleteById的接口,这样 Servlet 调用起来一目了然。这就是最简单实用的 DAO 模式,别把它上升到设计模式去理解。

4. 写核心代码:Servlet 收请求、DAO 查 MySQL、JSTL 渲染、Bootstrap 撑门面

骨架搭完,到了全篇最重的部分。我会按一个“查看学生列表 -> 添加成绩 ”的完整链路来讲。最需要记住的分层原则:Servlet 里不要写 JDBC,JSP 里不要写 Java 代码,所有页面取数据的活交给 DAO。

4.1 Servlet 路由:用 op 参数控制增删改查的入口

V2 采用了统一入口的写法,一个StudentServlet负责学生的增删改查,通过op参数区分动作。这样 URL 不必每个动作都建一个 Servlet,维护起来轻松一些。

@WebServlet("/student") public class StudentServlet extends HttpServlet { private StudentDao studentDao = new StudentDao(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String op = req.getParameter("op"); if (op == null) { op = "list"; } switch (op) { case "add": showAddPage(req, resp); break; case "edit": showEditPage(req, resp); break; case "delete": doDelete(req, resp); break; default: listPage(req, resp); break; } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String op = req.getParameter("op"); if ("create".equals(op)) { doCreate(req, resp); } else { resp.sendRedirect(req.getContextPath() + "/student"); } } private void listPage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<Student> students = studentDao.findAll(); req.setAttribute("studentList", students); req.getRequestDispatcher("/WEB-INF/jsp/student_list.jsp") .forward(req, resp); } }

这里需要注意的是 doGet 和 doPost 的分工。浏览器地址栏访问只发 GET,所以查询和删除放在 doGet;表单提交一般是 POST,所以新增和修改放在 doPost。req.setCharacterEncoding("UTF-8")放在最前面,它确保 POST 请求体里的中文在你读取参数之前就被正确解码,漏掉这一行,中文姓名大概率乱码。转发到WEB-INF/jsp下的 JSP,浏览器无法直接访问,这是安全上的一个习惯。

4.2 DAO 层:PreparedStatement 是底线,杜绝字符串拼接 SQL

DAO 是访问数据库的最后一层,所有 SQL 必须用PreparedStatement,原因很简单:预编译能处理特殊字符,还能防止基本的 SQL 注入。

public class StudentDao { public List<Student> findAll() { String sql = "SELECT student_id, student_no, student_name, class_name " + "FROM student ORDER BY student_no"; List<Student> list = new ArrayList<>(); try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Student s = new Student(); s.setStudentId(rs.getInt("student_id")); s.setStudentNo(rs.getString("student_no")); s.setStudentName(rs.getString("student_name")); s.setClassName(rs.getString("class_name")); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; } public int insert(Student s) { String sql = "INSERT INTO student (student_no, student_name, class_name)" + " VALUES (?, ?, ?)"; try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, s.getStudentNo()); ps.setString(2, s.getStudentName()); ps.setString(3, s.getClassName()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }

占位符?的赋值顺序从上到下从 1 开始数,这是新手比较容易翻车的地方。setString的三个参数和 SQL 里的三个问号一一对应,如果写反了,记录就跑到错误字段去了。try-with-resources会自动关掉Connection、PreparedStatement、ResultSet,这是避免连接泄漏最有效的写法,比自己finally里手动关闭省心得多。

4.3 把成绩写进数据库:事务的核心价值

成绩录入不能只往score表里插一行就完事,如果以后要顺便更新学生平均分、课程平均分这类统计字段,就必须保证两步操作同时成功或同时失败。用 MySQL 事务来做,就是这种场景的标准解。

public boolean saveScore(Score score) { String insertScore = "INSERT INTO score " + "(student_id, course_id, semester, exam_type, score) " + "VALUES (?, ?, ?, ?, ?)"; String updateStudentStat = "UPDATE student SET total_score = total_score + ? " + "WHERE student_id = ?"; Connection conn = null; try { conn = DbUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps1 = conn.prepareStatement(insertScore)) { ps1.setInt(1, score.getStudentId()); ps1.setInt(2, score.getCourseId()); ps1.setString(3, score.getSemester()); ps1.setString(4, score.getExamType()); ps1.setBigDecimal(5, score.getScore()); ps1.executeUpdate(); } try (PreparedStatement ps2 = conn.prepareStatement(updateStudentStat)) { ps2.setBigDecimal(1, score.getScore()); ps2.setInt(2, score.getStudentId()); ps2.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } finally { if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

setAutoCommit(false)关闭自动提交后,事务就开始了。两条 SQL 在同一个Connection上顺序执行,都成功才commit,任何一条抛异常都会进入rollback,把已经执行的插回撤。这就像是“后悔药”,避免数据库里出现“成绩加上了但总分没更新”这种半截数据。

4.4 JSP 用 JSTL 和 EL 渲染列表,页面里不要再写 Java 代码

JSP 是视图层,V2 的核心理念是不允许<% %>出现。用 EL 表达式\${}取值,用 JSTL 的<c:forEach>做循环,页面清爽很多,多人接手也容易看懂。

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>学生列表</title> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/bootstrap/css/bootstrap.min.css"> </head> <body> <div class="container mt-4"> <h3>学生列表</h3> <table class="table table-bordered table-hover"> <thead> <tr> <th>学号</th> <th>姓名</th> <th>班级</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach var="item" items="${studentList}"> <tr> <td>${item.studentNo}</td> <td>${item.studentName}</td> <td>${item.className}</td> <td> <a href="${pageContext.request.contextPath}/student?op=edit&id=${item.studentId}" class="btn btn-sm btn-outline-primary">编辑</a> <a href="${pageContext.request.contextPath}/student?op=delete&id=${item.studentId}" class="btn btn-sm btn-outline-danger" onclick="return confirm('确认删除该学生吗?');">删除</a> </td> </tr> </c:forEach> </tbody> </table> </div> </body> </html>

items="${studentList}"是 Servlet 里setAttribute("studentList", students)对应的名字,必须完全一致。item.studentNo调用的就是实体类的getStudentNo()方法。每个链接都拼了${pageContext.request.contextPath},这个前缀保证了不管项目部署在哪个上下文路径下,资源路径都不会 404。删除按钮上的onclick是浏览器内置的二次确认,虽然它不能防住恶意请求,但能防住手滑。

4.5 Bootstrap 让页面真正能看:栅格和表格样式

后端写得再顺,页面如果还是白底黑字表格,演示效果会大打折扣。Bootstrap 的引入建议用本地静态资源,而不是 CDN 网页链接,课设答辩现场没网也不会掉链子。

src/main/webapp/static/bootstrap/ ├── css/bootstrap.min.css └── js/bootstrap.bundle.min.js

页面里用 Bootstrap 5 的栅格做表单布局,表单一列放不下的时候,可以把它改成两列:

<div class="row"> <div class="col-md-6 mb-3"> <label class="form-label">学号</label> <input type="text" name="studentNo" class="form-control" required> </div> <div class="col-md-6 mb-3"> <label class="form-label">姓名</label> <input type="text" name="studentName" class="form-control" required> </div> </div>

col-md-6就是栅格参数,在中等宽度以上的屏幕上每个输入框占一半宽度;mb-3控制上下间距。Bootstrap 的按钮、表格、导航栏细节比较多,想要什么组件,直接翻它的官方文档复制基础类名,改成自己的参数就行,没有必要一个个记。

5. 排查与避坑:从“能跑”到“不怕拷问”的五个高频翻车点

这一章是踩坑记录,每条都是真实项目里反复出现的问题。如果你照着上面的代码写,遇到任何一个现象,直接对照原因改就行。

5.1 现象:MySQL 8 启动报错 “Public Key Retrieval is not allowed”

原因:MySQL 8 默认认证插件是caching_sha2_password,JDBC 连接时如果useSSL=false,驱动需要先向服务器获取公钥来加密密码传输,但连接串里没有显式允许,于是直接拒绝。 解决:在jdbc.url末尾追加allowPublicKeyRetrieval=true,最简单的写法是:

jdbc.url=jdbc:mysql://localhost:3306/student_scores?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true

也可以把 MySQL 用户改回mysql_native_password插件,但改动用户会影响所有连接,不推荐。

5.2 现象:页面中文全部变成“?”或者“杩涘害”之类的乱码

原因:字符集在四个环节里至少有一处没对齐。最常见的是 JSP 页面本身是 UTF-8,但 POST 参数解析时用了默认 ISO-8859-1;也可能是数据库表用了utf8,而连接参数没有characterEncoding=utf8。 解决:按链路统一口径。页面头部加上contentType="text/html;charset=UTF-8";Servlet 的doPost第一行写req.setCharacterEncoding("UTF-8");连接串里带上characterEncoding=utf8;建表语句里用utf8mb4。这四个地方都对齐,基本不会乱码。

5.3 现象:Bootstrap 样式加载不出来,浏览器控制台报 404

原因:JSP 里直接写了href="/static/bootstrap/css/bootstrap.min.css"。如果项目部署后上下文路径是/student-score-system,浏览器就会去http://localhost:8080/static/...找资源,根路径不对自然 404。 解决:所有静态资源路径都加上上下文前缀:

<link rel="stylesheet" href="${pageContext.request.contextPath}/static/bootstrap/css/bootstrap.min.css">

后端的转发路径不受影响,但重定向sendRedirect也必须写成resp.sendRedirect(req.getContextPath() + "/student"),否则会跳出当前上下文。

5.4 现象:JSP 列表能打开,但没有数据;直接在 MySQL 客户端执行 SQL 却有结果

原因:DAO 里打开连接后没有走到executeQuery,最典型的是 SQL 查询条件接收的参数是null,比如班级筛选框留空时直接拼接了LIKE '%%'之外的错误条件;另一种情况是结果集映射错了列名,rs.getString("student_no")手误写成驼峰studentNo,MySQL 区分大小写时直接报列不存在。 解决:第一步把 SQL 打印出来贴到客户端执行,确认语句正确;第二步检查rs取列名是否用的是 SQL 里的别名而非 Java 字段名。更好的做法是永远给查询列起别名,让取数的名字跟实体字段完全一致:

SELECT student_no AS studentNo, student_name AS studentName FROM student

5.5 现象:Tomcat 启动报NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config

原因:项目的 JSTL 依赖缺失,或者你装的是 Tomcat 10+。Tomcat 10 把javax.*换成了jakarta.*,而 JSP 页面里声明的 taglib 前缀是http://java.sun.com/jsp/jstl/core,包名不匹配就会报类找不到。 解决:如果坚持用当前代码,就用 Tomcat 9 及以下版本,并把 pom 里 JSTL 依赖加上;如果想升级到 Tomcat 10,JSP 里的 taglib 前缀和依赖包要全部换成jakarta.servlet.jsp.jstl.*系列。V2 这个项目的定位是老技术栈,所以我一般直接锁 Tomcat 9。

6. 让系统更好拿出手:成绩排名 SQL 和最后一遍验收流程

核心功能完成后,真正拉开差距的是收尾技巧。成绩排名是老师最容易问到的“这功能你做了吗”的项目点,也是学生成绩系统里最能体现 SQL 能力的地方。

MySQL 8 及以上可以直接用窗口函数:

SELECT s.student_no, s.student_name, SUM(sc.score) AS total_score, RANK() OVER (ORDER BY SUM(sc.score) DESC) AS rank_no FROM score sc JOIN student s ON s.student_id = sc.student_id GROUP BY s.student_id, s.student_no, s.student_name ORDER BY rank_no;

RANK()在总分相同时会产生并列名次,比如两个人都是 280 分,就会同时是第 1 名,而下一个是第 3 名。如果希望并列之后接着第 2 名,就改用DENSE_RANK()。这个细节答出来,面试官就知道你是真的在业务场景里调过排名逻辑,而不只是背了窗口函数的参数。

最后把我自己的验收习惯分享出来。动手部署前,先把数据库重新初始化一遍,跑init.sql,确认从零能起来;然后把页面里每一个按钮都点过去,重点测试“删除有成绩的学生”这个级联操作,确认不会把数据库干崩;再打开浏览器控制台,看有没有静态资源 404;最后把 war 包放到另一个干净 Tomcat 里启动一次,排除本机 IDE 环境问题。这套流程走完,系统才算真正“能交付”。这几年带我踩得最快的一个教训就是:自己觉得“应该没问题”的地方,往往就是问题所在。希望这些细节能在你做这个方向时候帮你少走几步弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 23:58:26

装配车间MES落地指南:SimpleMES工单流转、BOM与齐套检查实战

简介&#xff1a;一套基于.NET 4.0的SimpleMES加工装配模拟系统&#xff0c;面向MES系统学习者、课程设计或毕业设计人员&#xff0c;以及需要快速搭建制造执行原型的开发者。服务端与客户端分工明确&#xff1a;服务端包含基础档案、加工与装配计划管理、实时看板和数据初始化…

作者头像 李华
网站建设 2026/10/6 23:33:55

戴尔5577拆机清灰换硅脂全攻略:从工具准备到温度实测一次讲透

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 23:33:19

Codex实战:从代码生成到软件工程智能体的演进与落地

前两年大家聊AI写代码&#xff0c;基本还停留在“自动补全”和“单文件生成”的阶段。但从2025年开始&#xff0c;有一个词被大家反复提起&#xff0c;而且含金量被严重低估了——软件工程智能体。把这条路真正走通、并且让“代码生成大模型”这个概念显得过时的典型代表&#…

作者头像 李华
网站建设 2026/10/6 23:28:48

给 Claude 接入实时搜索:基于 MCP 协议的 Serp MCP Server 实践指南

1. 为什么我要给 Claude 接上实时搜索 Claude 本身的知识是有截止日期的&#xff0c;这一点用过的人都清楚。你问它今天有什么新闻、某个库最新版本号是多少、某个 API 最近有没有改签名&#xff0c;它要么直接说不知道&#xff0c;要么更危险——一本正经地编一个看起来很像真…

作者头像 李华
网站建设 2026/10/6 23:28:47

工业循环冷却水水质规范怎么落地?从指标限值到加药排污全解析

简介&#xff1a;《工业循环冷却水水质规范》&#xff08;GB50050-2007&#xff09;是一份针对工业循环冷却水系统的强制性国家标准文档&#xff0c;适合工业企业水处理岗位、设备运维人员、工程设计及环保管理人员查阅使用。资源包共1个PDF文件&#xff0c;大小仅28KB&#xf…

作者头像 李华
网站建设 2026/10/6 23:24:06

MOS管衬底接法全解析:从原理图到版图的避坑指南

1. 衬底到底该接哪儿&#xff1a;一个被很多人忽略的基础问题做模拟电路或者版图的朋友&#xff0c;尤其是刚入行的&#xff0c;大概率都遇到过这个场景&#xff1a;画原理图的时候&#xff0c;NMOS的衬底引脚随手就接到了地上&#xff0c;PMOS的衬底引脚随手就接到了电源上&am…

作者头像 李华