原生JSP+Servlet学生管理系统:可部署的Java Web实战项目
简介本资源是一套基于Java Web技术栈开发的学生信息管理系统完整实现面向Java初学者与Web开发入门者聚焦JSP动态页面、Servlet业务逻辑处理及MVC分层实践帮助学习者系统掌握从请求响应、数据库交互JDBCMySQL、会话管理到前后端协同的全流程开发能力。压缩包共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 MVC2.1 目录结构即运行逻辑WEB-INF是安全边界lib决定兼容性整个项目采用标准Java EE Web应用结构核心目录如下所有路径均以项目根目录为基准student-system/ ├── src/ # Java源码Servlet类、工具类 │ ├── servlet/ # 所有Servlet实现类LoginServlet.java, StudentListServlet.java等 │ └── util/ # 工具类DBUtil.java封装JDBCValidator.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.jarJDBC驱动、jstl-1.2.jarJSTL标签库 ├── 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中提供核心结构如下表名字段类型说明usersidINT PK AI用户ID管理员usernameVARCHAR(50) NOT NULL UNIQUE登录账号如adminpasswordVARCHAR(100) NOT NULLBCrypt加密后的密码非明文studentsidINT PK AI学生主键student_idVARCHAR(20) NOT NULL UNIQUE学号业务主键如2023001nameVARCHAR(50) NOT NULL姓名genderTINYINT(1) DEFAULT 0性别0男/1女class_nameVARCHAR(50)班级名称scoreDECIMAL(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?errornot_login); return; } chain.doFilter(request, response); // 放行已登录用户 }关键参数说明req.getSession(false)false表示不创建新Session仅获取已有Session。若用户未登录此调用返回null避免无意义Session生成。req.getContextPath()动态获取应用上下文路径如部署为/student则返回/student确保重定向URL正确硬编码/login.jsp会导致404。?errornot_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 ?, ?; ListStudent 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/1010.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.10.20.30000000000000004这类浮点误差。所有校验失败均return中断流程绝不继续执行插入逻辑。4. 部署与运行从本地IDE到Tomcat服务器的完整路径4.1 开发环境配置JDK、Tomcat、MySQL三件套版本锁死项目要求严格匹配以下版本组合任何一项不一致都将导致启动失败组件推荐版本验证方式不匹配后果JDKJDK 11.0.20LTSjava -version输出11.0.20JDK 17Unsupported major.minor version 61.0字节码版本不兼容TomcatApache Tomcat 9.0.83CATALINA_HOME/bin/version.batWindows或./version.shLinuxTomcat 10javax.servlet包不存在全项目报红MySQLMySQL Community Server 8.0.33mysql --versionMySQL 5.7驱动握手失败Communications link failure血泪经验某开发者在Mac上用Homebrew安装mysql5.7却下载了mysql-connector-java-8.0.33.jar反复出现The server time zone value UTC is unrecognized错误。解决方案在JDBC URL末尾添加serverTimezoneGMT%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 IDEAFile→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.bat浏览器访问http://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未声明isELIgnoredfalse导致EL表达式${}被忽略重定向失效。解决在index.jsp第一行添加指令% page isELIgnoredfalse % % 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非provideddependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version scopecompile/scope /dependency5.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?page2但页面仍显示第1页原因student_list.jsp中分页链接写死为.jsp后缀未通过Servlet路由导致请求未经过StudentListServletpage参数被忽略。解决将分页链接改为Servlet路径c:forEach begin1 end${totalPages} vari a href${pageContext.request.contextPath}/StudentListServlet?page${i} ${i} /a /c:forEach并在web.xml中配置Servlet映射servlet-mapping servlet-nameStudentListServlet/servlet-name url-pattern/StudentListServlet/url-pattern /servlet-mapping5.5 现象Tomcat启动后localhost:8080/student/login.jsp返回404但localhost:8080/显示Tomcat首页原因项目未正确部署到Tomcatwebapps目录下缺少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 version1.0 encodingUTF-8? Configuration statusWARN Appenders Console nameConsole targetSYSTEM_OUT PatternLayout pattern%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /Console File nameFile fileNamelogs/student-system.log PatternLayout pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/ /File /Appenders Loggers Logger namecom.example.servlet leveldebug additivityfalse AppenderRef refConsole/ AppenderRef refFile/ /Logger Root levelinfo AppenderRef refConsole/ /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(pagepageParam)在debug级别关闭时仍执行字符串拼接的性能损耗duration记录SQL查询JSP渲染总耗时若持续500ms说明数据库索引缺失或网络延迟。6.3 生产环境日志归档用RollingFile自动切分防磁盘爆满将FileAppender替换为RollingFile添加按天归档和最大保留30天RollingFile nameRollingFile fileNamelogs/student-system.log filePatternlogs/$${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 size10 MB/ /Policies DefaultRolloverStrategy max30/ /RollingFile关键配置filePatternlogs/$${date:yyyy-MM}/student-system-%d{MM-dd-yyyy}-%i.log.gz按月建目录每日生成gzip压缩包SizeBasedTriggeringPolicy size10 MB单个日志文件超10MB立即切分max30最多保留30个归档文件超限自动删除最旧的。从那以后我每次部署新版本都会先检查logs/目录下是否有student-system.log生成并用tail -f logs/student-system.log盯着登录流程的日志输出——只要看到LoginServlet received username: admin和紧接着的LoginServlet redirect to /student_list.jsp我就知道认证链路通了。这种确定性比任何文档都管用。希望帮到你。本文还有配套的精品资源点击获取