ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JSP+Servlet+JDBC学生信息管理系统:分页搜索与CRUD实战

JSP+Servlet+JDBC学生信息管理系统:分页搜索与CRUD实战 简介面向Java Web课程设计与期末作业的学生信息管理系统项目包涵盖登录验证、学生信息增删改查、课程关联等典型功能适合正在学习Servlet/JSP、MVC分层架构与数据库编程的初学者。压缩包共209个文件大小仅4.57MB包含11个java源码文件、4个sql建表脚本、37个class编译产物以及150张png界面操作截图可据此快速还原系统运行效果也便于对照源码理解前后端交互。已有414人学习下载。项目按MVC思路组织源码涉及登录管理、数据库连接等核心模块配合数据库表结构脚本与依赖jar包能在Tomcat环境下直接部署实践。通过完整走查登录模块、学生信息管理面板及数据库连接实现读者可掌握JDBC操作、页面请求响应流程、配置管理及项目部署等关键技能是期末设计或Java Web入门练手的实用参考。1. 学生信息管理系统一份能直接交的 Java Web 期末作业期末作业里出现频率最高的题目学生信息管理系统绝对排前三。Java Web 方向更是重灾区——老师要求用 JSP Servlet 做一套完整的 CRUD还要带分页、模糊查询、登录拦截。听起来不难但真动手写的时候大部分人卡在环境配置、JDBC 连接、Tomcat 部署这些基础环节上代码反而写得不多。这套资源就是把「从零到能答辩」的全套 Java Web 学生信息管理系统给你拆好数据库脚本、Dao 层、Servlet 控制层、JSP 页面、配置文件全都在你拿到之后改改数据库密码、换换学校名就能跑起来。适合三类人赶期末作业的学生、刚学完 Java 基础想找个完整 web 项目练手的人、以及需要一套代码做课程设计底子的人。下面我从技术选型开始讲把每一层代码怎么组织的逻辑说清楚再给你踩过的坑。2. 技术选型为什么期末作业首选 JSP Servlet JDBC 而不是 SSM2.1 Tomcat MySQL 8 的环境版本匹配这套资源的技术栈是最经典的 Java Web 三件套JSP Servlet JDBC服务器用 Tomcat 9.x数据库用 MySQL 8.x开发工具用 IDEA 2024 社区版或旗舰版都行。先解释为什么不用 Spring Boot —— 期末作业的本质是让老师看到你理解 HTTP 请求怎么走、Servlet 生命周期怎么回事、JSP 怎么渲染页面。Spring Boot 把这一切都封装了你写完了老师一问三不知反而扣分。环境版本上有个很关键的匹配点JDK 版本和 Tomcat 版本必须兼容。JDK 8 对应 Tomcat 9 完全没问题但如果你电脑装的是 JDK 17直接用 Tomcat 9 也能跑只是要把 web.xml 的头部声明改成 Servlet 4.0 的版本。如果用的是 Tomcat 10那问题就大了——Tomcat 10 把 javax.servlet 包名改成了 jakarta.servlet网上找的很多旧代码直接编译报错。我建议你图省心就用 JDK 8 Tomcat 9这个是兼容性最稳的组合。注意确认 java -version 和 catalina.bat version 的输出结果两个版本号差太大会出现 ClassNotFoundException 或 NoClassDefFoundError这两个错在期末作业现场最容易让脑子一片空白。2.2 JDBC 连接参数与 DBUtil 工具类封装数据库连接这块我见太多人把 DriverManager.getConnection 写在每个 Dao 方法里一个项目里重复了二十几遍。这种写法能跑但一旦数据库密码改了你要全局替换。这套资源里我做了一层 DBUtil 封装用静态代码块加载驱动连接参数集中在常量里。public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }URL 里那串参数一个一个说useUnicodetrue 和 characterEncodingutf8 是保证中文能正常读写少了这两个参数插入的中文到数据库里全变成问号serverTimezoneAsia/Shanghai 是 MySQL 8 必加的不加会报时区错误useSSLfalse 是为了省掉 SSL 握手的时间本机调试没必要开。close 方法里我重载了三个参数版本有的查询没有 ResultSet 就传 null这样做的好处是调用方不用自己写 try-catch-finally 那一大堆。2.3 三层架构在期末作业里的合理边界这套代码用的是标准的 JSP Servlet Dao 三层JSP 只负责展示Servlet 只做请求接收和转发Dao 层只写 SQL。很多同学会在 Servlet 里直接拼 SQL虽然也能跑通但答辩的时候老师一定会问「如果我要把 MySQL 换成 Oracle 怎么办」——你用三层架构只需要改 Dao 层。这套资源里的分包结构是 com.example.entityStudent 实体类、com.example.daoStudentDao 接口 实现类、com.example.servletStudentServlet、com.example.utilDBUtilwebapp 下面放 JSP。这个分包不是随便分的实体类对应数据库表的字段Dao 层对应数据访问逻辑Servlet 对应控制逻辑老师看到这个结构就知道你理解了分层答辩第一印象就稳了。3. 把学生信息 CRUD 跑通从建表到核心代码3.1 数据库表结构设计与约束数据库脚本是这套资源里最先要看的东西。学生表的设计要满足两个最基本的需求一个是信息完整展示一个是学号唯一不能重复。CREATE DATABASE IF NOT EXISTS student_db DEFAULT CHARACTER SET utf8mb4; USE student_db; CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号唯一约束, name VARCHAR(50) NOT NULL COMMENT 姓名, gender CHAR(1) DEFAULT 男 COMMENT 性别, age INT COMMENT 年龄, class_name VARCHAR(50) COMMENT 班级, phone VARCHAR(20) COMMENT 联系电话, email VARCHAR(50) COMMENT 邮箱, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4; INSERT INTO t_student (student_no, name, gender, age, class_name, phone, email) VALUES (2024001, 张伟, 男, 20, 计科2401班, 13800001111, zhangweiexample.com), (2024002, 李娜, 女, 19, 计科2401班, 13800002222, linaexample.com), (2024003, 王强, 男, 21, 软工2402班, 13800003333, wangqiangexample.com);注意三个设计上的细节。第一id 用自增主键但业务上唯一标识是 student_no所以给它加了 UNIQUE 约束——这样即使两个学生同名学号也不会冲突。第二gender 用 CHAR(1) 存「男」「女」省空间查询结果也直观不用像存数字那样还要做映射。第三create_time 设置了 DEFAULT CURRENT_TIMESTAMP插入数据的时候不用手动传时间数据库自动填这个细节答辩时老师可能会问你要答得上来。3.2 StudentDao 实现类的 SQL 与参数绑定Dao 层是整套代码里最容易出 bug 的地方。这套资源里的 StudentDao 实现类用了 PreparedStatement 做参数绑定这个选择很重要。我见过有人图省事用 Statement 直接拼 SQL 字符串结果输入的名字里带个单引号SQL 直接报错。PreparedStatement 有两个核心优势一是预编译SQL 结构先发给数据库解析执行的时候只传参数性能好二是参数用占位符 ? 绑定从根上杜绝 SQL 注入。期末作业里写 PreparedStatement 也是加分项老师看了会认为你有安全意识。public class StudentDaoImpl implements StudentDao { Override public ListStudent findByPage(int pageNo, int pageSize, String keyword) { ListStudent list new ArrayList(); String sql SELECT id, student_no, name, gender, age, class_name, phone, email, create_time FROM t_student WHERE 11; ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql AND (name LIKE ? OR student_no LIKE ? OR class_name LIKE ?); String likePattern % keyword.trim() %; params.add(likePattern); params.add(likePattern); params.add(likePattern); } sql ORDER BY id DESC LIMIT ?, ?; Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); ps conn.prepareStatement(sql); for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } ps.setInt(params.size() 1, (pageNo - 1) * pageSize); ps.setInt(params.size() 2, pageSize); rs ps.executeQuery(); while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStudentNo(rs.getString(student_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setAge(rs.getInt(age)); s.setClassName(rs.getString(class_name)); s.setPhone(rs.getString(phone)); s.setEmail(rs.getString(email)); s.setCreateTime(rs.getTimestamp(create_time)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } return list; } }这段代码有个非常值得抄的写法SQL 先拼固定部分再用 StringBuilder 或 按条件追加 where 片段参数统一放到 List 里最后再按顺序绑定。这样做的核心原因是参数个数动态变化先把所有 ? 对应的值收拢到一个容器里最后统一 set 进去。LIMIT 的参数要注意第一个参数是起始行序号从 0 开始所以 (pageNo - 1) * pageSize比如当前第 2 页pageSize 是 10那起始行就是 10正好是第 11 条记录——这里要是写成 pageNo * pageSize第二页就把第一页的最后一条数据重复查出来了。3.3 Servlet 路由设计一个 Servlet 处理所有请求Web 项目里 Servlet 的数量和路由设计直接影响代码可读性。这套资源里我只用一个 StudentServlet通过 action 参数分发到不同的业务方法。这种设计在期末作业里比每个功能写一个 Servlet 更合适理由是减少 web.xml 或注解的配置量而且代码结构一眼能看懂。如果你的项目用了 Servlet 3.0 的注解方式直接在类上写 WebServlet(/student) 就行不用动 web.xml。WebServlet(/student) public class StudentServlet extends HttpServlet { private StudentDao studentDao new StudentDaoImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (list.equals(action)) { list(req, resp); } else if (delete.equals(action)) { delete(req, resp); } else if (toEdit.equals(action)) { toEdit(req, resp); } else if (toAdd.equals(action)) { req.getRequestDispatcher(/add.jsp).forward(req, resp); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (add.equals(action)) { add(req, resp); } else if (update.equals(action)) { update(req, resp); } else if (search.equals(action)) { list(req, resp); } } private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String keyword req.getParameter(keyword); int pageNo 1; int pageSize 10; String pageNoStr req.getParameter(pageNo); if (pageNoStr ! null !pageNoStr.isEmpty()) { pageNo Integer.parseInt(pageNoStr); } ListStudent students studentDao.findByPage(pageNo, pageSize, keyword); int totalCount studentDao.count(keyword); int totalPages (int) Math.ceil(totalCount * 1.0 / pageSize); req.setAttribute(students, students); req.setAttribute(currentPage, pageNo); req.setAttribute(totalPages, totalPages); req.setAttribute(totalCount, totalCount); req.setAttribute(keyword, keyword); req.getRequestDispatcher(/list.jsp).forward(req, resp); } }doGet 处理查询、删除、跳转编辑页这些幂等操作doPost 处理新增和更新的写操作——这个分工理论上符合 HTTP 语义答辩老师问「为什么不用 doGet 做新增」你能答上来。pageNo 从 request 参数里取取不到就用默认值 1这个兜底逻辑必须有否则第一次访问没有 pageNo 参数直接空指针。totalPages 计算用的 Math.ceil 向上取整totalCount 是 21 的时候除以 10 得到 2.1Math.ceil 后是 3正好 3 页。这里有个隐藏问题要注意Integer.parseInt 如果收到非数字字符串会抛 NumberFormatException更稳的写法是捕获异常后回退到默认值你可以在自己的代码里加上。3.4 JSP 列表页EL 表达式 JSTL 渲染列表页是整套系统里用户直接看的界面用 EL 表达式和 JSTL 标签替换掉 Java 脚本片段。很多教材还在教 % % 这种写法但实际项目中早就不用了——脚本片段让 HTML 和 Java 代码混在一起浏览器渲染慢代码也丑。这套资源的 list.jsp 核心部分比较简洁。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title学生信息列表/title link relstylesheet hrefcss/style.css /head body h2学生信息列表/h2 form actionstudent methodget input typehidden nameaction valuelist input typetext namekeyword value${empty keyword ? : keyword} placeholder输入姓名/学号/班级 button typesubmit搜索/button /form table border1 cellpadding8 cellspacing0 tr thID/thth学号/thth姓名/thth性别/th th年龄/thth班级/thth电话/thth邮箱/thth操作/th /tr c:forEach items${students} vars tr td${s.id}/td td${s.studentNo}/td td${s.name}/td td${s.gender}/td td${s.age}/td td${s.className}/td td${s.phone}/td td${s.email}/td td a hrefstudent?actiontoEditid${s.id}编辑/a | a hrefstudent?actiondeleteid${s.id} onclickreturn confirm(确定删除该学生吗?)删除/a /td /tr /c:forEach /table div c:if test${currentPage 1} a hrefstudent?actionlistpageNo${currentPage - 1}keyword${empty keyword ? : keyword}上一页/a /c:if span第 ${currentPage} / ${totalPages} 页共 ${totalCount} 条/span c:if test${currentPage totalPages} a hrefstudent?actionlistpageNo${currentPage 1}keyword${empty keyword ? : keyword}下一页/a /c:if /div /body /html注意搜索表单里那个隐藏的 actionlist因为表单 method 是 get提交后 URL 会变成 student?actionlistkeywordxxx这样 Servlet 才能正确分发。keyword 回显用了 EL 的空值判断防止第一次加载时输入框里显示 null 字面量。分页链接里也拼了 keyword 参数这个细节非常关键——如果你在搜索状态下点了下一页keyword 没传过去下一页就变成不带条件查询了这是分页搜索最常见的 bug 之一。这套代码里每个关键位置都处理了这个状态保留你可以逐个检查确认。4. 分页查询与模糊搜索答辩时最容易被追问的两个点4.1 分页参数的传递链路从前端到 SQL 的完整流转分页是期末作业答辩时的必问题目你要把参数怎么走的链条背熟。前端 JSP 页面点击页码链接URL 里带的参数是 pageNo比如 student?actionlistpageNo2。Tomcat 收到请求后Servlet 容器把参数封装进 HttpServletRequest你的 Servlet 代码通过 req.getParameter(pageNo) 拿到字符串 2再 Integer.parseInt 转成 int。接着传给 Dao 层的 findByPage(pageNo, pageSize, keyword)最终在 SQL 里变成 LIMIT 10, 10。这个链路里最容易出问题的是类型转换。request.getParameter 永远返回 String如果前端传了 pageNoabcInteger.parseInt 直接抛 NumberFormatException页面 500。我一般会在解析参数的地方做个保护捕获 NumberFormatException 后回退到默认值 1。int pageNo 1; try { String pageNoStr req.getParameter(pageNo); if (pageNoStr ! null !pageNoStr.isEmpty()) { pageNo Integer.parseInt(pageNoStr); } } catch (NumberFormatException e) { pageNo 1; }补充一个隐藏场景如果用户手贱把 URL 改成 pageNo999总页码只有 5查出来结果是空列表。你可以在 Servlet 里对 pageNo 做边界校正如果大于 totalPages 就让它等于 totalPages。这套资源里加了这个处理建议你答辩的时候主动提这个边界条件老师会觉得你想得周到。4.2 模糊搜索与分页组合时的 SQL 拼接坑模糊搜索和分页分开写都简单组合起来就出现经典的「搜索后翻页失效」问题。现象是这样的你在搜索框里输入「张」点击搜索第一页结果正常点下一页结果变成了全表数据——因为下一页的链接里没带 keyword 参数。还有另一个翻车现场是 SQL 里 LIKE 的占位符顺序搞错导致 LIMIT 的起始行被当成 LIKE 参数绑定。正确思路是三个要素同时考虑SQL 层面where 条件追加到 LIMIT 之前参数绑定层面likePattern 参数排在 limit 参数前面前端链接层面keyword 必须显式拼在翻页 URL 里。三个地方只要有一个漏了查询就错。提示给你一个自检方法在 Dao 层代码里临时加一行 System.out.println(sql)把最终 SQL 打到控制台看一遍。如果输出的 SQL 是 SELECT ... WHERE name LIKE %张% ORDER BY id DESC LIMIT 10, 10说明拼接顺序是对的如果 WHERE 语句消失或者 LIMIT 值不对按顺序检查 keyword 参数传递。5. 避坑排查期末作业最常见的 6 个翻车现场5.1 Tomcat 启动后访问 404现象IDEA 里 Tomcat 启动日志显示 Server startup in XXXX ms但浏览器访问 http://localhost:8080/ 显示 404。原因八成是部署描述符里没有将项目根路径映射到默认页面或者 web.xml 里没有配置 welcome-file访问根路径时 Tomcat 找不到要显示的页面。解决在 web.xml 里加 welcome-file 配置welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list然后直接访问 http://localhost:8080/项目名/ 验证。5.2 启动时报 ClassNotFoundException: com.mysql.cj.jdbc.Driver现象Tomcat 启动后访问某个页面控制台报错 ClassNotFoundException指向 DBUtil 的静态代码块。原因MySQL 驱动 jar 包没有放到 WEB-INF/lib 目录下或者放到了 Tomcat 的 lib 目录但版本不匹配。IDEA 里常见的操作是把 jar 添加到项目依赖但没勾选 Deploy to Server运行的时候 Tomcat 没把 jar 带上。解决把 mysql-connector-java-8.0.x.jar 物理拷贝到项目的 web/WEB-INF/lib 目录下然后右键 Add as Library。启动前在 IDEA 的 Artifacts 设置里确认 WEB-INF/lib 下面能看到这个 jar。5.3 插入数据后中文全部变成问号现象从前端表单提交中文姓名页面上显示正常但 Navicat 打开数据库表看到的是 „???。原因三层字符集至少有一层出了问题。最直接的原因是 JDBC URL 没带 characterEncodingutf8 参数其次是页面本身 content type 没设置 UTF-8再一个是数据库表默认字符集不是 utf8mb4。解决先改 JDBC URL 确保 useUnicodetruecharacterEncodingutf8再检查 JSP 第一行的 page 指令里 contentTypetext/html;charsetUTF-8 有没有加最后执行 ALTER TABLE t_student CONVERT TO CHARACTER SET utf8mb4;。三个位置都检查一遍再插入一条测试数据基本能解决。5.4 端口占用Tomcat 启动直接崩现象IDEA 控制台报错 Port 8080 was already in useTomcat 起不来。原因之前某次运行没有正常关闭 Tomcat 进程或者有其他程序占了 8080 端口比如某个服务、Nginx、另一个 IDE 的调试端口。解决优先把残留的 java 进程按 PID kill 掉Windows 下命令是 netstat -ano | findstr 8080 然后 taskkill /PID 进程号 /F。如果担心影响其他项目也可以改 Tomcat 端口打开 conf/server.xml把 Connector port 改成 8081、8082 这种不冲突的端口。5.5 查询结果列表页样式全丢现象列表页的 CSS 样式出不来页面纯 HTML 裸奔。原因JSP 里引用 CSS 用的是相对路径 css/style.css但当前路径是 project/student?actionlist相对路径解析出来会变成 project/student/css/style.css自然找不到文件。解决统一用${pageContext.request.contextPath}/css/style.css绝对路径引用。5.6 update 操作后数据库数据没变现象编辑表单提交后提示成功但刷新列表数据还是旧值。原因检查 SQL 是否真的执行了 update 语句。常见的情况是 getParameter 拿到的 id 为空导致 where 条件不成立executeUpdate 影响行数是 0。解决在 update 方法里加一个影响行数的日志输出int rows ps.executeUpdate(); System.out.println(影响行数 rows); 如果 rows 为 0断点查看 id 参数有没有传对。6. 答辩前必做的最后验证四步走完再交作业整套代码跑通之后别急着交。我自己的习惯是每次期末作业输出前强制走一遍「从零部署验证」你要照着做一遍能拦住 90% 的现场翻车。第一步把数据库删掉重来DROP DATABASE student_db; 然后重新执行提供的 sql 脚本这一步确认脚本文件本身没问题而不是你电脑里残留的旧数据在撑着。第二步把项目在 IDEA 里 Clean 一次再 Rebuild确认没有编译错误然后把 target 目录里旧的 classes 清掉。第三步用「干净环境」模拟答辩现场——关掉你开发时一直开着的 MySQL 进程和 Tomcat重新启动 MySQL启动 Tomcat打开无痕浏览器访问 http://localhost:8080/项目名/因为无痕浏览器不会带旧缓存和 Cookie最接近老师机器上的真实情况。第四步过一遍核心功能用例我给你列个表格参考操作步骤预期结果常见失败点输入正确账号密码登录跳转到学生列表页数据库密码没改成 root 的本地密码新增一个学生只填必填字段列表页出现新数据StudentServlet 的 doPost 没配置 actionadd搜索「张」点击搜索列表只显示姓张的记录keyword 参数在翻页链接中丢失翻到第 2 页再翻回来搜索条件保留数据不重不漏LIMIT 起始值算错导致重复删除第 2 页的一条数据删除后页面停留在第 2 页接受到 record ID 用错导致删除失败做完这四步你心里基本有底了。篇幅有限只写了核心逻辑和最容易踩的坑剩下的完整代码包括登录模块、编辑弹窗、CSS 样式都在资源包里。说实话做这套作业的时候最折磨我的不是写代码而是反复调试那些明明配置对了却仍然报错的环境问题——有一次因为 MySQL 8 的 caching_sha2_password 认证插件和旧版 JDBC 驱动不兼容我整整折腾了一晚上。从那以后我每次搭 Java Web 环境都强制走一遍「版本匹配清单」JDK 版本、Tomcat 版本、MySQL 版本、JDBC 驱动版本四项逐一确认无误再写代码。这套资源里的环境和代码都是按这个清单验证过的希望帮到你。本文还有配套的精品资源点击获取
返回列表