尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JSP+Servlet+JDBC学生管理系统:从登录到增删改查的JavaWeb实战解析

JSP+Servlet+JDBC学生管理系统:从登录到增删改查的JavaWeb实战解析 简介一份基于JavaWeb的学生信息管理系统完整源码与数据库文件适合Java初学者和高校学生用于课程设计或毕业设计参考覆盖学生信息录入、查询、修改、删除等常见管理功能。压缩包共44个文件约322KB包含JSP动态页面、Java源文件、编译后的class文件、图片素材以及SQL数据库脚本、XML配置和Eclipse工程文件目录划分清晰。目前已有1384人学习/浏览。通过源码可学习Servlet、JSP、JDBC三者的实际配合领悟MVC分层设计思想数据库脚本可直接导入快速恢复学生表、课程表等演示数据JSP页面覆盖登录、列表、新增、修改、删除等典型功能模块并涉及登录验证与数据交互处理适合动手实践帮助理解Web应用从前端页面到后端逻辑、再到底层数据库的完整请求链路。1. 先想明白这套 JSP ServletJDBC 学生管理系统是怎么组织起来的在各类课程设计、毕业设计资源里搜索「学生信息管理系统」出来的结果大多是 Spring Boot 全家桶或 SSM 框架的项目。它们有个通病Maven 依赖下载半天、启动类绕了几层真正与业务相关的数据操作反而被框架标签盖得严严实实。这套基于 JSP Servlet JDBC 的老牌学生信息管理系统恰恰因为没有任何框架包装成为理解 JavaWeb 请求流转最直接的素材。压缩包里的结构非常清晰WebContent下全是 JSP 页面src下是 Servlet 与业务类stuinfo.sql是现成的初始化脚本。想看明白「一次 HTTP 请求如何从 JSP 页面进入 Java 代码、再返回页面渲染」的完整链路它就是最合适的教学样本。2. 建表与连接从 stuinfo.sql 看学生表设计以及 JDBC 为什么不直接用 DriverManager2.1 从目录结构看懂这套系统的分层通过 Eclipse 导入这个 Dynamic Web Project 之后先不要急着运行浏览一遍目录结构。这个项目的根目录下Java 源码全部放在src目录Web 资源统一放在WebContent目录build目录是 Eclipse 自动生成的编译输出目录。关键文件与职责如下表所示文件路径承担的职责WebContent/index.jsp登录成功后的主页面入口通常包含功能菜单WebContent/Login.jsp用户登录表单页提交到登录 ServletWebContent/erro.jsp登录失败或 Session 失效时的错误回显页WebContent/list.jsp学生数据列表页包含检索和分页入口WebContent/insert.jsp/doinsert.jsp新增学生录入界面与接收请求的处理页面WebContent/update.jsp/doUpdate.jsp记录回显后的编辑页与更新操作处理页WebContent/Delete.jsp/doDelete.jsp删除确认页与执行删除的处理页src下的自定义包Servlet 控制器、Student 实体类、数据库访问类stuinfo.sql建库、建表、插入初始数据的 SQL 脚本从这套结构能看出经典的瘦控制器写法JSP 页面负责展示和收集参数Servlet 接收请求后调用数据访问层再将结果放回 request 域或 session 域。有的版本会把 DAO 层单独拆出一个类有的版本直接在一个 Servlet 里用Connection写死 SQL导入后留意src下包的划分即可。2.2 从 stuinfo.sql 拆解学生表字段设计打开数据库脚本核心是一个学生信息表。以下是与常见课程设计项目一致的建表语句CREATE DATABASE IF NOT EXISTS db_student DEFAULT CHARACTER SET utf8mb4; USE db_student; DROP TABLE IF EXISTS t_student; CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键自增, stu_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号用字符串而非整数, stu_name VARCHAR(30) NOT NULL COMMENT 姓名, gender CHAR(1) DEFAULT 男 COMMENT 性别单字符存储, birth_date DATE DEFAULT NULL COMMENT 出生日期, phone VARCHAR(15) DEFAULT NULL COMMENT 联系方式, enroll_date DATE DEFAULT NULL COMMENT 入学时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生信息表;学号字段用VARCHAR(20)而不是INT是一个容易被忽略但很关键的选型。学号可能出现20240001这样的前导零用整数类型存储会自动去掉前导零后续做精确匹配时会产生数据不一致而且学号本身没有算术运算需求字符串是数据库设计中的合理选择。gender字段用CHAR(1)配合默认值简单直接如果想要更强的扩展性可以用TINYINT存代号再加字典表解释但课程设计场景下第一种更直观。主键id使用自增整数与业务字段stu_no分开好处是后续做成绩表、选课表关联时可以用短主键建外键。如果该项目的 SQL 脚本里还包含了用户表一般会是username和password两个字段便于实现登录验证。2.3 JDBC 连接驱动加载到连接池的取舍运行这套系统前需要先明白数据库连接是从哪里来的。课程设计版本最常见到的写法是直接用DriverManager创建连接package cn.student.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/db_student?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD root; static { try { // MySQL 5.x 驱动类名MySQL 8.x 需要换成 com.mysql.cj.jdbc.Driver Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL 驱动未找到请检查 WEB-INF/lib 下是否有 jar 包); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这里每次请求都会创建物理连接在高并发真实项目中是最先被淘汰的写法但对于本系统这种教学性质的小规模应用完全够用。关键在于 URL 上的三个参数useSSLfalse是为了跳过 MySQL 8.x 默认的 SSL 握手警告serverTimezoneAsia/Shanghai解决驱动连接时区校验导致的报错characterEncodingutf8确保中文以 UTF-8 编码写入数据库。如果本地 MySQL 版本是 8.x 及以上驱动类名要换成com.mysql.cj.jdbc.Driver同时 jar 包也需要替换成mysql-connector-java-8.0.x.jar否则会抛ClassNotFoundException。真实项目里更常见的做法是引入Druid或HikariCP连接池核心思路是初始化时预建一批连接避免每次请求都经历「建立连接 → 执行 SQL → 销毁连接」的完整生命周期。3. 登录这一步Servlet 会话检查在 Login.jsp 与 erro.jsp 之间怎么起作用3.1 一次登录请求的完整流转登录功能看起来只是「比对用户名和密码」但这套源码里隐藏着 JavaWeb 状态管理的核心逻辑。用户在Login.jsp输入账号密码后表单会以 POST 方式提交到登录 Servlet。与新版 Servlet 注解配置不同老项目通常在WEB-INF/web.xml里声明映射关系servlet servlet-nameLoginClServlet/servlet-name servlet-classcn.servlet.LoginClServlet/servlet-class /servlet servlet-mapping servlet-nameLoginClServlet/servlet-name url-pattern/loginCl/url-pattern /servlet-mapping对应的Login.jsp核心表单代码form actionloginCl methodpost input typetext nameusername placeholder用户名 / input typepassword namepassword placeholder密码 / button typesubmit登 录/button /formServlet 里接收参数并校验的代码通常是这样的protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); // 校验逻辑查询 t_user 表中是否存在匹配记录 UserDao dao new UserDao(); User user dao.findByUsernameAndPassword(username, password); if (user ! null) { // 登录成功将用户信息放入 Session 而不是 request HttpSession session request.getSession(); session.setAttribute(currentUser, user); response.sendRedirect(index.jsp); } else { // 登录失败转发到错误页 request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(erro.jsp).forward(request, response); } }注意这段代码里的两个关键动作。第一request.setCharacterEncoding(UTF-8)必须放在读取参数之前否则 POST 请求中的中文参数会出现乱码这个问题在中文用户名校验时尤其明显。第二成功用sendRedirect失败用forward区别在于重定向是两次请求地址栏会变成index.jsp刷新页面不会重复提交表单转发是一次请求内的跳转request 域中的errorMsg属性还能继续使用。这是课程设计答辩时最常见的考点。3.2 Session 超时与登出处理登录成功之后用户信息存放在 Session 中。后续index.jsp或其他 JSP 页面读取当前用户常见写法是session.getAttribute(currentUser)。Session 默认超时时间为 30 分钟如果希望调整可以在web.xml中显式配置session-config session-timeout60/session-timeout /session-config这里的单位是分钟。设置太短会导致用户操作到一半被迫重新登录设置太长又会增加服务器内存压力。对于管理员角色单一的学生管理系统30 到 60 分钟是比较合理的区间。相应地免登录退出通常是一个logout.jsp或独立的 Servlet核心代码只有三行HttpSession session request.getSession(false); if (session ! null) { session.invalidate(); // 销毁整个 Session而不是只删除某个属性 } response.sendRedirect(Login.jsp);getSession(false)的false参数值得说明它表示如果当前没有关联的 Session 就直接返回null而不是新创建一个空 Session。读到null之后不再执行任何会话操作避免了大量无效 Session 对象堆积。3.3 用 Filter 统一拦截未登录访问第一版系统往往在每一个 JSP 页面里都写一段if (session.getAttribute(currentUser) null)的判断代码大量重复且容易漏掉。更好的处理方案是定义一个统一的登录过滤器在请求进入页面之前做拦截package cn.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; // 登录页和登录处理接口本身必须放行否则会形成循环跳转 String uri request.getRequestURI(); if (uri.endsWith(Login.jsp) || uri.endsWith(loginCl)) { chain.doFilter(request, response); return; } HttpSession session request.getSession(false); if (session ! null session.getAttribute(currentUser) ! null) { chain.doFilter(request, response); } else { response.sendRedirect(Login.jsp); } } }在web.xml中注册过滤器时url-pattern映射到/*即可覆盖所有页面。这个过滤器的关键判断是放行条件Login.jsp和登录 Servlet 本身不能拦截否则用户尚未登录时访问登录页也会被重定向到登录页形成死循环。静态资源如 CSS、JS、图片建议也加入放行列表否则过滤器会对imgs目录下的图片也做一次会话检查没有性能问题但会多出无意义的代码执行。如果有多个过滤器执行顺序与web.xml中filter-mapping的声明顺序保持一致。4. 落地的增删改查从 list.jsp 到 doinsert.jsp、doDelete.jsp 的完整请求链4.1 列表页的查询链路Servlet 查询 JSP 遍历学生列表页是这套系统的核心界面。正常情况下用户访问list.jsp时前面的过滤器会先确认 Session 有效。列表数据的加载逻辑通常由一个查询 Servlet 完成protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); // 从 DAO 层获取全部学生记录 StudentDao dao new StudentDao(); ListStudent studentList dao.findAll(); request.setAttribute(studentList, studentList); // 转发到 list.jsp 渲染页面 request.getRequestDispatcher(list.jsp).forward(request, response); }对应的StudentDao中查找全部记录的方法public ListStudent findAll() { ListStudent list new ArrayList(); String sql SELECT id, stu_no, stu_name, gender, birth_date, phone, enroll_date FROM t_student; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStuNo(rs.getString(stu_no)); s.setStuName(rs.getString(stu_name)); s.setGender(rs.getString(gender)); s.setBirthDate(rs.getDate(birth_date)); s.setPhone(rs.getString(phone)); s.setEnrollDate(rs.getDate(enroll_date)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码使用了 JDK 7 引入的 try-with-resources 语法Connection、PreparedStatement、ResultSet都会在代码块结束后自动关闭不需要手动写一堆finally来释放资源。注意关闭顺序是ResultSet先于Statement先于Connection严格遵循从里到外的顺序。使用PreparedStatement而不是Statement拼接 SQL既能避免 SQL 注入又不需要处理各种引号转义问题。4.2 新增与删除POST 表单与 GET 传参的差异新增学生的入口在insert.jsp表单提交到doinsert.jsp。以 Servlet 方式处理时doPost方法内逐字段读取表单参数然后调用 DAO 的insert方法。一个容易踩的坑是日期字段的处理SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd); String birthDateStr request.getParameter(birthDate); java.util.Date utilDate sdf.parse(birthDateStr); java.sql.Date sqlDate new java.sql.Date(utilDate.getTime());SimpleDateFormat默认的解析比较宽松2024-13-50这类非法日期不会立刻报错而是会递进到下一月份或年份。想要严格校验可以设置sdf.setLenient(false)这样非法日期会直接抛出ParseException。插入成功后一般会重定向回列表页避免刷新页面时重复插入相同记录。删除操作通常使用 GET 方式携带主键 IDprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int id Integer.parseInt(request.getParameter(id)); StudentDao dao new StudentDao(); boolean flag dao.deleteById(id); if (flag) { response.sendRedirect(listCl); // 重定向回到列表 Servlet } else { request.setAttribute(errorMsg, 删除失败请重试); request.getRequestDispatcher(erro.jsp).forward(request, response); } }删除按钮对应的前端链接写法为a hrefdeleteCl?id${student.id} onclickreturn confirm(确定删除这条记录吗)删除/a这里把删除动作放在a标签里本质上是 GET 请求参数直接暴露在 URL 上除了主键之外不要携带任何敏感信息。同时onclick的confirm弹窗是前端防误删的第一道防线真正的高危删除操作一般还会在 Servlet 端加一层逻辑比如判断该学生是否具备关联成绩记录。需要特别说明的是GET 请求的 ID 参数在 Servlet 端记得处理异常情况——如果 URL 参数缺失或不是数字Integer.parseInt会抛NumberFormatException导致 500 错误。常见的处理方式是把parseInt放在 try-catch 里发生异常时直接重定向到 erro.jsp而不是让异常一路冒泡。4.3 修改与回显如何准确更新目标记录修改功能由两条路径配合完成。点击列表页的「编辑」链接时携带记录 ID 请求查询 ServletServlet 按主键查出单条记录并回显到update.jsp。编辑提交后再由doUpdate.jsp及对应 Servlet 接收全部字段并执行 UPDATE 语句。回显的核心在于隐藏域form actionupdateCl methodpost !-- 隐藏域用于提交时定位要更新的记录 -- input typehidden nameid value${student.id} / 学号input typetext namestuNo value${student.stuNo} /br/ 姓名input typetext namestuName value${student.stuName} /br/ input typesubmit value保存修改 / /form隐藏域是这类老项目里最常见的回显做法它是表单中不可见的参数提交时和普通字段一样进入请求参数列表。update.jsp本身的 EL 表达式${student.stuNo}是从 request 域中取出的无转义输出前提是查询 Servlet 使用setAttribute(student, student)设置了对象。更新逻辑在执行时要限制WHERE id?否则会批量更新全部记录这一点需要特别留意。另外前端页面展示学生列表时建议做 HTML 转义防止学生在姓名里写入script标签JSP 页面可以使用fn:escapeXml()或 JSTL 的c:out标签来输出字段直接使用${student.name}在 XSS 防护上是存在缺口的。5. 部署与排错Eclipse Tomcat MySQL 一次跑通的关键细节5.1 三个版本决定了项目能否启动老项目之所以在别人电脑上跑不起来绝大多数时候是版本不匹配。需要先锁定三组对应关系组件推荐版本说明JDK1.8课程设计项目体积小没有用到高版本特性JDK 8 兼容性最好Tomcat8.5 或 9.x支持 Servlet 3.1 / 4.0与 Eclipse 集成最稳定MySQL5.7 或 8.05.7 配合旧驱动开箱即用8.0 需换驱动类名并处理时区驱动 jar5.1.49 或 8.0.33与 MySQL 版本严格对应两个版本不能混用驱动 jar 包建议直接复制到WebContent/WEB-INF/lib目录而不是只加入 Build Path。后者在 Eclipse 中编译能通过但发布到 Tomcat 时可能因为WEB-INF/lib下没有 jar 而报ClassNotFoundException。这个坑在导入外部源码时出现频率极高。5.2 一处乱码三处排查中文乱码是 JSP Servlet 时代最顽固的问题。检查顺序推荐如下。第一处是 JSP 页面顶部的pageEncoding声明% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %同时确保 HTML 的meta charsetUTF-8与之一致。第二处是 POST 请求的编码处理需要在 Servlet 内读取参数之前执行request.setCharacterEncoding(UTF-8)。第三处是 GET 请求的地址栏参数这一处经常被漏掉Tomcat 8 及以上对 GET 请求默认使用 UTF-8而 Tomcat 7 及以下默认是 ISO-8859-1。如果部署环境是低版本 Tomcat需要在conf/server.xml的 Connector 节点上加上Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /设置完成后重启 Tomcat 才生效。数据库连接 URL 里的characterEncodingutf8对应最后一处保障。如果这三处都统一了页面上中文基本不会出现乱码问题stuinfo.sql建表语句里的DEFAULT CHARSETutf8mb4则是数据侧的兜底方案。5.3 按错误形态对照排除部署过程中遇到控制台报错时直接看异常信息的第一个英文单词基本能确定方向控制台异常关键词错误原因处理方式ClassNotFoundException: com.mysql.jdbc.Driver驱动 jar 缺失或版本放错位置将 mysql-connector jar 放入WEB-INF/lib并重新发布Communications link failure数据库地址、端口或服务未启动检查 MySQL 服务状态确认3306端口未被占用核对连接 URLUnknown database db_student未执行stuinfo.sql或库名不一致在 Navicat / 命令行中执行脚本再确认连接的库名Access denied for user rootlocalhost数据库账号或密码错误修改DBUtil中的账号密码注意 MySQL 8.x 默认 caching_sha2_password 插件与老驱动不兼容Table db_student.t_student doesnt exist表名大小写不匹配在 Linux 下 MySQL 表名区分大小写统一改为全小写最后一个常用调试技巧在 DAO 层方法内部临时打印 SQL 与参数定位是 SQL 拼接问题还是参数传入问题System.out.println(Executing SQL: sql); System.out.println(Query param id: id);定位完成后记得移除这些调试日志避免控制台输出干扰答辩演示。list.jsp页面直接输出的字段如果出现空值检查查询出的Student对象是否在 Servlet 端正确执行了setAttribute同时确认 JSTL 标签库在页面中已经用% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %完成声明并用 EL 的${student.stuName}形式输出这能让页面代码比原始 Java 脚本片段干净得多。本文还有配套的精品资源点击获取
返回列表