首页 / 资讯中心 / 文章详情

Java Web基础实战:Servlet+JSP+MySQL学生成绩管理系统

Java Web基础实战:Servlet+JSP+MySQL学生成绩管理系统 ★ FEATURED ARTICLE
简介这是一套面向Java Web初学者与高校课程设计学生的学生成绩管理系统完整实现基于ServletJSPMySQL三层架构开发覆盖用户登录、学生信息管理、成绩录入与查询等核心业务适用于期末大作业、JavaWeb课程设计及基础项目实训。资源包共188个文件包含42个JSP页面负责前端交互与展示、24个Java类含GradeDao、StudentDao、LoginServlet等关键业务与控制层代码、72个编译后Class文件、6个SQL脚本含数据库建表与初始化数据、以及CSS/JS/HTML等配套资源整体压缩包仅11.29MB轻量易部署。已有223人学习下载所有源码均经本地实测可运行评审得分98分内容通过助教审定附带完整设计报告结构清晰、注释规范便于理解MVC分层逻辑、掌握JDBC连接池配置与常见CRUD操作实践。1. JavaServletJSPMySQL学生成绩管理系统不是“过时技术堆砌”而是Web开发底层能力的实体化训练场你可能在招聘JD里看到“熟悉Java Web基础技术栈”——但真让你手写一个能增删改查、带登录校验、数据持久化到MySQL、页面可跳转可传参的完整系统很多人卡在“Servlet怎么接收表单”“JSP里EL表达式为什么不生效”“MySQL中文乱码怎么连三遍都调不好”。这个标题里的“JavaServletJSPMySQL学生成绩管理系统”不是毕业设计交差用的玩具项目而是一套被反复验证过的最小可行Web开发闭环训练路径它强制你亲手打通HTTP请求→Servlet控制层→JDBC数据访问→MySQL建表与事务→JSP视图渲染→表单回显与错误提示。没有Spring Boot自动装配的遮蔽没有MyBatis的ORM魔法所有链路裸露可见。适合两类人刚学完Java语法想落地Web逻辑的新人以及准备Java面试需夯实底层原理的转岗者。它解决的不是“能不能跑”而是“为什么必须这样写”——比如为什么Servlet要继承HttpServlet而不是直接实现Servlet接口为什么JSP里用c:forEach比% for(...) { %更安全为什么MySQL的utf8mb4字符集和collation配置错一个参数学生姓名就变成问号下面我们就从零开始把这套系统真正跑通、调透、踩明白。2. 搭建环境与初始化工程用最简依赖跑通第一个Servlet请求2.1 JDK、Tomcat、MySQL版本选择与避坑对齐这不是随便下载最新版就能开干的事。实际部署中版本错配是90%新手第一道墙。我们锁定JDK 8u361LTS Apache Tomcat 9.0.83支持Servlet 4.0 MySQL 5.7.44兼容性最强。为什么不是JDK 17或Tomcat 10因为JSP在Tomcat 10中默认使用Jakarta EE命名空间jakarta.servlet.*而本项目源码中大量使用javax.servlet.*包——强行升级会导致编译报错且无意义。MySQL选5.7而非8.x是因为其默认字符集为latin1若未显式配置utf8mb4中文插入会静默失败不是报错是存进去变空格或问号。安装后务必验证# 检查JDK java -version # 输出应含 1.8.0_361 # 检查Tomcat $CATALINA_HOME/bin/version.sh # Linux 或 catalina.bat versionWindows # 检查MySQL mysql --version # 输出应含 5.7.44提示Windows下MySQL安装时勾选“Add to PATH”避免后续JDBC连接因找不到mysql.exe而报Cannot run program mysqlTomcat解压后conf/server.xml中Connector port8080端口若被占用改端口后记得同步修改IDE中Server配置。2.2 手动创建动态Web项目结构不依赖IDE向导很多教程教你在Eclipse里点几下生成项目结果换台电脑就编译失败——因为你没搞清Web应用的真实目录契约。我们手动构建标准结构这是理解web.xml和类加载路径的基础StudentScoreSystem/ ├── src/ # Java源码根目录 │ └── com/example/score/ # 包路径对应Servlet类 │ ├── servlet/ │ │ ├── LoginServlet.java │ │ └── ScoreListServlet.java │ └── dao/ │ └── ScoreDAO.java ├── WebContent/ # Web资源根目录Tomcat部署时的context root │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符Servlet映射核心 │ │ └── lib/ # 放mysql-connector-java-5.1.47.jar注意5.1.x适配MySQL 5.7 │ ├── index.jsp │ ├── login.jsp │ └── score_list.jsp └── build.xml # 可选Ant构建脚本本文暂不展开关键动作WEB-INF/web.xml必须存在且声明web-app根元素Servlet 3.0虽支持注解但本项目源码多基于XML配置强行改注解易出兼容问题lib/下只放一个JDBC驱动jarmysql-connector-java-5.1.47.jar官网下载勿用8.x版本——它默认启用useSSLtrue而MySQL 5.7默认关闭SSL连接会超时src/下的Java类编译后必须输出到WebContent/WEB-INF/classes/IDE需配置Output folder为此路径否则Tomcat启动时报ClassNotFoundException。2.3 写第一个Servlet从HTTP请求到页面跳转的完整链路以LoginServlet为例它接收login.jsp提交的用户名密码校验后跳转到成绩列表页。代码必须体现三个关键点请求获取、业务处理、响应控制。// src/com/example/score/servlet/LoginServlet.java package com.example.score.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取表单参数注意GET请求用getParameterPOST同理 String username req.getParameter(username); String password req.getParameter(password); // 2. 简单校验真实项目应查DB此处先硬编码模拟 if (admin.equals(username) 123456.equals(password)) { // 3. 设置session属性供后续页面读取 req.getSession().setAttribute(user, username); // 4. 重定向到成绩列表页避免重复提交 resp.sendRedirect(req.getContextPath() /score_list.jsp); } else { // 5. 校验失败设置错误信息转发回登录页保持URL不变 req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }逻辑说明doPost()而非doGet()登录表单form methodpost必须匹配req.getSession().setAttribute()将用户标识存入Session是JSP中% session.getAttribute(user) %的数据来源resp.sendRedirect()vsreq.getRequestDispatcher().forward()前者是客户端跳转URL变化GET请求后者是服务器端转发URL不变仍为POST上下文req.getContextPath()获取应用上下文路径如/StudentScoreSystem确保重定向路径可移植不写死/score_list.jsp。3. 数据库设计与JDBC连接让MySQL真正成为系统的“数据心脏”3.1 学生成绩表的最小合理设计含外键与字符集别一上来就建student、course、score三张表还搞级联删除——本项目核心是“成绩管理”聚焦score表即可跑通流程。但字段设计必须直击痛点字段名类型约束说明idINT PRIMARY KEY AUTO_INCREMENTNOT NULL主键自增student_nameVARCHAR(50)NOT NULL学生姓名必须设为utf8mb4course_nameVARCHAR(50)NOT NULL课程名称scoreDECIMAL(5,2)CHECK(score BETWEEN 0 AND 100)成绩精确到小数点后两位create_timeDATETIMEDEFAULT CURRENT_TIMESTAMP记录插入时间执行建表SQL前必须先执行字符集设置这是中文乱码的终极解法-- 连接MySQL后立即执行 SET NAMES utf8mb4; CREATE DATABASE IF NOT EXISTS score_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE score_db; CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_name VARCHAR(50) NOT NULL, course_name VARCHAR(50) NOT NULL, score DECIMAL(5,2) CHECK(score 0 AND score 100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;注意CHARACTER SET utf8mb4和COLLATE utf8mb4_unicode_ci必须同时指定缺一不可。utf8mb4支持emoji和四字节中文如某些生僻字utf8mb4_unicode_ci排序规则比utf8mb4_general_ci更准确。3.2 JDBC工具类封装解决连接泄漏与SQL注入两大顽疾新手常犯错误每次操作都新建Connection用完不close拼接SQL字符串导致注入。正确做法是封装JDBCUtil用静态块加载驱动用try-with-resources保证资源释放// src/com/example/score/dao/JDBCUtil.java package com.example.score.dao; import java.sql.*; public class JDBCUtil { private static final String URL jdbc:mysql://localhost:3306/score_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8; private static final String USER root; private static final String PASSWORD your_password; // 生产环境应加密或配置文件读取 static { try { Class.forName(com.mysql.jdbc.Driver); // MySQL 5.x驱动类名 } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL Driver not found, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 通用查询方法防SQL注入用PreparedStatement public static ResultSet executeQuery(String sql, Object... params) throws SQLException { Connection conn null; PreparedStatement pstmt null; ResultSet rs null; try { conn getConnection(); pstmt conn.prepareStatement(sql); for (int i 0; i params.length; i) { pstmt.setObject(i 1, params[i]); // 参数从1开始编号 } rs pstmt.executeQuery(); return rs; } catch (SQLException e) { close(rs, pstmt, conn); throw e; } } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception e) { e.printStackTrace(); } } } } }参数说明URL中的useUnicodetruecharacterEncodingutf8mb4强制JDBC使用utf8mb4编码与数据库字符集对齐serverTimezoneGMT%2B8解决MySQL时区与Java时区不一致导致的java.sql.SQLException: The server time zone value ... is unrecognized错误executeQuery()接受可变参数Object... params自动绑定到PreparedStatement占位符彻底规避字符串拼接注入。3.3 DAO层实现把成绩数据CRUD操作拆解为原子方法ScoreDAO类负责所有数据库交互方法命名直白addScore、listAllScores不暴露SQL细节// src/com/example/score/dao/ScoreDAO.java package com.example.score.dao; import com.example.score.entity.Score; import java.sql.*; import java.util.ArrayList; import java.util.List; public class ScoreDAO { // 添加成绩 public boolean addScore(Score score) { String sql INSERT INTO score (student_name, course_name, score) VALUES (?, ?, ?); try (Connection conn JDBCUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, score.getStudentName()); pstmt.setString(2, score.getCourseName()); pstmt.setBigDecimal(3, score.getScore()); return pstmt.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } // 查询全部成绩按时间倒序 public ListScore listAllScores() { ListScore scores new ArrayList(); String sql SELECT id, student_name, course_name, score, create_time FROM score ORDER BY create_time DESC; try (Connection conn JDBCUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql); ResultSet rs pstmt.executeQuery()) { while (rs.next()) { Score s new Score(); s.setId(rs.getInt(id)); s.setStudentName(rs.getString(student_name)); s.setCourseName(rs.getString(course_name)); s.setScore(rs.getBigDecimal(score)); s.setCreateTime(rs.getTimestamp(create_time)); scores.add(s); } } catch (SQLException e) { e.printStackTrace(); } return scores; } }关键点Score实体类需有get/set方法及toString()便于调试addScore()中pstmt.setBigDecimal()DECIMAL类型必须用BigDecimal接收避免float/double精度丢失listAllScores()用try-with-resources自动关闭ResultSet、PreparedStatement、Connection杜绝连接泄漏。4. JSP页面开发与EL表达式让动态内容真正“活”在页面上4.1 login.jsp表单提交与错误提示的标准化写法JSP不是HTML它需要处理服务端传递的数据。login.jsp必须包含三要素表单提交、EL表达式取值、JSTL标签库支持。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head meta charsetUTF-8 title登录/title /head body h2学生成绩管理系统登录/h2 !-- 显示错误信息 -- c:if test${not empty error} div stylecolor: red;${error}/div /c:if form action${pageContext.request.contextPath}/login methodpost 用户名input typetext nameusername requiredbr 密码input typepassword namepassword requiredbr input typesubmit value登录 /form /body /html逻辑说明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %引入JSTL核心标签库c:if比% if(...) { %更安全避免Java代码混入HTML${pageContext.request.contextPath}动态获取应用上下文路径确保action地址正确如/StudentScoreSystem/login${error}从Servlet中req.setAttribute(error, ...)传递过来的值EL表达式自动解析required属性HTML5前端校验减轻服务端压力。4.2 score_list.jsp用JSTL遍历成绩列表并格式化显示score_list.jsp展示所有成绩记录需处理空列表、时间格式化、链接跳转。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !DOCTYPE html html head meta charsetUTF-8 title成绩列表/title /head body h2成绩列表/h2 !-- 欢迎信息 -- 欢迎${sessionScope.user}a href${pageContext.request.contextPath}/logout退出/a !-- 添加新成绩链接 -- a href${pageContext.request.contextPath}/add_score.jsp添加成绩/abrbr !-- 成绩表格 -- c:choose c:when test${empty scores} p暂无成绩记录/p /c:when c:otherwise table border1 tr th序号/th th学生姓名/th th课程名称/th th成绩/th th录入时间/th th操作/th /tr c:forEach items${scores} varscore varStatusstatus tr td${status.count}/td td${score.studentName}/td td${score.courseName}/td td${score.score}/td tdfmt:formatDate value${score.createTime} patternyyyy-MM-dd HH:mm:ss//td tda href${pageContext.request.contextPath}/delete_score?id${score.id}删除/a/td /tr /c:forEach /table /c:otherwise /c:choose /body /html参数说明fmt:formatDateJSTL格式化标签将java.util.Date或java.sql.Timestamp转为可读字符串varStatusstatus获取循环状态${status.count}从1开始计数比${status.index}更符合人类习惯${sessionScope.user}从Session中取值sessionScope明确作用域避免与requestScope混淆删除链接?id${score.id}传递主键由DeleteServlet接收并执行删除。4.3 add_score.jsp表单验证与数据回显的实战技巧新增成绩页面需支持“提交失败后保留已填内容”这是用户体验分水岭。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head meta charsetUTF-8 title添加成绩/title /head body h2添加成绩/h2 !-- 错误提示 -- c:if test${not empty errorMsg} div stylecolor: red;${errorMsg}/div /c:if form action${pageContext.request.contextPath}/add_score methodpost 学生姓名input typetext namestudentName value${param.studentName ! null ? param.studentName : } requiredbr 课程名称input typetext namecourseName value${param.courseName ! null ? param.courseName : } requiredbr 成绩input typenumber namescore step0.01 min0 max100 value${param.score ! null ? param.score : } requiredbr input typesubmit value提交 /form /body /html技巧解析value${param.studentName ! null ? param.studentName : }param隐式对象获取URL参数如?studentName张三实现提交失败后自动回显step0.01input typenumber支持小数步进匹配DECIMAL(5,2)精度min0 max100HTML5前端校验与数据库CHECK约束形成双重保障。5. 常见问题排查那些让开发者抓狂却极易解决的“玄学”错误5.1 现象JSP页面中文显示为“???”但MySQL里查数据是正常的原因JSP文件本身编码、Tomcat响应编码、MySQL连接编码三者未统一。常见漏点是JSP文件保存为ANSIWindows记事本默认而非UTF-8。解决在IDE中右键JSP文件 → “Properties” → Encoding → 选“UTF-8”JSP顶部声明% page contentTypetext/html;charsetUTF-8 %web.xml中添加过滤器全局设置响应编码filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping5.2 现象Servlet类编译成功但访问时404Tomcat日志报Servlet [xxx] is not available原因web.xml中servlet-class路径写错或WEB-INF/classes/下class文件未生成。解决检查web.xml中servlet-class是否与Java类的完整包路径一致如com.example.score.servlet.LoginServlet查看WebContent/WEB-INF/classes/com/example/score/servlet/LoginServlet.class是否存在若用IDE确认Project → Properties → Java Build Path → Source → Output folder指向WebContent/WEB-INF/classes。5.3 现象JDBC连接MySQL报Communications link failure或Connection refused原因MySQL服务未启动或JDBC URL端口错误默认3306或防火墙拦截。解决Windows下运行services.msc找到“MySQL57”服务确保状态为“正在运行”命令行执行netstat -ano | findstr :3306确认3306端口被mysqld.exe占用检查JDBC URLjdbc:mysql://localhost:3306/...若MySQL装在虚拟机localhost需改为宿主机IP。5.4 现象c:forEach遍历不出数据页面空白原因Servlet中未将List放入request.setAttribute(scores, scores)或JSP中EL表达式写错如${scoreList}而非${scores}。解决在Servlet中确认req.setAttribute(scores, scoreDAO.listAllScores());在JSP中检查c:forEach items${scores} ...的items属性名是否与setAttribute的key完全一致区分大小写开启JSP调试在web.xml中添加jsp-configjsp-property-groupurl-pattern*.jsp/url-patternel-ignoredfalse/el-ignored/jsp-property-group/jsp-config。5.5 现象添加成绩后页面跳转到空白页或报HTTP Status 405原因Servlet中处理POST请求的方法名为doPost()但web.xml中url-pattern映射到的Servlet类名错误或表单methodpost与Servlet中doGet()方法冲突。解决确认表单form methodpost确认Servlet类中重写了doPost()非doGet()检查web.xml中该Servlet的servlet-class是否指向正确类且url-pattern与表单action匹配如/add_score对应url-pattern/add_score/url-pattern。6. 进阶技巧用Filter统一处理登录校验与异常让代码更健壮6.1 登录校验Filter避免每个Servlet重复写Session判断把登录检查从每个Servlet中抽离用Filter实现AOP式拦截。创建LoginFilter// src/com/example/score/filter/LoginFilter.java package com.example.score.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); // 白名单登录页、登录Servlet、静态资源放行 String uri request.getRequestURI(); if (uri.endsWith(/login.jsp) || uri.endsWith(/login) || uri.endsWith(/css/) || uri.endsWith(/js/)) { chain.doFilter(req, resp); return; } // 校验Session中是否有user if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }然后在web.xml中注册filter filter-nameLoginFilter/filter-name filter-classcom.example.score.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/*/url-pattern /filter-mapping效果所有请求除白名单外自动校验登录态未登录则重定向到登录页——从此ScoreListServlet里不用再写if (session null) ...。6.2 全局异常处理Filter捕获500错误并友好提示当DAO抛出SQLExceptionServlet未捕获时用户看到的是Tomcat默认错误页。用Filter统一处理// src/com/example/score/filter/ExceptionFilter.java package com.example.score.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class ExceptionFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(req, resp); } catch (Exception e) { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 记录日志 e.printStackTrace(); // 转发到错误页 request.setAttribute(errorMessage, e.getMessage()); request.getRequestDispatcher(/error.jsp).forward(request, response); } } }配套error.jsp% page contentTypetext/html;charsetUTF-8 % h2系统发生错误/h2 p错误信息${errorMessage}/p a href${pageContext.request.contextPath}/score_list.jsp返回首页/a血泪经验我曾在线上环境因未加此Filter一个SQL语法错误直接暴露完整堆栈给用户被安全团队打回整改。加Filter后错误收敛到统一页面既保护敏感信息又方便运维定位。6.3 性能优化点JDBC连接池替换原生DriverManager当前JDBCUtil.getConnection()每次新建连接高并发下性能崩塌。生产环境必须用连接池。推荐HikariCP轻量、高性能下载hikari-cp-4.0.3.jar放入WEB-INF/lib/创建HikariConfig.javapublic class HikariConfig { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/score_db?...); config.setUsername(root); config.setPassword(pwd); config.setDriverClassName(com.mysql.jdbc.Driver); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }将JDBCUtil.getConnection()调用替换为HikariConfig.getConnection()。后悔药项目上线后QPS超50就出现连接超时加了HikariCP后扛住300 QPS。连接池不是“锦上添花”是Web应用的生存底线。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站