简介这是一套面向高校计算机相关专业学生的Java Web课程设计资源采用JSPServletMySQL技术栈实现学生信息管理系统适合作为期末大作业、课程设计或Java Web入门练手项目。压缩包共241个文件约9.17MB其中109个Java源文件承载后端业务逻辑与数据库操作56个JSP页面负责前端展示与交互另有23个JS脚本、8个CSS样式及15个jar依赖包配合图片、字体、SQL建表脚本和说明文档构成完整可运行工程。资源已通过本地编译验证评审分达98分难度适中内容经助教老师审定。目前已有247人学习下载。读者可获得完整源码、数据库脚本与文档说明便于快速理解分层结构、掌握增删改查与页面跳转实现并在此基础上二次开发或撰写设计报告。1. 从零手搓 JavaJSPMySQL 学生信息管理系统这套老技术栈为什么还值得做一遍如果你正在搜「JavaJSPMysql实现Web学生信息管理系统源码数据库文档说明」大概率是三种人之一课程设计deadline在即的学生、想拿一个完整CRUD项目练手的Java初学者、或者面试前想补一段「能讲清楚请求怎么从浏览器走到数据库」的实战经历的求职者。这套技术栈听起来确实老SpringBoot满天飞的年代还写JSP很多人第一反应是「过时了」。但恰恰因为它够原始你才能看清一个Web系统最底层的骨架Servlet怎么接请求、JSP怎么渲染页面、JDBC怎么连数据库、Session怎么做登录态。这些在Spring框架里被封装成注解和自动配置的东西在JSP项目里全是明面上的代码调一遍比看十篇八股文管用。这篇文章不讲空话直接按「环境搭起来→数据库建起来→功能跑起来→坑填起来」的路径走一遍目标是让你在本地完整复现一个能增删改查、能登录、能分页的学生信息管理系统。2. 环境与项目骨架把 JDK、Tomcat、MySQL 三件套跑通2.1 版本选型为什么我建议 JDK 8 Tomcat 8.5 MySQL 5.7/8.0这套组合不是随便挑的。JSP 技术栈对高版本 JDK 的兼容性一直有玄学问题JDK 8 是经过大量项目验证最稳的版本Tomcat 8.5 对 Servlet 3.1 规范支持完整MySQL 5.7 和 8.0 在 JDBC 驱动上只差一个com.mysql.cj.jdbc.Driver的写法。如果你用 JDK 17 配 Tomcat 10javax.servlet 包名已经改成 jakarta.servlet老项目的 JSP 页面和 Servlet 类全部要改 import纯属给自己找麻烦。新手直接按下面这个表配熟手可以跳过。组件推荐版本备注JDK1.8.0_xxx环境变量 JAVA_HOME 指向安装目录Tomcat8.5.x解压即用不装服务MySQL5.7 或 8.08.0 需注意驱动类名和时区参数IDEEclipse/IDEA能配 Tomcat 即可JDBC 驱动mysql-connector-java 5.1.x 或 8.0.x放 WEB-INF/lib 下环境变量配置这一步是新手第一个翻车点。JAVA_HOME 要指向 JDK 根目录而不是 bin 目录Path 里加%JAVA_HOME%\bin。配完在命令行敲java -version和javac -version两个都能出版本号才算过。Tomcat 不用配环境变量直接在 IDE 里关联解压目录就行。2.2 项目目录结构一个标准 JSP Web 项目长什么样在 Eclipse 里新建 Dynamic Web Project或者 IDEA 里建 Java Enterprise 项目生成的结构应该包含这几个关键目录StudentMS/ ├── src/ # Java 源码 │ └── com/student/ │ ├── dao/ # 数据库操作层 │ ├── entity/ # 实体类 │ ├── servlet/ # 控制层 │ └── util/ # 工具类DBUtil ├── WebContent/ 或 web/ │ ├── WEB-INF/ │ │ ├── lib/ # 放 mysql-connector-java.jar │ │ └── web.xml # 部署描述符 │ ├── css/ │ ├── js/ │ ├── login.jsp │ ├── student_list.jsp │ └── student_edit.jsp └── ...这个分层不是形式主义。DAO 层只管 SQLServlet 层只管收参数和跳转JSP 只管展示出了问题你能快速定位是哪一层。很多课设项目把所有逻辑塞进 JSP 的% %脚本片段里改一个字段要在三个页面里找维护成本极高。2.3 数据库连接工具类一个 DBUtil 解决所有 JDBC 重复代码每次查数据都写一遍Class.forNameDriverManager.getConnection是纯体力活。抽一个工具类出来后面所有 DAO 都调它package com.student.util; import java.sql.*; public class DBUtil { // MySQL 8.0 用 com.mysql.cj.jdbc.Driver5.7 用 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // 8.0 必须加 serverTimezone否则报时区错误 private static final String URL jdbc:mysql://localhost:3306/student_db ?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(JDBC驱动加载失败检查lib下有没有jar包, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 统一关闭资源顺序不能反ResultSet - Statement - Connection public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }逻辑说明静态代码块在类加载时执行一次把驱动注册好后面每次getConnection直接拿连接。参数说明useUnicodetruecharacterEncodingutf8保证中文不乱码serverTimezoneAsia/Shanghai是 MySQL 8.0 的硬性要求不加会抛The server time zone value xxx is unrecognized。关闭顺序必须是 ResultSet 先关再关 Statement最后关 Connection反过来关会导致游标失效异常。3. 数据库设计与建表学生表、用户表、成绩表怎么拆3.1 三张核心表的字段设计与 SQL 建表语句学生信息管理系统的数据模型不复杂但字段类型选错后面全是坑。学号用 VARCHAR 不用 INT因为学号可能有前导零性别用 CHAR(1) 存「男/女」比用 INT 存 0/1 直观创建时间用 DATETIME 方便排序。下面直接给可执行的建表 SQLCREATE DATABASE IF NOT EXISTS student_db DEFAULT CHARSET utf8mb4; USE student_db; -- 用户表登录用 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT admin ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 学生表核心业务表 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(1) DEFAULT 男, age INT, major VARCHAR(100) COMMENT 专业, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 初始化管理员账号 INSERT INTO t_user (username, password) VALUES (admin, 123456);逻辑说明utf8mb4而不是utf8因为 MySQL 的 utf8 是阉割版存不了 emoji 和部分生僻字。AUTO_INCREMENT主键让插入时不用管 id。UNIQUE约束在学号上防止重复录入。参数说明ENGINEInnoDB支持事务和外键DEFAULT CHARSETutf8mb4在建表级别锁定字符集避免继承数据库默认值出问题。3.2 分页查询的 SQL 写法LIMIT 的两个参数别搞反学生列表页一定要分页不然数据一多页面直接卡死。MySQL 的分页靠LIMIT offset, size第一个参数是跳过多少条第二个是取多少条-- 第 1 页每页 10 条 SELECT * FROM t_student ORDER BY id DESC LIMIT 0, 10; -- 第 2 页 SELECT * FROM t_student ORDER BY id DESC LIMIT 10, 10; -- 第 n 页offset (n-1) * size逻辑说明ORDER BY id DESC保证新录入的学生排前面分页顺序稳定。参数说明offset 的计算公式是(当前页码 - 1) * 每页条数这个公式写错就会出现第一页和第二页数据重复或者跳页。总记录数用单独的SELECT COUNT(*) FROM t_student查用来算总页数。3.3 模糊查询与动态条件拼接WHERE 11 到底该不该用按姓名或专业搜索时SQL 条件要动态拼。常见做法是用WHERE 11打底后面每个条件用AND追加StringBuilder sql new StringBuilder(SELECT * FROM t_student WHERE 11); ListObject params new ArrayList(); if (name ! null !name.trim().isEmpty()) { sql.append( AND name LIKE ?); params.add(% name.trim() %); } if (major ! null !major.trim().isEmpty()) { sql.append( AND major ?); params.add(major); } sql.append( ORDER BY id DESC LIMIT ?, ?); params.add((pageNum - 1) * pageSize); params.add(pageSize);逻辑说明WHERE 11是个恒真条件作用是让后面的AND拼接不用判断是不是第一个条件省掉一堆 if-else。参数说明LIKE ?配合%value%实现模糊匹配注意百分号是加在 Java 字符串里而不是 SQL 里。所有参数走PreparedStatement的setObject不要用字符串拼接否则 SQL 注入一打一个准。4. Servlet JSP 实现增删改查请求怎么走完整链路4.1 学生列表页Servlet 查数据JSP 只负责渲染列表功能是理解 MVC 的最佳入口。Servlet 负责调 DAO 拿ListStudent塞进 request 域转发到 JSP// StudentListServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int pageNum 1; String pageStr request.getParameter(pageNum); if (pageStr ! null !pageStr.isEmpty()) { pageNum Integer.parseInt(pageStr); } int pageSize 10; StudentDao dao new StudentDao(); ListStudent list dao.findByPage(pageNum, pageSize); int total dao.countAll(); int totalPages (total pageSize - 1) / pageSize; request.setAttribute(studentList, list); request.setAttribute(pageNum, pageNum); request.setAttribute(totalPages, totalPages); request.getRequestDispatcher(/student_list.jsp).forward(request, response); }逻辑说明getParameter拿页码没传就默认第 1 页。totalPages用(total pageSize - 1) / pageSize向上取整避免最后一页数据被截断。参数说明setAttribute存的数据在转发后 JSP 里用 EL 表达式${studentList}取。forward是服务器内部跳转浏览器地址栏不变如果用sendRedirect则 request 域数据会丢。JSP 页面用 JSTL 的c:forEach遍历不要写% for(...) %脚本片段% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 trth学号/thth姓名/thth性别/thth专业/thth操作/th/tr c:forEach items${studentList} varstu tr td${stu.sno}/td td${stu.name}/td td${stu.gender}/td td${stu.major}/td td a hrefStudentEditServlet?id${stu.id}编辑/a a hrefStudentDeleteServlet?id${stu.id} onclickreturn confirm(确定删除)删除/a /td /tr /c:forEach /table逻辑说明c:forEach是 JSTL 标签编译后就是 Java 循环但页面干净得多。${stu.sno}走的是实体类的 getter 方法所以 Student 类必须有getSno()。参数说明删除链接加onclick确认框防止误点。id参数是数据库主键不是学号因为学号可能被修改。4.2 新增与编辑一个表单两种模式怎么复用新增和编辑的页面结构几乎一样区别只是编辑时要回填数据。用一个 JSP 加判断搞定// StudentEditServlet.java - 处理编辑前的数据回显 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String idStr request.getParameter(id); if (idStr ! null !idStr.isEmpty()) { int id Integer.parseInt(idStr); Student stu new StudentDao().findById(id); request.setAttribute(student, stu); } request.getRequestDispatcher(/student_edit.jsp).forward(request, response); }JSP 表单里用 EL 表达式做回填新增时${student}为 nullEL 会自动显示空字符串form actionStudentSaveServlet methodpost input typehidden nameid value${student.id} 学号input typetext namesno value${student.sno}br 姓名input typetext namename value${student.name}br 性别input typeradio namegender value男 ${student.gender 男 ? checked : }男 input typeradio namegender value女 ${student.gender 女 ? checked : }女br 专业input typetext namemajor value${student.major}br input typesubmit value保存 /form逻辑说明隐藏域id是区分新增和编辑的关键id 为空走 INSERTid 有值走 UPDATE。参数说明EL 三元表达式${student.gender 男 ? checked : }控制单选按钮选中状态注意 EL 里字符串比较用或eq都行。4.3 登录与 Session 拦截别让未登录用户直接访问列表页登录功能的核心是把用户信息存进 Session然后在其他 Servlet 里检查// LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); UserDao dao new UserDao(); User user dao.login(username, password); if (user ! null) { request.getSession().setAttribute(currentUser, user); response.sendRedirect(StudentListServlet); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }逻辑说明登录成功用sendRedirect而不是forward因为跳转后要发起新请求避免刷新页面重复提交表单。参数说明getSession()默认创建新会话setAttribute存的用户对象在整个会话期间有效。其他 Servlet 开头加一段检查User current (User) request.getSession().getAttribute(currentUser); if (current null) { response.sendRedirect(login.jsp); return; }这段代码建议抽成一个 Filter在web.xml里配置拦截所有/*路径比每个 Servlet 里复制粘贴靠谱。5. 避坑与排查JSP 项目最容易翻车的 5 个地方5.1 中文乱码POST 和 GET 要分开治现象表单提交后数据库里存的是????或者页面显示问号。原因POST 请求默认用 ISO-8859-1 解码GET 请求在 Tomcat 8 以后默认 URIEncoding 是 UTF-8 但部分版本不是。解决POST 在 Servlet 第一行加request.setCharacterEncoding(UTF-8)且必须在getParameter之前调用GET 乱码要改 Tomcat 的server.xml在 Connector 标签加URIEncodingUTF-8。数据库连接 URL 里的characterEncodingutf8也要确认加上。5.2 ClassNotFoundException: com.mysql.jdbc.Driver现象启动后第一次查数据库就报驱动找不到。原因mysql-connector-java 的 jar 包没放进WEB-INF/lib目录或者放进去但 IDE 没有重新部署。解决确认 jar 在WEB-INF/lib下然后在 IDE 里对项目执行 Clean重新发布到 Tomcat。注意 MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver写错了也是这个报错。5.3 JSP 页面修改后刷新没变化现象改了 JSP 代码浏览器刷新还是旧页面。原因Tomcat 的 work 目录缓存了编译后的 Servlet。解决在 Tomcat 的conf/server.xml里找到 Host 标签加unpackWARstrue autoDeploytrue或者在 IDE 里配置热部署。最直接的办法是每次改完 JSP 在浏览器里按 CtrlF5 强制刷新还不行就删掉 Tomcat 的 work 目录重启。5.4 删除和编辑功能点了没反应现象列表页点删除链接页面跳转但数据没删。原因链接的href里参数名和 Servlet 里getParameter的名字对不上或者 Servlet 的url-pattern配错了。解决打开浏览器 F12 看 Network 面板确认请求 URL 里带没带 id 参数再看 Servlet 的WebServlet(/StudentDeleteServlet)注解路径和表单 action 是否一致。这种问题用浏览器开发者工具一看就清楚别靠猜。5.5 分页最后一页数据重复或丢失现象翻到最后一页数据跟上一页有重复或者少了几条。原因offset 计算公式写成了pageNum * pageSize而不是(pageNum - 1) * pageSize。解决把公式改对同时检查总页数计算有没有用向上取整。如果每页 10 条、总共 25 条总页数应该是 3 页第三页只有 5 条LIMIT 20, 10正好取到第 21 到 25 条。6. 进阶技巧把项目改造成能写进简历的样子6.1 用 Filter 统一处理编码和登录校验与其在每个 Servlet 里写setCharacterEncoding和 Session 检查不如写一个 Filter 一次性解决WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String uri request.getRequestURI(); // 登录页和登录请求放行其他页面检查 Session if (uri.endsWith(login.jsp) || uri.endsWith(LoginServlet)) { chain.doFilter(req, resp); return; } if (request.getSession().getAttribute(currentUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }逻辑说明WebFilter(/*)拦截所有请求先统一设编码再判断是不是登录相关路径不是就检查 Session。参数说明request.getContextPath()拿到项目部署路径避免硬编码/StudentMS这种前缀换项目名也不用改代码。这个 Filter 写完后所有 Servlet 里的编码和登录检查代码全部删掉代码量直接少三分之一。6.2 用 BaseServlet 消灭重复的 doGet/doPost 分发每个 Servlet 都写doGet调doPost很烦。抽一个 BaseServlet用反射根据请求参数里的method值调用对应方法public class BaseServlet extends HttpServlet { protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method request.getParameter(method); if (method null || method.isEmpty()) { method list; // 默认方法 } try { Method m this.getClass().getMethod(method, HttpServletRequest.class, HttpServletResponse.class); m.invoke(this, request, response); } catch (Exception e) { throw new ServletException(方法调用失败 method, e); } } }逻辑说明重写service方法从请求参数拿方法名用反射调用当前类的同名方法。参数说明子类 Servlet 继承 BaseServlet 后只需要写public void list(...)、public void add(...)这样的方法不用再管 doGet/doPost。请求 URL 变成StudentServlet?methodlist这种形式一个 Servlet 管一个模块的所有操作。6.3 验证清单怎么确认你的系统真的能跑改完代码别急着交按这个清单过一遍验证项操作预期结果登录输入 admin/123456跳转到学生列表页未登录拦截直接访问 StudentListServlet跳回登录页新增填学号姓名提交列表页出现新记录编辑点编辑改专业保存列表页显示新专业删除点删除确认记录消失总数减一分页造 25 条数据翻页3 页最后一页 5 条模糊搜索输入姓名片段搜索只显示匹配记录中文新增姓名「张三」数据库和页面都正常显示这张表过完项目基本就没大问题了。我自己的习惯是每次改完 DAO 层代码先单独写个 main 方法测一下 SQL 能不能跑通再去调 Servlet这样能把问题范围缩小到一层里。JSP 这套东西虽然老但把请求链路走通一遍后面学 SpringMVC 的时候你会发现自己理解得比直接上手框架的人快得多。希望帮到你。本文还有配套的精品资源点击获取
