news 2026/10/9 3:14:47

Java Web学生信息管理系统源码实战:Servlet+JSP+MySQL完整解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Web学生信息管理系统源码实战:Servlet+JSP+MySQL完整解析

简介:这是一份基于Java Web的学生信息管理系统完整源码包,面向正在学习Java Servlet、JSP与JDBC的开发者,也适合用作课程设计或毕业设计的参考项目。系统围绕用户、学生、课程与成绩四大模块展开,实现登录验证、学生信息增删改查、成绩与课程数据管理等功能,可帮助读者理解从前端JSP到后端Servlet再到数据库的完整交互链路。资源共35个文件,其中23个Java文件涵盖了实体类、数据库操作工具及Servlet控制层,5个JSP页面用于登录、主页、成绩、更新等界面,另有JAR依赖包、SQL初始化脚本、XML配置及说明文档,整体压缩包约2.52MB,目录结构清晰,可直接导入IDE运行。目前已有107人学习下载。通过阅读源码、运行项目并修改SQL脚本,读者能够掌握JDBC连接、MVC分层思想、SQL查询更新以及Java Web项目部署等关键技能,获得一套可复用、可扩展的完整工程模板。

1. 学生信息管理系统:Java Web 源码到底值不值得下

很多同学从网上下载的 Java Web 学生信息管理系统源码,十有八九第一次启动会翻车——不是代码烂,而是环境、驱动、字符集这些不起眼的环节先出了问题。这套系统说白了就是一个典型的 CRUD 项目:学生信息的增删改查、登录会话、分页列表,再加一层简单的权限判断。把它跑通、读懂、改成自己的课设,比直接啃 mybatis 源码、springframework 源码更接近真实开发的节奏。适合正在做 Java 课程设计、准备实习面试、想搞懂请求从浏览器走到数据库全过程的人。这篇我就按自己复现这类源码的经验,把技术栈选型、核心代码拆解和踩坑记录一次讲透。

2. 先看技术栈再动手:JSP + Servlet 老架构为什么还值得读

2.1 选型理由:课设源码比 Spring Boot 更适合入门

现在市面上能下载到的学生信息管理系统源码,绝大多数还是 JSP + Servlet + JavaBean + MySQL 的老组合,少部分改用 Spring Boot + MyBatis。老架构看起来过时,但对新手反而友好:请求进来先被 web.xml 里的 Servlet 接住,你在 doGet/doPost 里一行一行加断点就能看到参数怎么被解析、业务怎么被调用、结果怎么被转发回页面,整个请求生命周期是透明的。换成 Spring Boot 之后,DispatcherServlet 和一堆注解把流程包成了黑匣子,新手遇到问题反而不知道去哪看日志。

我一般建议按这个顺序选择下载哪一套源码:如果目标是交课程设计,优先选 JSP + Servlet 版本,因为它和教材里的知识点对得上;如果是要做毕业设计或者写进简历,再考虑 Spring Boot + MyBatis 的改写版本。前者负责让你看懂,后者负责让你显得专业。两套源码的数据库表结构几乎一样,先跑通老架构再迁移到新框架,成本很低。

这套源码还承担一个「源码剖析与架构实战」的入门任务:你应该从中学会的不是某个高深技巧,而是「一个请求的完整生命周期」。后面我会把登录和分页两条主线单独拆出来讲,那两段代码就是整个系统的骨架,读懂它们,别的功能都是套模板。

2.2 环境与工具清单:JDK、Tomcat、MySQL 版本怎么配

复现这套源码之前,先把工具版本对齐。最常见的失败原因不是代码问题,而是 JDK 17 配 Tomcat 9、MySQL 8 配了 5.x 驱动这类版本错位。下面是我实际复现时常用的组合:

组件推荐版本说明
JDK1.8(或 8u202)老源码几乎都是 JDK 8 编译,用新 JDK 会遇到 IllegalAccessError
Tomcat8.5 或 9.0Servlet 3.1/4.0,兼容 JSP 2.3
MySQL5.7 或 8.08.0 需要改时区参数,见第 4 章
MySQL 驱动5.1.49(配 5.7)/ 8.0.x(配 8.0)驱动类名不同,注意区分
IDEEclipse 或 IntelliJ IDEA导入时选「Existing Project / Web」

