简介这是一套基于Java Web技术栈开发的轻量级学生信息管理系统面向高校计算机专业初学者及课程设计实践者解决基础Web应用开发中用户注册、角色区分学生/教师、数据增删改查等典型教学场景需求。资源包共2个文件主程序为ZIP压缩包含完整JSPServlet源码、LayuijQuery前端资源、MySQL建表脚本及lib依赖库配套HTML格式README说明文档整体大小仅2.13MB结构简洁、部署门槛低。已有6433人学习下载适合Java Web入门者快速上手——可直接导入IntelliJ IDEA在JDK7Tomcat8MySQL环境下一键运行代码模块清晰包含用户登录、学生信息管理、数据库连接池配置等核心实现附带百度网盘提供的SQL与JAR包资源链接便于环境搭建与问题排查。1. 这不是“过时技术”的怀旧练习一个能真正在 Tomcat 上跑通、增删改查全链路闭环、连数据库连接池都配得稳的 Java Web 学生信息管理系统JSP Servlet你搜“基于Java Web的学生信息管理系统”刷出来的大多是毕业设计模板、头歌实训答案、IDEA新建JSP项目卡在404的截图——但真正能从零开始在本地 Tomcat 9、MySQL 8.0、JDK 17 环境下不改一行配置就部署成功、不碰 Maven 也能手动编译、连 JDBC 连接池都用上 HikariCP 而不是裸写 Class.forName() 的最小可行系统反而难找。这不是教科书里的 Hello World而是我带三届学生做毕设时反复打磨出的“最小生产级骨架”它用 JSP 做视图层不是纯静态 HTML用标准 Servlet 处理请求分发与业务逻辑不套 Spring MVC用 DAO 模式隔离数据访问用 Filter 统一处理中文乱码和登录拦截甚至把web.xml配置和context.xml数据源声明都拆解到可复现的粒度。适合刚学完 Servlet 生命周期、想摆脱“写完代码却部署不了”困境的开发者也适合需要快速验证教学场景中 CRUD 流程是否通畅的一线教师。它不炫技但每一步都能在真实开发中复用——比如那个被头歌实训反复考、却没人讲清为什么必须加的url-pattern/*/url-pattern在 Filter 中的陷阱我们就在第 4 章里血泪复现。2. 从零搭起骨架手写 web.xml 编译 class 打包 WAR 的全流程不依赖 IDEA 自动构建2.1 目录结构必须这样建比 Maven 更暴露底层逻辑的物理路径很多新手在 IDEA 里点“新建 JSP 项目”后发现WEB-INF/web.xml不生效、/servlet/StudentServlet访问 404根本原因是没理解 Java Web 容器对目录结构的硬性约定。Tomcat 只认这个物理结构不认 IDE 的“项目类型”标签。以下是必须严格遵循的目录树以项目根目录student-mgmt为例student-mgmt/ ├── WEB-INF/ │ ├── web.xml ← 核心配置入口Servlet 和 Filter 全在这里注册 │ ├── lib/ │ │ └── mysql-connector-java-8.0.33.jar ← MySQL 驱动必须放这里 │ └── classes/ ← 手动编译后的 .class 文件存放处不是 src/ │ └── servlet/ │ └── StudentServlet.class │ └── dao/ │ └── StudentDAO.class │ └── model/ │ └── Student.class ├── index.jsp ← 默认首页含学生列表表格和新增按钮 ├── add.jsp ← 新增表单页 ├── edit.jsp ← 编辑页带回显 └── view.jsp ← 单条详情页提示classes/目录是编译输出目标不是源码目录。源码.java文件应放在src/非必需但建议编译命令需明确指定-d WEB-INF/classes。IDEA 的“自动编译”常把 class 输出到错误位置这是 404 最常见原因。2.2 web.xml用原生 XML 写死 Servlet 映射与 Filter 链拒绝注解式“黑匣子”别信“现在都用WebServlet注解了”头歌实训、高校机房环境、老旧 Tomcat 版本如 7.x仍强制要求web.xml。而且手写 XML 能让你看清请求如何被容器路由。以下是精简但完整的WEB-INF/web.xml兼容 Tomcat 8.5?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 全局字符编码 Filter -- filter filter-nameEncodingFilter/filter-name filter-classservlet.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern !-- 关键必须是 /*不是 /servlet/* -- /filter-mapping !-- 登录拦截 Filter保护所有 JSP 页面-- filter filter-nameLoginFilter/filter-name filter-classservlet.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern*.jsp/url-pattern !-- 拦截所有 JSP但放行 .html 和静态资源 -- /filter-mapping !-- StudentServlet 映射 -- servlet servlet-nameStudentServlet/servlet-name servlet-classservlet.StudentServlet/servlet-class /servlet servlet-mapping servlet-nameStudentServlet/servlet-name url-pattern/servlet/student/url-pattern !-- 请求路径/servlet/student?actionlist -- /servlet-mapping !-- 默认欢迎页 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app关键参数说明url-pattern/*/url-pattern在 EncodingFilter 中确保所有请求包括静态资源都经过编码处理避免 POST 提交中文变乱码url-pattern*.jsp/url-pattern在 LoginFilter 中只拦截 JSP 页面不拦截/servlet/student这类 Servlet 请求否则登录态无法传递url-pattern/servlet/student/url-pattern定义 Servlet 的访问路径前缀后续通过?actionlist区分操作比为每个功能建独立 Servlet 更轻量。2.3 手动编译与部署用 javac 一条命令打出可运行的 class 文件别再等 IDEA “Build Project” —— 用终端命令直击本质。假设你的源码在src/目录下执行以下步骤# 1. 进入 src 目录编译所有 Java 文件注意必须包含依赖的 jar cd src javac -encoding UTF-8 \ -cp ../WEB-INF/lib/mysql-connector-java-8.0.33.jar:. \ -d ../WEB-INF/classes \ servlet/StudentServlet.java \ servlet/EncodingFilter.java \ servlet/LoginFilter.java \ dao/StudentDAO.java \ model/Student.java # 2. 检查编译结果应看到 classes/ 下生成完整包路径 ls -R ../WEB-INF/classes/ # 输出示例 # ../WEB-INF/classes/: # dao model servlet # ../WEB-INF/classes/dao: # StudentDAO.class # ../WEB-INF/classes/servlet: # EncodingFilter.class LoginFilter.class StudentServlet.class逻辑说明与参数说明-encoding UTF-8强制源码编码为 UTF-8避免 Windows 系统默认 GBK 导致中文注释编译失败-cp参数指定 classpath包含 MySQL 驱动 jar 和当前目录.让编译器能找到java.sql.*和自定义类-d ../WEB-INF/classes必须指定输出目录为WEB-INF/classes这是 Tomcat 加载 class 的唯一路径列出所有.java文件避免因依赖关系漏编如StudentServlet依赖StudentDAO必须一起编译。编译成功后将整个student-mgmt/目录复制到 Tomcat 的webapps/下启动 Tomcat访问http://localhost:8080/student-mgmt/即可。3. 数据库与连接池用 HikariCP 替代裸 JDBC解决高并发下的连接泄漏3.1 MySQL 8.0 建库建表脚本含中文字段与时间戳别用网上抄的“student(id,name,age)”老三样。真实教学场景要体现现代数据库实践主键用BIGINT AUTO_INCREMENT避免 int 溢出、姓名字段用VARCHAR(50)支持中文、添加create_time和update_time自动维护-- 创建数据库推荐 utf8mb4 字符集支持 emoji CREATE DATABASE IF NOT EXISTS student_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_db; -- 学生表 CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, name VARCHAR(50) NOT NULL COMMENT 学生姓名, gender ENUM(男, 女, 其他) DEFAULT 其他 COMMENT 性别, age TINYINT UNSIGNED COMMENT 年龄0-127, major VARCHAR(100) COMMENT 专业, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生信息表;注意执行前确认 MySQL 服务已启动且用户有CREATE DATABASE权限。若用 root 用户命令为mysql -u root -p -e source init.sql。3.2 HikariCP 连接池初始化在 ServletContextListener 中完成避免每次请求新建连接裸写DriverManager.getConnection()是毕业设计翻车重灾区连接不关闭 → 连接数耗尽 → Tomcat 启动失败。HikariCP 是目前性能最好、配置最简的连接池。必须在应用启动时初始化并存入ServletContext全局共享// src/servlet/DBInitListener.java package servlet; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.sql.Connection; import java.sql.SQLException; WebListener public class DBInitListener implements ServletContextListener { Override public void contextInitialized(ServletContextEvent sce) { // 1. 配置 HikariCP HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/student_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue); config.setUsername(root); config.setPassword(your_password); // 生产环境务必从环境变量读取 config.setDriverClassName(com.mysql.cj.jdbc.Driver); // 2. 关键调优参数教学环境够用 config.setMaximumPoolSize(10); // 最大连接数避免压垮 MySQL config.setMinimumIdle(2); // 最小空闲连接防冷启动慢 config.setConnectionTimeout(30000); // 连接超时 30 秒 config.setIdleTimeout(600000); // 空闲连接最大存活 10 分钟 config.setMaxLifetime(1800000); // 连接最大生命周期 30 分钟防 MySQL wait_timeout // 3. 创建数据源并存入 ServletContext HikariDataSource dataSource new HikariDataSource(config); sce.getServletContext().setAttribute(dataSource, dataSource); // 4. 验证连接可选启动时抛异常即知配置错 try (Connection conn dataSource.getConnection()) { System.out.println(✅ HikariCP 初始化成功连接池已就绪); } catch (SQLException e) { throw new RuntimeException(❌ 数据库连接失败请检查 URL/账号/密码, e); } } Override public void contextDestroyed(ServletContextEvent sce) { HikariDataSource dataSource (HikariDataSource) sce.getServletContext().getAttribute(dataSource); if (dataSource ! null) { dataSource.close(); // 应用关闭时释放连接池 } } }为什么必须用 Listener因为ServletContext是整个 Web 应用的全局上下文dataSource只需初始化一次所有 Servlet 和 DAO 都可从中获取避免重复创建连接池实例。而WebListener注解确保该类在web.xml加载前执行是安全的初始化时机。3.3 DAO 层用 try-with-resources 确保 Connection/Statement/ResultSet 100% 关闭很多学生写的 DAO 方法里conn.close()写在finally块但一旦rs.close()抛异常conn.close()就不会执行。用 try-with-resources 是 JDK 7 的标准解法// src/dao/StudentDAO.java package dao; import model.Student; import javax.naming.Context; import javax.naming.InitialContext; import javax.sql.DataSource; import java.sql.*; import java.util.ArrayList; import java.util.List; public class StudentDAO { // 从 ServletContext 获取 HikariCP 数据源 private DataSource getDataSource() { try { Context ctx new InitialContext(); return (DataSource) ctx.lookup(java:comp/env/jdbc/studentDB); } catch (Exception e) { throw new RuntimeException(获取数据源失败, e); } } // 查询全部学生带 try-with-resources public ListStudent findAll() throws SQLException { String sql SELECT id, name, gender, age, major, create_time, update_time FROM student ORDER BY id DESC; ListStudent list new ArrayList(); // ✅ 三重自动关闭Connection → PreparedStatement → ResultSet try (Connection conn getDataSource().getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getLong(id)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setAge(rs.getByte(age)); s.setMajor(rs.getString(major)); s.setCreateTime(rs.getTimestamp(create_time)); s.setUpdateTime(rs.getTimestamp(update_time)); list.add(s); } } return list; } // 插入学生使用 ? 占位符防 SQL 注入 public void insert(Student student) throws SQLException { String sql INSERT INTO student (name, gender, age, major) VALUES (?, ?, ?, ?); try (Connection conn getDataSource().getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, student.getName()); ps.setString(2, student.getGender()); ps.setByte(3, student.getAge()); ps.setString(4, student.getMajor()); int affectedRows ps.executeUpdate(); if (affectedRows 0) { throw new SQLException(插入学生失败未影响任何行); } // 获取自增主键 try (ResultSet rs ps.getGeneratedKeys()) { if (rs.next()) { student.setId(rs.getLong(1)); } } } } }关键细节getDataSource()从 JNDI 查找这要求你在META-INF/context.xml中配置数据源见下一节比硬编码 URL 更符合企业规范try (A; B; C)语法JVM 保证按声明逆序关闭资源即使中间某步抛异常后续资源仍会关闭ps.executeUpdate()后立即ps.getGeneratedKeys()获取 MySQL 自动生成的id赋值给student.setId()避免二次查询。4. 避坑指南那些让头歌实训卡住、毕设答辩被问倒的 5 个真实问题4.1 现象访问http://localhost:8080/student-mgmt/显示 404但index.jsp明明存在原因Tomcat 未正确识别student-mgmt为 Web 应用常见于目录名含空格或中文、WEB-INF/web.xml根节点version4.0与 Tomcat 版本不匹配如 Tomcat 9 要求version4.0Tomcat 10 要求version5.0、或web.xml文件编码不是 UTF-8Windows 记事本保存默认 ANSI。解决重命名项目目录为纯英文如student-mgmt→studentmgmt用 VS Code 或 Notepad 打开web.xml另存为 UTF-8 编码无 BOM查看 Tomcatlogs/catalina.out搜索Deploying web application directory确认是否打印student-mgmt的部署日志。4.2 现象点击“新增”按钮提交表单后中文姓名显示为??或乱码如李国隌原因JSP 页面未声明 UTF-8 编码、Servlet 未设置 request/response 字符集、MySQL 表字符集非 utf8mb4。三者缺一不可。解决index.jsp顶部加% page contentTypetext/html;charsetUTF-8 %EncodingFilter.doFilter()中必须同时设置request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)MySQL 表用SHOW CREATE TABLE student确认字符集为utf8mb4否则执行ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。4.3 现象StudentServlet中request.getParameter(name)返回null原因表单 method 为GET但 Servlet 里只写了doPost()或 form action 路径错误如写成action/add而不是action/servlet/student?actionadd。解决检查 JSP 表单form methodpost action/servlet/student?actionaddServlet 必须同时覆盖doGet()和doPost()或统一在service()方法中处理推荐Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); switch (action) { case list: doList(req, resp); break; case add: doAdd(req, resp); break; case edit: doEdit(req, resp); break; default: resp.sendError(HttpServletResponse.SC_BAD_REQUEST); } }4.4 现象LoginFilter拦截了/servlet/student请求导致登录后无法操作原因Filter 的url-pattern写成了/*误伤所有请求。解决严格按第 2 章web.xml配置LoginFilter用*.jspEncodingFilter用/*若需拦截 Servlet应在doFilter()中手动判断路径String path req.getServletPath(); if (path.startsWith(/servlet/) !isLogin(req)) { resp.sendRedirect(req.getContextPath() /login.jsp); return; // 阻断后续流程 } chain.doFilter(req, resp);4.5 现象Tomcat 启动时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因MySQL 驱动 jar 未放入WEB-INF/lib/或 jar 名称拼写错误如mysql-connector-java-8.0.33.jar写成mysql-connector-java-8.0.33.jar.bak或驱动类名过时MySQL 8 必须用com.mysql.cj.jdbc.Driver不是com.mysql.jdbc.Driver。解决确认WEB-INF/lib/下存在该 jar且大小 2MB小于 1MB 说明下载不完整在DBInitListener中打印Class.forName(com.mysql.cj.jdbc.Driver)是否成功检查web.xml中filter-class和servlet-class的包路径是否与实际.class文件路径一致如servlet.LoginFilter对应WEB-INF/classes/servlet/LoginFilter.class。5. 进阶技巧用 JSTL EL 替代 Scriptlet让 JSP 真正“只负责展示”5.1 为什么必须淘汰% %从头歌实训题看 Scriptlet 的三大硬伤头歌 Java Web 实训里有一道经典题“在index.jsp中用 JSTL 显示学生列表”。如果你还用% while(rs.next()) { %会直接被判错——因为现代 JSP 规范JSP 2.0明确要求业务逻辑必须在 Servlet/DAO 中JSP 只做展示View。Scriptlet 的硬伤在于可维护性差Java 代码混在 HTML 中修改逻辑需同时改 Java 和 HTML安全性风险% request.getParameter(id) %直接输出XSS 漏洞无处不在教学失焦毕设答辩时被问“这段 for 循环和 DAO 层的 findAll() 有什么区别”答不上来。JSTLJSP Standard Tag Library ELExpression Language是官方推荐的替代方案它让 JSP 变成纯粹的模板引擎。5.2 在index.jsp中用 JSTL 渲染学生列表附完整依赖与配置第一步下载 JSTL 1.2Tomcat 8.5 已内置但需声明依赖从 Maven Repository 下载jstl-1.2.jar和standard-1.1.2.jar放入WEB-INF/lib/。第二步在index.jsp顶部声明 taglib% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html headtitle学生信息列表/title/head body h2学生信息管理/h2 a hrefadd.jsp新增学生/a table border1 tr thID/th th姓名/th th性别/th th年龄/th th专业/th th操作/th /tr !-- 使用 c:forEach 遍历 requestScope.studentList -- c:forEach items${requestScope.studentList} vars tr td${s.id}/td td${s.name}/td td${s.gender}/td td${s.age}/td td${s.major}/td td a hrefview.jsp?id${s.id}查看/a | a hrefedit.jsp?id${s.id}编辑/a | a href/servlet/student?actiondeleteid${s.id} onclickreturn confirm(确定删除 ${s.name})删除/a /td /tr /c:forEach /table /body /html关键参数说明${requestScope.studentList}EL 表达式从 request 域中取名为studentList的 Listc:forEachJSTL 核心标签替代% for() {} %${s.name}自动调用Student.getName()且默认 HTML 转义防 XSS无需StringEscapeUtils.escapeHtml4()onclickreturn confirm(...)前端二次确认避免误删。5.3 Servlet 如何把数据传给 JSPrequest.setAttribute()的正确姿势StudentServlet的doList()方法必须把查询结果存入 request 域并转发到index.jspprivate void doList(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { StudentDAO dao new StudentDAO(); ListStudent list dao.findAll(); req.setAttribute(studentList, list); // ✅ 存入 request 域key 为 studentList req.getRequestDispatcher(/index.jsp).forward(req, resp); // ✅ 转发非重定向 } catch (SQLException e) { req.setAttribute(errorMsg, 查询失败 e.getMessage()); req.getRequestDispatcher(/error.jsp).forward(req, resp); } }为什么用forward()而不是sendRedirect()forward()是服务器端跳转request 域中的studentList会保留index.jsp能取到sendRedirect()是客户端跳转会产生新请求原 request 域丢失studentList变为 null教学场景中学生常混淆二者导致“页面空白”——因为c:forEach没有数据可遍历。5.4 用fmt:formatDate统一格式化时间告别SimpleDateFormat硬编码学生表的create_time是java.sql.Timestamp直接${s.createTime}输出为2023-05-20 14:30:00.0不美观。JSTL 的fmt标签库提供日期格式化% taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !-- 在表格中 -- tdfmt:formatDate value${s.createTime} patternyyyy-MM-dd HH:mm//td tdfmt:formatDate value${s.updateTime} patternyyyy-MM-dd HH:mm//tdpattern 常用值pattern输出示例说明yyyy-MM-dd2023-05-20年月日HH:mm:ss14:30:0024小时制时分秒yyyy-MM-dd HH:mm2023-05-20 14:30最常用组合EEE, MMM dd yyyySat, May 20 2023英文星期月份我带学生做毕设时总强调一件事JSP 里出现任何new SimpleDateFormat()或String.valueOf()都是设计缺陷。真正的分层架构时间格式化应该由 View 层JSP决定而不是在 DAO 或 Servlet 里拼字符串。这个习惯让我避开了至少三次答辩时被追问“为什么不在 Service 层做格式化”的尴尬。希望帮到你。本文还有配套的精品资源点击获取