简介:本资源是一套基于Java Web技术栈开发的学生信息管理系统完整实现,面向Java初学者与Web开发入门者,聚焦JSP动态页面、Servlet业务逻辑处理及MVC分层实践,帮助学习者系统掌握从请求响应、数据库交互(JDBC+MySQL)、会话管理到前后端协同的全流程开发能力。压缩包共576个文件,涵盖156个HTML页面、132个CSS样式表、79个JavaScript脚本、20个Java源码与对应class字节码、4个JSP核心视图页,以及db_studentinfo.sql数据库脚本等关键组件,整体2.75MB,结构清晰、即导即用。已有997人学习下载,资源包含LoginServlet、StudentListServlet、GradeSaveServlet等典型控制器类及DAO数据访问层实现,辅以完整前端交互与样式支持,可直接部署于Tomcat容器运行,是理解Java Web基础架构与工程化落地的优质教学案例。
1. JSP + Servlet 学生信息管理系统:不是教学Demo,是能跑通增删改查、带登录校验、可部署到Tomcat的真实Java Web最小可行系统
你可能在教材里见过“学生管理系统”四个字——但那往往是只贴了5行JSP代码、Servlet里写死一个ArrayList、连数据库连接都用Class.forName()硬编码的演示片段。这次不一样:这是一个从零开始、完整覆盖用户登录、学生列表分页展示、按学号精确查询、批量删除、表单验证+后端拦截、以及Tomcat 9+环境下实测可部署的Java Web项目。它不依赖Spring Boot自动配置,不包装成jar包,而是用最原始的web.xml声明式配置、HttpServletRequest原生对象处理请求、HttpServletResponse写响应,让你看清HTTP请求从浏览器发出到页面渲染的每一层流转。适合刚学完Servlet生命周期、想摆脱IDEA自动生成模板、真正理解doGet/doPost区别、并准备把第一个Web项目扔进公司测试环境的Java初学者。它不是玩具,是能当作业交、能当面试作品、能改两行就变成课程设计的生产级起点。
2. 项目结构与技术选型:为什么坚持用原生Servlet而非Spring MVC?
2.1 目录结构即运行逻辑:WEB-INF是安全边界,lib决定兼容性
整个项目采用标准Java EE Web应用结构,核心目录如下(所有路径均以项目根目录为基准):
student-system/ ├── src/ # Java源码(Servlet类、工具类) │ ├── servlet/ # 所有Servlet实现类(LoginServlet.java, StudentListServlet.java等) │ └── util/ # 工具类(DBUtil.java封装JDBC,Validator.java做表单校验) ├── WebContent/ # Web资源根目录(Eclipse时代遗留名,实际等同于现代IDE的webapp) │ ├── index.jsp # 首页(重定向到登录页) │ ├── login.jsp # 登录表单(含用户名/密码输入、错误提示区) │ ├── student_list.jsp # 学生列表页(含分页控件、操作按钮) │ ├── student_add.jsp # 添加表单(含学号/姓名/性别/班级/成绩字段) │ └── WEB-INF/ │ ├── web.xml # 核心配置:Servlet映射、欢迎文件、过滤器声明 │ └── lib/ # 必须包含:mysql-connector-java-8.0.33.jar(JDBC驱动)、jstl-1.2.jar(JSTL标签库) ├── build.xml # Ant构建脚本(可选,用于自动化编译+打包) └── README.md # 部署说明(含Tomcat版本要求、数据库建表SQL)提示:
WEB-INF目录下的内容无法被浏览器直接访问,这是Servlet规范强制的安全机制。web.xml必须放在此处,否则Tomcat启动时会报No web.xml present错误;lib中的jar包若缺失,常见现象是ClassNotFoundException: com.mysql.cj.jdbc.Driver或JSTL标签无法解析。
2.2 技术栈选择依据:拒绝过度封装,直击HTTP本质
- Servlet版本:明确使用
javax.servlet.http.HttpServlet(非jakarta.servlet.http.HttpServlet),对应Servlet 4.0规范,兼容Tomcat 9.0.x(最低要求)。若强行升级到Tomcat 10+,需将所有javax.*包名替换为jakarta.*,否则ClassNotFoundException必现。 - JSP引擎:依赖Tomcat内置的Jasper编译器,不额外引入JSP容器。JSP中仅使用
<%= %>表达式和<c:forEach>等基础JSTL标签,禁用Scriptlet(<% %>),确保逻辑与视图分离。 - 数据库驱动:选用
mysql-connector-java-8.0.33.jar而非5.x版本,因MySQL 8.0+默认启用caching_sha2_password认证插件,旧驱动无法握手。若用MySQL 5.7,请降级驱动至5.1.49。 - JSTL版本:固定
jstl-1.2.jar(非1.2.5或2.x),因其与Servlet 4.0完全兼容;高版本JSTL 2.x需配合Jakarta EE 9+,此处会引发NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config。
2.3 数据库设计:三张表支撑完整业务流
系统依赖MySQL数据库,建表语句在README.md中提供,核心结构如下:
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
users | id | INT PK AI | 用户ID(管理员) |
username | VARCHAR(50) NOT NULL UNIQUE | 登录账号(如admin) | |
password | VARCHAR(100) NOT NULL | BCrypt加密后的密码(非明文!) | |
students | id | INT PK AI | 学生主键 |
student_id | VARCHAR(20) NOT NULL UNIQUE | 学号(业务主键,如2023001) | |
name | VARCHAR(50) NOT NULL | 姓名 | |
gender | TINYINT(1) DEFAULT 0 | 性别(0男/1女) | |
class_name | VARCHAR(50) | 班级名称 | |
score | DECIMAL(5,2) DEFAULT 0.00 | 成绩(支持小数) |
注意:
users表密码必须使用BCrypt加密存储,login.jsp提交的明文密码需经BCrypt.hashpw()比对。若直接存明文,LoginServlet中if (inputPass.equals(dbPass))看似能过,但上线即被扫库——这是新手最常翻车的玄学点。
3. 核心功能实现:从登录拦截到分页查询的完整链路
3.1 登录认证:Filter拦截未授权请求,Session维持用户状态
系统通过LoginFilter实现全局权限控制,其doFilter()方法逻辑如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; // 放行登录页、登录处理、静态资源 String uri = req.getRequestURI(); if (uri.endsWith("/login.jsp") || uri.endsWith("/LoginServlet") || uri.contains("/static/") || uri.endsWith(".css") || uri.endsWith(".js")) { chain.doFilter(request, response); return; } // 检查Session中是否存在user属性 HttpSession session = req.getSession(false); if (session == null || session.getAttribute("user") == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp?error=not_login"); return; } chain.doFilter(request, response); // 放行已登录用户 }关键参数说明:
req.getSession(false):false表示不创建新Session,仅获取已有Session。若用户未登录,此调用返回null,避免无意义Session生成。req.getContextPath():动态获取应用上下文路径(如部署为/student则返回/student),确保重定向URL正确,硬编码/login.jsp会导致404。?error=not_login:携带错误参数,login.jsp通过request.getParameter("error")判断并显示提示:“请先登录”。
3.2 学生列表分页:手写SQL LIMIT子句,避免内存溢出
StudentListServlet不加载全表数据到内存,而是通过LIMIT offset, size实现物理分页:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int currentPage = Integer.parseInt(request.getParameter("page") != null ? request.getParameter("page") : "1"); int pageSize = 10; // 每页10条 int offset = (currentPage - 1) * pageSize; String sql = "SELECT * FROM students ORDER BY id DESC LIMIT ?, ?"; List<Student> students = DBUtil.executeQuery(sql, offset, pageSize); // 查询总记录数用于计算总页数 String countSql = "SELECT COUNT(*) FROM students"; int totalRecords = DBUtil.querySingleInt(countSql); int totalPages = (int) Math.ceil((double) totalRecords / pageSize); request.setAttribute("students", students); request.setAttribute("currentPage", currentPage); request.setAttribute("totalPages", totalPages); request.setAttribute("totalRecords", totalRecords); request.getRequestDispatcher("/student_list.jsp").forward(request, response); }逻辑说明:
offset计算公式(currentPage - 1) * pageSize是分页铁律,错写成currentPage * pageSize会导致第1页跳过首条数据。Math.ceil()确保总页数向上取整(如103条数据,103/10=10.3,向上取整为11页)。DBUtil.executeQuery()是封装好的JDBC查询方法,内部使用PreparedStatement防止SQL注入,绝不用字符串拼接SQL。
3.3 表单验证:前后端双校验,防君子也防小人
student_add.jsp中前端用HTML5属性做基础约束,后端AddStudentServlet进行严格校验:
// 后端校验逻辑(AddStudentServlet doPost方法内) String studentId = request.getParameter("student_id").trim(); String name = request.getParameter("name").trim(); String genderStr = request.getParameter("gender"); String className = request.getParameter("class_name"); String scoreStr = request.getParameter("score"); // 1. 学号非空且长度≤20 if (studentId.isEmpty() || studentId.length() > 20) { request.setAttribute("error", "学号不能为空且长度不能超过20位"); request.getRequestDispatcher("/student_add.jsp").forward(request, response); return; } // 2. 姓名非空 if (name.isEmpty()) { request.setAttribute("error", "姓名不能为空"); request.getRequestDispatcher("/student_add.jsp").forward(request, response); return; } // 3. 性别必须为0或1 int gender = 0; try { gender = Integer.parseInt(genderStr); if (gender != 0 && gender != 1) throw new NumberFormatException(); } catch (NumberFormatException e) { request.setAttribute("error", "性别值非法"); request.getRequestDispatcher("/student_add.jsp").forward(request, response); return; } // 4. 成绩转为BigDecimal,支持小数 BigDecimal score = BigDecimal.ZERO; try { score = new BigDecimal(scoreStr); if (score.compareTo(BigDecimal.ZERO) < 0 || score.compareTo(new BigDecimal("100.00")) > 0) { throw new IllegalArgumentException("成绩必须在0~100之间"); } } catch (Exception e) { request.setAttribute("error", "成绩格式错误或超出范围"); request.getRequestDispatcher("/student_add.jsp").forward(request, response); return; }参数说明:
trim()清除用户输入首尾空格,避免" 2023001 "被当作合法学号。BigDecimal替代Double处理成绩,防止0.1+0.2=0.30000000000000004这类浮点误差。- 所有校验失败均
return中断流程,绝不继续执行插入逻辑。
4. 部署与运行:从本地IDE到Tomcat服务器的完整路径
4.1 开发环境配置:JDK、Tomcat、MySQL三件套版本锁死
项目要求严格匹配以下版本组合,任何一项不一致都将导致启动失败:
| 组件 | 推荐版本 | 验证方式 | 不匹配后果 |
|---|---|---|---|
| JDK | JDK 11.0.20(LTS) | java -version输出11.0.20 | JDK 17+:Unsupported major.minor version 61.0(字节码版本不兼容) |
| Tomcat | Apache Tomcat 9.0.83 | CATALINA_HOME/bin/version.bat(Windows)或./version.sh(Linux) | Tomcat 10+:javax.servlet包不存在,全项目报红 |
| MySQL | MySQL Community Server 8.0.33 | mysql --version | MySQL 5.7:驱动握手失败,Communications link failure |
血泪经验:某开发者在Mac上用Homebrew安装
mysql@5.7,却下载了mysql-connector-java-8.0.33.jar,反复出现The server time zone value 'UTC' is unrecognized错误。解决方案:在JDBC URL末尾添加&serverTimezone=GMT%2B8,或降级驱动至5.1.49。
4.2 Eclipse/IDEA项目导入:Web Deployment Assembly是关键
在IDE中导入项目后,必须手动配置Deployment Assembly,否则发布到Tomcat时WEB-INF/lib为空:
- Eclipse:右键项目 →
Properties→Deployment Assembly→ 点击Add...→Java Build Path Entries→ 选择Maven Dependencies(若用Maven)或Web App Libraries(若手动管理lib)→Finish。 - IntelliJ IDEA:
File→Project Structure→Artifacts→ 选中student-system:war exploded→ 在右侧Output Layout中,右键WEB-INF/lib→From Modules...→ 勾选所有jar包 →OK。
提示:若跳过此步,Tomcat日志中会出现
java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config,表面是JSTL问题,实则是jar未打包进war。
4.3 Tomcat部署三步法:解压、配库、启服务
- 解压项目为war包:在
WebContent目录下执行jar -cvf ../student.war .(Linux/Mac)或使用WinRAR右键“添加到压缩文件”并命名为student.war。 - 复制驱动jar到Tomcat:将
mysql-connector-java-8.0.33.jar复制到$CATALINA_HOME/lib/目录(非WEB-INF/lib),确保Tomcat启动时能加载JDBC驱动。 - 启动并验证:
浏览器访问# Linux/Mac $CATALINA_HOME/bin/startup.sh # Windows %CATALINA_HOME%\bin\startup.bathttp://localhost:8080/student/login.jsp,若看到登录页即成功。首次访问若卡住,检查Tomcat日志logs/catalina.out中是否有SEVERE: Error listenerStart,大概率是web.xml语法错误。
5. 避坑指南:五个真实踩过的坑,每一条都附带复现步骤与修复命令
5.1 现象:登录成功后跳转到空白页,地址栏显示http://localhost:8080/student/
原因:web.xml中<welcome-file-list>配置了index.jsp,而index.jsp内容为<% response.sendRedirect("login.jsp"); %>,但该JSP未声明isELIgnored="false",导致EL表达式${}被忽略,重定向失效。
解决:在index.jsp第一行添加指令:
<%@ page isELIgnored="false" %> <% response.sendRedirect(request.getContextPath() + "/login.jsp"); %>5.2 现象:学生列表页显示javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config
原因:jstl-1.2.jar未放入WEB-INF/lib,或放入了但IDE未将其加入Deployment Assembly。
解决:
- 确认
WebContent/WEB-INF/lib/jstl-1.2.jar存在; - Eclipse中检查
Deployment Assembly是否包含该jar; - 若用Maven,确保
pom.xml中scope为compile(非provided):
<dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>compile</scope> </dependency>5.3 现象:添加学生时报错Data truncation: Data too long for column 'student_id' at row 1
原因:MySQL中students.student_id字段定义为VARCHAR(10),但用户输入了15位学号。
解决:登录MySQL执行:
ALTER TABLE students MODIFY COLUMN student_id VARCHAR(20);注意:修改前备份数据,
MODIFY COLUMN会重建表,大数据量时耗时较长。
5.4 现象:分页链接点击后,URL变成http://localhost:8080/student/student_list.jsp?page=2,但页面仍显示第1页
原因:student_list.jsp中分页链接写死为.jsp后缀,未通过Servlet路由,导致请求未经过StudentListServlet,page参数被忽略。
解决:将分页链接改为Servlet路径:
<c:forEach begin="1" end="${totalPages}" var="i"> <a href="${pageContext.request.contextPath}/StudentListServlet?page=${i}"> ${i} </a> </c:forEach>并在web.xml中配置Servlet映射:
<servlet-mapping> <servlet-name>StudentListServlet</servlet-name> <url-pattern>/StudentListServlet</url-pattern> </servlet-mapping>5.5 现象:Tomcat启动后,localhost:8080/student/login.jsp返回404,但localhost:8080/显示Tomcat首页
原因:项目未正确部署到Tomcat,webapps目录下缺少student文件夹或student.war。
解决:
- 检查
$CATALINA_HOME/webapps/目录,确认存在student/文件夹(解压后)或student.war(未解压); - 若存在
student.war,删除student/文件夹,重启Tomcat触发自动解压; - 若
webapps下无任何student相关文件,说明IDE未发布成功,重新执行Run on Server或手动拷贝。
6. 进阶技巧:用Log4j2统一日志,让排查问题像读小说一样清晰
6.1 日志分级与输出位置:INFO以上写文件,DEBUG只在控制台
系统默认只打印System.out.println(),但线上环境需要结构化日志。引入log4j2.xml配置(放在src/main/resources/):
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> <File name="File" fileName="logs/student-system.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </File> </Appenders> <Loggers> <Logger name="com.example.servlet" level="debug" additivity="false"> <AppenderRef ref="Console"/> <AppenderRef ref="File"/> </Logger> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>效果:
com.example.servlet包下所有类(如LoginServlet)的debug()日志同时输出到控制台和logs/student-system.log文件;- 其他包(如
util.DBUtil)的debug()日志仅输出到控制台,info()及以上才写入文件; - 日志文件自动按日期滚动(需添加
RollingFileAppender,此处简化)。
6.2 在Servlet中埋点:记录关键参数与耗时
以StudentListServlet为例,在doGet()开头和结尾添加日志:
private static final Logger logger = LogManager.getLogger(StudentListServlet.class); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); String pageParam = request.getParameter("page"); logger.debug("StudentListServlet received page param: {}", pageParam); // ...原有业务逻辑... long duration = System.currentTimeMillis() - startTime; logger.info("StudentListServlet processed {} records in {} ms", students.size(), duration); }参数说明:
{}是Log4j2占位符,比字符串拼接高效,且避免logger.debug("page="+pageParam)在debug级别关闭时仍执行字符串拼接的性能损耗;duration记录SQL查询+JSP渲染总耗时,若持续>500ms,说明数据库索引缺失或网络延迟。
6.3 生产环境日志归档:用RollingFile自动切分,防磁盘爆满
将FileAppender替换为RollingFile,添加按天归档和最大保留30天:
<RollingFile name="RollingFile" fileName="logs/student-system.log" filePattern="logs/$${date:yyyy-MM}/student-system-%d{MM-dd-yyyy}-%i.log.gz"> <PatternLayout> <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="10 MB"/> </Policies> <DefaultRolloverStrategy max="30"/> </RollingFile>关键配置:
filePattern="logs/$${date:yyyy-MM}/student-system-%d{MM-dd-yyyy}-%i.log.gz":按月建目录,每日生成gzip压缩包;SizeBasedTriggeringPolicy size="10 MB":单个日志文件超10MB立即切分;max="30":最多保留30个归档文件,超限自动删除最旧的。
从那以后我每次部署新版本,都会先检查logs/目录下是否有student-system.log生成,并用tail -f logs/student-system.log盯着登录流程的日志输出——只要看到LoginServlet received username: admin和紧接着的LoginServlet redirect to /student_list.jsp,我就知道认证链路通了。这种确定性,比任何文档都管用。希望帮到你。
本文还有配套的精品资源,点击获取