注意一点:如果你的电脑只装了 JDK 17,不要急着卸载。可以在 IDEA 里给这个项目单独指定一个 JDK 1.8 的 SDK,Tomcat 运行时也指向它。这样既不影响你写新代码,也能让老源码正常跑。Tomcat 我推荐用 8.5,因为它对老 web.xml 的兼容性更宽松,部署时不会因为 schema 版本报错。

MySQL 选型的坑在驱动。5.x 驱动类的类名是 com.mysql.jdbc.Driver,8.x 改成了 com.mysql.cj.jdbc.Driver。下载的源码里如果写死了第一个类名,而你机器上用的是 MySQL 8.0 和配套驱动,启动时会直接报 ClassNotFoundException。遇到这种情况先别改代码,把驱动 jar 换成对应版本,通常就解决了。后面第 4 章我会再展开。

2.3 项目导入 IDE 后,目录结构应该长什么样

下载到的 zip 解压后,正常是一个 Web 工程目录。我见过太多人把整个 src 文件夹拖进 Eclipse 就算导入了,结果 Tomcat 根本找不到类。标准的 Java Web 工程长这样:

StudentManager/ ├── src/ # Java 源码 │ └── com/student/ │ ├── bean/ # Student.java、User.java 等实体 │ ├── dao/ # StudentDao.java,JDBC 操作 │ ├── servlet/ # LoginServlet、StudentServlet │ └── util/ # DBUtil.java,数据库连接工具 ├── WebContent/ # Web 根目录(IDEA 里叫 web/) │ ├── index.jsp # 登录页 │ ├── list.jsp # 学生列表页 │ ├── WEB-INF/ │ │ ├── web.xml # 路由与 Servlet 注册 │ │ └── lib/ # mysql-connector-java.jar 必须放这里 │ └── css/ js/ └── build/ # 编译输出,IDE 自动生成

提示:带 WEB-INF 的目录才是 Web 根目录。Eclipse 用 File → Import → Existing Projects into Workspace;IDEA 用 File → Open 选择解压目录,然后右键根目录 Add Framework Support 勾选 Web。

web.xml 决定所有 URL 和 Servlet 的映射关系,如果你打开源码发现里面只有一个空壳或者干脆没有 web.xml,说明这份源码被压缩时丢了文件,属于不完整版本,建议换一份。数据库连接信息通常写在 src 下的 db.properties 里,或者直接硬编码在 DBUtil.java 的 static 块中。硬编码的版本要小心:里面写的密码、端口、库名不一定是你的环境,先改成自己的,再启动。这一步不做好,后面全是「数据库连接失败」的红字。

3. 核心代码拆解:登录、分页、增删改查的复现路径

3.1 先把数据库连接封装好:db.properties + DBUtil

一套学生管理系统里最容易被抄错的就是数据库连接。很多人喜欢在每一个 DAO 方法里写一遍 DriverManager.getConnection,最后程序能跑,但代码闻起来不对。良好的结构是把连接收进一个工具类,所有 DAO 复用同一个入口。先看 db.properties 的写法:

# 数据库连接参数,按你自己的 MySQL 环境修改 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/studentdb?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

注意 url 里的两个参数:useUnicode=true 和 characterEncoding=utf8 缺一不可,否则中文写入数据库后全变成问号。如果你用 MySQL 8.0,驱动类改成 com.mysql.cj.jdbc.Driver,并把时区参数追加到 url 后面,比如 serverTimezone=Asia/Shanghai。这段配置是所有中文乱码问题的第一道防线,我后面第 4 章还会专门说。

再看 DBUtil.java:

package com.student.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ResourceBundle; public class DBUtil { private static String driver; private static String url; private static String username; private static String password; // 静态块:类加载时读取一次配置文件 static { ResourceBundle rb = ResourceBundle.getBundle("db"); driver = rb.getString("jdbc.driver"); url = rb.getString("jdbc.url"); username = rb.getString("jdbc.username"); password = rb.getString("jdbc.password"); try { Class.forName(driver); } catch (ClassNotFoundException 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) { 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) {} } }

这段代码有三个参数值得注意。第一个是 ResourceBundle.getBundle("db"),它默认从 classpath 根目录找 db.properties,所以 properties 文件要放到 src 根目录,否则 getBundle 报 MissingResourceException。第二个是 Class.forName(driver),它负责在旧版 JDBC 中手动注册驱动,MySQL 5.7 场景必须保留;如果你用 JDBC 4 之后的版本,这段可以省略,但保留无副作用。第三个是 close 方法的三段式判空关闭,调用顺序必须是 ResultSet → Statement → Connection,反了会先关连接再关语句,部分驱动会报错。

在实际的源码里,DBUtil 通常还会提供增删改查的通用方法,比如 executeUpdate(String sql, Object... params) 和 query(String sql, RowMapper mapper)。这个设计思路就是后来 MyBatis 的雏形:把 SQL 变化的部分和固定流程分离。如果你手里这份源码只有 getConnection,没有通用方法,那 DAO 层会写很多重复代码,建议自己加一个,后面扩展功能会省力很多。

3.2 登录与会话:一个 Servlet 如何完成身份校验

登录是整个系统的入口。它的链路是:index.jsp 表单提交 → LoginServlet 的 doPost 接收参数 → 调用 UserDao 查库 → 查到则把用户名放进 Session → 重定向到 list.jsp;查不到则请求转发回 index.jsp 并带出错误提示。下面是精简过但结构完整的 LoginServlet:

package com.student.servlet; import com.student.bean.User; import com.student.dao.UserDao; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.IOException; 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) { // 登录成功:把用户对象放进 Session,供后续页面判断权限 HttpSession session = request.getSession(); session.setAttribute("loginUser", user); response.sendRedirect(request.getContextPath() + "/student/list"); } else { // 登录失败:回登录页,用请求属性带出提示 request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("/index.jsp").forward(request, response); } } }

这段代码有三处新手容易忽略。第一处是 request.setCharacterEncoding("UTF-8"),它必须放在读取任何 getParameter 之前才生效,否则 POST 提交过来的中文用户名会乱码。第二处是 sendRedirect 与 forward 的选择:登录成功后用重定向,可以避免刷新页面时重复提交表单;失败后用 forward,保留 request 属性里的错误提示在 JSP 里回显。第三处是 HttpSession 默认超时时间是 30 分钟,在 web.xml 里可以调短,但不要写成永久,会话不失效是安全隐患。

对应的 web.xml 注册片段长这样:

<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.student.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>

注意这里映射的是/login,不是/LoginServlet,所以表单的 action 应该写成${pageContext.request.contextPath}/login。很多源码里表单 action 直接写/login,少了 contextPath,部署后点击登录会 404。如果下载的源码有这个毛病,要么在 JSP 里补上 ctxPath 前缀,要么把 url-pattern 改成绝对路径,二选一,别两个同时改。

3.3 分页查询:PageBean 与 SQL 的 LIMIT 参数

学生列表页是系统的门面。数据量小的时候一次性查出来没问题,但课设答辩时老师往往会顺手加 50 条测试数据,页面一次渲染上百行表格既慢又难看。分页是源码里最容易看出水平的模块。常规实现是 PageBean + DAO 两个类配合完成,先看 PageBean:

package com.student.bean; import java.util.List; public class PageBean<T> { private int pageNum; // 当前页码 private int pageSize; // 每页条数 private long total; // 总记录数 private int totalPages; // 总页数 private List<T> list; // 当前页数据 public PageBean(int pageNum, int pageSize) { this.pageNum = pageNum; this.pageSize = pageSize; } public void setTotal(long total) { this.total = total; this.totalPages = (int) Math.ceil((double) total / pageSize); } // getter/setter 省略 }

注意 totalPages 的计算用Math.ceil((double) total / pageSize),99 条数据、每页 10 条时结果是 10 页,不会把第 10 页吞掉。如果你在下载的源码里看到total % pageSize == 0 ? total/pageSize : total/pageSize+1这种写法,逻辑也对,只是可读性差一些。

StudentDao 里的分页查询核心方法:

public List<Student> findPage(int pageNum, int pageSize) throws SQLException { String sql = "SELECT id, name, gender, age, major FROM student ORDER BY id ASC LIMIT ?, ?"; List<Student> list = new ArrayList<>(); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); ps = conn.prepareStatement(sql); // MySQL 的 LIMIT 从 0 开始,第一页的 offset 是 0 ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); rs = ps.executeQuery(); while (rs.next()) { Student stu = new Student(); stu.setId(rs.getInt("id")); stu.setName(rs.getString("name")); // 其余字段赋值省略 list.add(stu); } } finally { DBUtil.close(rs, ps, conn); } return list; }

LIMIT 的两个参数一个是 offset,一个是 rowcount。第一次写分页的人最容易把 offset 直接传 pageNum,导致第二页跳到第 11 条而不是第 10 条之后。公式是(pageNum - 1) * pageSize,pageNum 从 1 开始,这套源码后半段的排序和条件查询都基于这个公式展开。查询条件变化时,SQL 里的 WHERE 部分要拼接,但 LIMIT 两个占位符的位置和顺序不要动,动了就出事。

注意:如果源码里加了搜索框,SQL 会拼接成 WHERE name LIKE ? LIMIT ?, ?,like 的参数别直接拼字符串,PreparedStatement 里传%+ keyword +%即可,能挡住一部分 SQL 注入。

3.4 增删改查:用一个 StudentServlet 集中分发四种操作

学生管理系统的 CRUD 通常通过 URL 上的 action 参数区分,这样一个 Servlet 就能承载新增、编辑、删除、列表四种操作。下面给一个常见写法:

package com.student.servlet; import com.student.bean.Student; import com.student.dao.StudentDao; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.IOException; public class StudentServlet extends HttpServlet { private StudentDao studentDao = new StudentDao(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); if ("delete".equals(action)) { int id = Integer.parseInt(request.getParameter("id")); studentDao.deleteById(id); response.sendRedirect(request.getContextPath() + "/student/list"); } else if ("edit".equals(action)) { int id = Integer.parseInt(request.getParameter("id")); Student stu = studentDao.findById(id); request.setAttribute("student", stu); request.getRequestDispatcher("/edit.jsp").forward(request, response); } else { // 默认走分页列表 int pageNum = request.getParameter("page") == null ? 1 : Integer.parseInt(request.getParameter("page")); PageBean<Student> pageBean = studentDao.findPageBean(pageNum, 10); request.setAttribute("pageBean", pageBean); request.getRequestDispatcher("/list.jsp").forward(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); if ("add".equals(action)) { Student stu = new Student(); stu.setName(request.getParameter("name")); stu.setGender(request.getParameter("gender")); stu.setAge(Integer.parseInt(request.getParameter("age"))); stu.setMajor(request.getParameter("major")); studentDao.insert(stu); response.sendRedirect(request.getContextPath() + "/student/list"); } // update 的操作与 add 几乎一样,多取一个 id 再调 updateById } }

这里有一个设计选择:新增和修改如果走同一个 JSP 表单,那么表单提交时多带一个隐藏域<input type="hidden" name="id" value="${student.id}">,doPost 里判断 id 是否为空来决定 insert 还是 update。这种写法课设里最好用,因为表单页面可以复用。Integer.parseInt 对空值会抛 NumberFormatException,正规点应该在 DAO 层外面做参数校验,数值型字段传空时给默认值 0,避免程序直接 500。源码里如果没做校验,你二次开发时补上这层,答辩时是一个加分点。

前面代码里出现三次response.sendRedirect(request.getContextPath() + "/student/list"),这是列表页的 URL。对应 web.xml 里 StudentServlet 映射到/student/list,而 action 是 query string 里的参数,不参与映射。这种「一个 Servlet + action 参数」的路由方式很老派,但逻辑集中,适合课设代码的阅读和调试。

4. 学生管理系统避坑指南:5 个跑不起来的问题,从乱码到连接泄漏

4.1 JSP 页面中文乱码:改了 setCharacterEncoding 还不够

现象:页面标题、表格里的学生姓名全是问号或者乱码,但数据库里的数据是正常的。

原因:乱码是三层编码不一致叠加出来的。第一层,JSP 文件的物理编码(IDE 右下角 UTF-8)和 JSP 顶部 pageEncoding 不一致;第二层,Servlet 里只设了 request.setCharacterEncoding("UTF-8"),没设 response.setContentType("text/html; charset=UTF-8"),响应输出用默认 ISO-8859-1;第三层,MySQL 表结构是 latin1 或 utf8mb4,JDBC 连接 url 里没有 characterEncoding=utf8。

解决:按顺序做三件事。一是把 JSP 文件统一另存为 UTF-8,并保证页面开头有<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>;二是在每个 Servlet 的 doGet/doPost 第一行补response.setContentType("text/html; charset=UTF-8");三是把 db.properties 的 url 加上 useUnicode=true&characterEncoding=utf8,并确认数据库表和字段的 charset 是 utf8mb4。改完这三处,重启 Tomcat,乱码基本消失。还有个别 Tomcat 版本需要检查 server.xml 里 Connector 的 URIEncoding 属性,老项目有时候会在那里指定 GBK,改成 UTF-8 即可。如果还乱,把 MySQL 的 character_set_server 参数也查一下,这一层是黑匣子,只能逐个排除。

4.2 ClassNotFoundException: com.mysql.jdbc.Driver:驱动 jar 没放进 WEB-INF/lib

现象:Tomcat 启动后访问登录页,点登录立刻报 ClassNotFoundException,类名是 com.mysql.jdbc.Driver。

原因:通常只有两种可能。一是下载的源码 zip 里没有带 lib 目录,或者 lib 里是 8.x 的 mysql-connector-java,驱动类名不匹配;二是 jar 放到了项目的 build path 里,但没有部署到 WEB-INF/lib 下。Java Web 应用的类加载器从 WEB-INF/classes 和 WEB-INF/lib 加载运行期类,只在 IDE 里配了 Library 而没打进 war,Tomcat 运行时根本看不见。

解决:把 mysql-connector-java-x.x.jar 复制到 WebContent/WEB-INF/lib(IDEA 工程就是 web/WEB-INF/lib)下,然后 Project Structure → Artifacts → 把 jar 加入 Output Layout(或右键 Put into Output Root)。Eclipse 用户直接放 lib 就会自动部署。驱动版本和 MySQL 对应上:MySQL 5.7 配驱动 5.1.49,MySQL 8.0 配驱动 8.0.x,然后检查 DBUtil 里 Class.forName 的类名是否对应,8.x 驱动对应 com.mysql.cj.jdbc.Driver。改完 Clean 一下工程再重启,这种问题立刻消失。

4.3 Tomcat 8080 端口被占用:项目没有报错但页面一直打不开

现象:Tomcat 启动日志里出现 Exception in thread "main" java.net.BindException: Address already in use: JVM_Bind :8080,或者页面刷新一直转圈。

原因:本机已经有其他进程占了 8080 端口,常见的还有另一个 Tomcat 实例、Nginx、或者某个开发工具的调试端口。

解决:在命令行执行netstat -ano | findstr :8080,记下最后一列的 PID,再用tasklist | findstr <PID>确认是什么进程。如果是你自己起的旧 Tomcat,用taskkill /F /PID <PID>结束。如果端口被别的服务占用,最省事的做法是改当前项目的端口:在 Server 配置或 IDEA 的 Run/Debug Configurations → HTTP port 里把 8080 改成 8081,记得访问 URL 同步改。另外顺手检查环境变量 CATALINA_HOME 是不是指向了一个旧 Tomcat,系统里装了多个 Tomcat 时很容易加载错安装目录,这个坑和端口占用往往一起出现。

4.4 数据库连接失败:MySQL 8 的时区与驱动不兼容

现象:Tomcat 能启动,登录页也能打开,但点登录后报 Communications link failure 或者 SQLNonTransientConnectionException: Public Key Retrieval is not allowed。

原因:MySQL 8.0 默认要求 JDBC 连接串里显式声明时区(serverTimezone),而老源码的 url 只有jdbc:mysql://localhost:3306/studentdb。Public Key Retrieval is not allowed 是 8.0 驱动默认不开 allowPublicKeyRetrieval 导致的,一般和 caching_sha2_password 认证插件同时出现。

解决:先改连接 url,在数据库名后面追加参数:

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

然后驱动替换成 mysql-connector-java-8.0.x,DBUtil 里的驱动类改为 com.mysql.cj.jdbc.Driver。useSSL=false 只建议本地开发用,生产环境必须开启 SSL。如果你的 MySQL 是用 Docker 起的,记得端口不是 3306 而是你映射出的宿主机端口,这一条常被忽略,白折腾半天。

4.5 Connection 不关闭:系统越跑越慢最后卡死

现象:程序能登录、能查列表,但连续操作十几分钟后,日志开始报 Cannot create PoolableConnectionFactory,或者 MySQL 连接数耗尽,页面一直转圈。

原因:源码里的一处经典坏味道——某些 DAO 方法里 getConnection 之后忘了在 finally 里 close。连接是宝贵的资源,每次不关都会占用 MySQL 的一个线程连接,MySQL 常见默认配置下满 151 个连接就拒绝新请求。这种情况日志不会直接告诉你「你没关连接」,只会表现成系统逐渐变慢,属于典型的黑匣子问题。

解决:给所有 DAO 方法做一次「连接三件套」检查。凡是打开了 Connection、PreparedStatement、ResultSet 的方法,finally 里必须依次关闭,参考上面 DBUtil.close 的写法。检查时重点找那些把 Connection 作为方法参数传进传出的写法,这种最容易漏。另一个前置手段是在 DBUtil 里加一个静态计数器,每次 getConnection 时递增、close 时递减,如果长期不为 0,说明有泄漏点:

private static AtomicInteger activeConnections = new AtomicInteger(0); public static Connection getConnection() throws SQLException { activeConnections.incrementAndGet(); return DriverManager.getConnection(url, username, password); } public static void close(ResultSet rs, Statement stmt, Connection conn) { // ...原有关闭逻辑 if (conn != null) { activeConnections.decrementAndGet(); } }

跑一遍增删改查后打印 activeConnections 的数量,如果稳定回到 0,说明没有新泄漏。这个小技巧平时看不出价值,等系统慢到答辩现场翻车时,它就是你的后悔药。要是你下载的源码用了连接池(比如 C3P0 或 Druid),查泄漏就更容易,Druid 自带的监控页就能看到活跃连接数,但老源码里多数没有,需要自己加。

5. 从能跑到能交付:源码验证与二次开发的落地技巧

先别急着给老师演示,按三层做一遍验证:第一层是功能验证,把「登录、增删改查、分页、退出」四条链路各走一遍,每条链路记下输入、输出和中间跳转,遇到 404 先看 Context Path 是不是带上了项目名;第二层是数据验证,删掉一条学生记录后去 MySQL 里确认物理删除真的执行了,再往库里手工插入 30 条数据,观察分页页码是否正确;第三层是异常验证,故意输错密码、传一个不存在的 id、清空某个必填项,确认页面友好报错而不是白屏或 500。这三层跑完,代码才算可交付。

二次开发时我建议优先做两件事,性价比最高。第一件是加日志:在 Servlet 入口打印请求参数,在 DAO 层打印 SQL 和耗时,以后排查问题不用再靠猜:

long start = System.currentTimeMillis(); // ...执行查询 long cost = System.currentTimeMillis() - start; System.out.println("[DAO] findPage cost=" + cost + "ms, pageNum=" + pageNum);

第二件是给这个老工程补上 Maven 结构,把 lib 里的 jar 换成 pom 依赖,web.xml 里的 Servlet 注册改成 @WebServlet 注解,这样项目从「课设 demo」变成「看起来像规范工程」的形态,面试聊起来也有的说。迁移时注意src/main/java、src/main/webapp两个目录的对应关系,别把 web.xml 放错位置。

我自己当年做课程设计时吃过一次亏:跑通之后急着交,没验证「删除学生后再分页」这个组合场景,结果最后一条被删后当前页是空的,页面直接显示出错。后来我在分页 Servlet 里加了一行「如果当前页没有数据且页码大于 1,自动回退到上一页」,这种边界判断平时没人提,但答辩现场特别容易暴露。建议你也顺着「空状态」「边界值」「重复提交」这三个方向把代码过一遍,比多写一个功能更有用。希望帮到你。

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

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

两数之和深度解析:哈希表解法、易错点与题型扩展

如果要在刷题界选一道“最熟悉的陌生人”&#xff0c;我大概率会投给两数之和。它是LeetCode的第1题&#xff0c;也是无数人打开编辑器写下的第一道算法题。但有意思的是&#xff0c;我面试过不少候选人&#xff0c;十个里有八个能秒答“用哈希表”&#xff0c;可一旦追问“为什…

作者头像 李华
网站建设 2026/10/9 3:13:03

VMware摄像头打不开?从USB直通到权限配置的完整排查指南

先说个结论&#xff1a;VMware里摄像头打不开&#xff0c;绝大多数时候不是摄像头坏了&#xff0c;也不是VMware本身残了&#xff0c;而是设备根本没被“接”进虚拟机&#xff0c;或者虚拟机这边的USB控制器、权限、驱动根本没准备好。这个问题我前前后后帮人排过不下几十次&am…

作者头像 李华
网站建设 2026/10/9 3:13:02

Linux文件IO与标准IO底层机制及性能实测对比

最近又把系统编程的笔记翻出来整理&#xff0c;看到文件IO和标准IO这一章&#xff0c;发现很多老问题依然值得重新聊一遍。学Linux编程绕不开文件IO和标准IO&#xff0c;面试题里也总爱问"read/write和fread/fwrite有什么区别"&#xff0c;但真正在工程里用顺手的人并…

作者头像 李华
网站建设 2026/10/9 3:12:47

手机端抖音无水印视频图片下载工具:从解析原理到实操指南

1. 为什么我会做一个手机端专用的无水印下载工具1.1 自带保存功能的两个痛点&#xff0c;也是这个工具存在的理由刷抖音的时候&#xff0c;看到一段特别喜欢的视频&#xff0c;想存到相册里&#xff0c;点一下保存&#xff0c;下载下来的却满屏都是发布者的抖音号水印。这个问题…

作者头像 李华
网站建设 2026/10/9 3:10:45

链表操作核心:移除元素与反转链表,掌握指针与虚拟头节点

我先把结论放在前面&#xff1a;链表这类题&#xff0c;在LeetCode上属于典型的“看着简单、一写就错”。数组问题写错了多半是边界管得不好&#xff0c;链表问题写错了几乎都是因为对指针变化时机的理解不到位。而“移除链表元素”和“反转链表”这两道题&#xff0c;恰好把链…

作者头像 李华
网站建设 2026/10/9 3:09:43

C#仓库条码管理系统源码解析:从WinForms到扫码入库的落地实践

简介&#xff1a;面向毕业设计场景的C#仓库条码管理系统源码&#xff0c;围绕入库、出库、库存查询和条码扫描等核心业务展开&#xff0c;提供一套可直接运行的Windows窗体应用方案&#xff0c;适合需要完成课设或毕设的C#学习者。压缩包共132个文件&#xff0c;以47个cs源码、…

作者头像 李华