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

JSP+Servlet学生信息管理系统实战:从部署到二次开发

JSP+Servlet学生信息管理系统实战:从部署到二次开发 ★ FEATURED ARTICLE
简介这份资源是面向Java Web初学者与课程实践者的学生信息管理系统完整项目以JSP与Servlet为核心结合MVC设计模式串联起动态页面展示、请求处理与数据模型适合用于理解Java Web开发流程、完成课程设计或作为入门实战案例。压缩包共576个文件约2.75MB包含html、css、png、js等前端页面与静态资源java与class源码及编译产物以及jar依赖、jsp页面、sql初始化脚本和json、xml等配置数据结构上覆盖了从界面到数据库的完整链路。已有999人学习下载。项目涉及JDBC增删改查、HTTP请求响应、会话管理、容器部署与异常日志等知识点读者可据此梳理Servlet与DAO分层写法、数据库建表脚本的导入方式以及前端表单验证思路是较为系统的Java Web入门参考。1. 接手一个 JSP Servlet 学生信息管理系统先看清它到底能干什么很多 Java Web 初学者在找练手项目时会翻到一堆“学生信息管理系统”的源码包下载下来打开一看满屏的.jsp和Servlet不知道从哪下手。我这次拆的就是这样一个典型项目用原生 JSP Servlet 实现的学生信息管理系统没有 Spring、没有 MyBatis纯 Servlet 容器跑起来数据库用 JDBC 直连。它适合两类人一是刚学完 Java Web 基础、想找一个能跑通完整 CRUD 流程的练手项目二是想理解“不用框架时一个请求到底怎么从浏览器走到数据库再返回”的开发者。项目核心就是学生信息的增删改查、分页列表、登录鉴权这几块麻雀虽小但 MVC 分层、过滤器、会话管理这些关键点一个不少拿来当 Java Web 的“解剖样本”正合适。2. 项目结构与技术栈没有框架的 MVC 是怎么落地的2.1 目录结构决定了一切拿到源码包第一件事不是急着跑而是先看目录。这个项目的结构非常“教科书式”典型 Maven Web 工程或者 Eclipse Dynamic Web Project 的布局StudentMS/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/example/sms/ │ │ ├── controller/ # Servlet 层 │ │ ├── service/ # 业务逻辑层 │ │ ├── dao/ # 数据访问层 │ │ ├── entity/ # 实体类 │ │ └── util/ # JDBC 工具类 │ ├── resources/ │ │ └── db.properties # 数据库配置 │ └── webapp/ │ ├── WEB-INF/ │ │ ├── web.xml # Servlet 映射、过滤器配置 │ │ └── lib/ # 依赖 jar │ ├── jsp/ # 视图页面 │ │ ├── login.jsp │ │ ├── student_list.jsp │ │ └── student_edit.jsp │ └── index.jsp └── pom.xml这个分层不是摆设。controller里每个 Servlet 对应一组操作service做业务判断比如学号是否重复dao只负责拼 SQL 和执行entity是纯 POJO。理解了这个结构后面改功能就知道该动哪一层不会把 SQL 写到 JSP 里。2.2 技术栈清单与选型逻辑层次技术说明视图JSP JSTL用c:forEach渲染列表避免脚本片段控制Servlet 3.x注解或 web.xml 映射业务普通 Java 类无框架手动 new持久JDBC PreparedStatement防 SQL 注入数据库MySQL驱动 mysql-connector-java会话HttpSession登录状态保持过滤Filter编码统一、登录拦截为什么不用框架因为这个项目的价值就在于“裸写”。你在这里能看清request.getParameter()拿到的是什么、response.sendRedirect()和forward()的区别、连接池不配时每次DriverManager.getConnection()有多慢。这些细节在 Spring 里被封装掉了但排查问题时恰恰需要这些底层认知。2.3 数据库表设计学生表通常长这样字段不多但够用CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender VARCHAR(4) DEFAULT 男, age INT, major VARCHAR(50), phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );user表存登录账号字段是id、username、password。注意sno加了唯一约束这是后面做“学号重复校验”的数据库兜底。很多新手只在 Service 层查一次就插入并发下会翻车唯一索引才是最后一道防线。3. 核心链路拆解一个学生列表请求的完整旅程3.1 从浏览器到 Servlet映射与参数接收用户在地址栏输入/student/list?page1容器根据web.xml或WebServlet注解找到对应的 Servlet。这个项目的列表 Servlet 核心代码如下WebServlet(/student/list) public class StudentListServlet extends HttpServlet { private StudentService studentService new StudentServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 接收分页参数做默认值兜底 String pageStr req.getParameter(page); int page (pageStr null || pageStr.isEmpty()) ? 1 : Integer.parseInt(pageStr); int pageSize 10; // 2. 调用 Service 拿分页数据 PageResultStudent result studentService.findByPage(page, pageSize); // 3. 数据放入 request 域转发到 JSP req.setAttribute(pageResult, result); req.getRequestDispatcher(/jsp/student_list.jsp).forward(req, resp); } }这里有几个关键点。第一page参数必须做空值和非法值兜底否则用户手动改 URL 传个abc进来Integer.parseInt直接抛NumberFormatException页面就是 500。第二用forward而不是redirect因为要把request域里的数据带到 JSP重定向会丢数据。第三Servlet 里不写任何 SQL 和业务判断只做参数接收和视图调度这是分层的底线。3.2 Service 层分页逻辑与业务校验Service 层是很多人容易写薄的地方。分页不只是“查第几页”还要算总页数、处理页码越界public class StudentServiceImpl implements StudentService { private StudentDao studentDao new StudentDaoImpl(); Override public PageResultStudent findByPage(int page, int pageSize) { // 总记录数 int total studentDao.count(); // 总页数向上取整 int totalPages (total pageSize - 1) / pageSize; // 页码越界修正 if (page 1) page 1; if (page totalPages totalPages 0) page totalPages; int offset (page - 1) * pageSize; ListStudent list studentDao.findByPage(offset, pageSize); PageResultStudent pr new PageResult(); pr.setList(list); pr.setPage(page); pr.setTotalPages(totalPages); pr.setTotal(total); return pr; } }(total pageSize - 1) / pageSize这个向上取整的写法比Math.ceil更稳因为整数除法不会引入浮点误差。页码越界修正是血泪经验用户在第 1 页删光了最后一页的数据再点“下一页”就会查到空列表不修正的话页面显示“第 2 页共 1 页”很别扭。3.3 DAO 层PreparedStatement 与结果集映射DAO 层只做一件事执行 SQL 并映射结果。分页查询用LIMIT offset, sizepublic class StudentDaoImpl implements StudentDao { Override public ListStudent findByPage(int offset, int pageSize) { String sql SELECT id, sno, name, gender, age, major, phone FROM student ORDER BY id DESC LIMIT ?, ?; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, offset); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setSno(rs.getString(sno)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setAge(rs.getInt(age)); s.setMajor(rs.getString(major)); s.setPhone(rs.getString(phone)); list.add(s); } } } catch (SQLException e) { throw new RuntimeException(分页查询学生失败, e); } return list; } }用 try-with-resources 确保Connection、PreparedStatement、ResultSet都能关闭这是不配连接池时最容易漏的地方漏几次数据库连接数就满了。LIMIT ?, ?用占位符而不是字符串拼接既防注入也避免类型问题。结果集映射手动写虽然啰嗦但字段名和实体属性一一对应改字段时不容易漏。3.4 JSP 视图JSTL 渲染与分页条JSP 里不要写% %脚本片段用 JSTL 和 EL% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table c:forEach items${pageResult.list} vars tr td${s.sno}/td td${s.name}/td td${s.gender}/td td${s.major}/td td a href${pageContext.request.contextPath}/student/edit?id${s.id}编辑/a a hrefjavascript:del(${s.id})删除/a /td /tr /c:forEach /table div classpager 第 ${pageResult.page} / ${pageResult.totalPages} 页 c:if test${pageResult.page 1} a href?page${pageResult.page - 1}上一页/a /c:if c:if test${pageResult.page pageResult.totalPages} a href?page${pageResult.page 1}下一页/a /c:if /div${pageContext.request.contextPath}不能省否则部署到非根路径比如/sms时链接全 404。分页条的“上一页/下一页”用c:if控制边界第一页不显示上一页最后一页不显示下一页。4. 跑起来环境准备、建库与部署的完整步骤4.1 环境与依赖确认这个项目对版本不挑但组合要对JDK 8 或 11Tomcat 8.5 或 9Tomcat 10 的包名从javax.servlet变成jakarta.servlet直接跑会报ClassNotFoundExceptionMySQL 5.7 或 8.0Maven 3.6先看pom.xml里的依赖核心就三个javax.servlet-apiscope 是 provided、jstl、mysql-connector-java。如果项目是 Eclipse 工程没有 pom就去WEB-INF/lib下确认 jar 在不在。4.2 建库建表与配置修改按第 2 章的建表语句执行再插一条管理员账号INSERT INTO user (username, password) VALUES (admin, 123456);然后改src/main/resources/db.propertiesjdbc.urljdbc:mysql://localhost:3306/student_ms?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码 jdbc.drivercom.mysql.cj.jdbc.DriverMySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver5.x 是com.mysql.jdbc.Driver写错就是ClassNotFoundException。serverTimezone不配MySQL 8 下时间字段可能报时区错误。4.3 部署与首次访问Maven 项目直接mvn clean package打 war 包丢进 Tomcat 的webapps目录启动后访问http://localhost:8080/StudentMS/。Eclipse 工程就右键 Run on Server。首次访问会被登录过滤器拦到login.jsp用admin/123456登录后进入列表页。如果启动报NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config说明 JSTL jar 没进WEB-INF/lib或者 Maven 依赖 scope 写成了 provided。这类问题看启动日志第一行异常就能定位别急着改代码。5. 避坑与排查那些让项目跑不起来的常见问题5.1 中文乱码POST 和 GET 要分开治现象表单提交的中文姓名存进数据库变成???。原因Tomcat 默认按 ISO-8859-1 解码请求体。解决在web.xml配一个编码过滤器或者每个 Servlet 开头req.setCharacterEncoding(UTF-8)。GET 请求的参数编码还跟 Tomcat 的server.xml里Connector的URIEncoding有关Tomcat 8 以后默认 UTF-8老版本要手动加。我一般直接上过滤器一劳永逸WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }5.2 登录后仍然跳回登录页过滤器路径没排除现象登录成功却一直循环跳登录页。原因登录过滤器拦截了/*把login.jsp和登录 Servlet 也拦了。解决在过滤器里放行登录相关路径和静态资源String uri ((HttpServletRequest) req).getRequestURI(); if (uri.contains(/login) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, resp); return; }5.3 删除后列表页码错乱现象在第 3 页删掉最后一条记录页面空白。原因删除后重定向回原页码但总页数已经减一。解决删除成功后重定向到列表时让列表 Servlet 自己做页码越界修正第 3.2 节的逻辑或者删除后统一回第 1 页。前者体验更好。5.4 数据库连接耗尽现象用一段时间后报Too many connections。原因DAO 里Connection没关或者异常路径下没走到close()。解决全部改 try-with-resources有条件的话引入 Druid 或 HikariCP 连接池把DBUtil.getConnection()换成从池里取。这个项目原生写法每次新建连接压测几十个并发就顶不住这是它作为教学项目的局限二次开发时优先补这块。5.5 Tomcat 10 直接跑报包名错误现象java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet。原因Tomcat 10 起 Servlet API 包名迁移到jakarta.servlet。解决要么换 Tomcat 9要么把源码里所有javax.servlet改成jakarta.servlet并换依赖坐标。教学项目建议直接用 Tomcat 9省得改一堆 import。6. 二次开发从教学项目到能用的系统我会先动这三处这个项目跑通之后大多数人会想加点东西。我的建议是按“先稳后扩”的顺序改别一上来就堆功能。第一处是连接池。原生DriverManager每次请求建连接是性能瓶颈也是稳定性隐患。引入 HikariCP 只需要改DBUtilpublic class DBUtil { private static final HikariDataSource DS; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(Props.get(jdbc.url)); config.setUsername(Props.get(jdbc.username)); config.setPassword(Props.get(jdbc.password)); config.setMaximumPoolSize(10); config.setMinimumIdle(2); DS new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return DS.getConnection(); } }改完之后 DAO 层一行不用动因为对外暴露的还是getConnection()。maximumPoolSize按数据库承受能力设教学环境 10 足够。第二处是密码存储。明文存密码是教学项目的通病改成 BCrypt 哈希登录时用BCrypt.checkpw()比对。这一步改动小但能让你理解“为什么不能明文存”。第三处是搜索与导出。列表页加一个按姓名/学号模糊查询的输入框DAO 里拼WHERE name LIKE ? OR sno LIKE ?注意%要拼在参数值里而不是 SQL 里。导出用 Apache POI 写 Excel或者简单点用 CSV一个 Servlet 输出流就能搞定。验证改动是否成功我习惯先跑一遍“登录 → 新增 → 搜索 → 编辑 → 删除 → 翻页”的完整链路再看 Tomcat 日志有没有异常堆栈。别只测正常路径手动传个page-1、idabc进去看兜底逻辑扛不扛得住。从那以后我每次接手这类裸 Servlet 项目都强制先把编码过滤器和连接池这两件事做掉再谈功能否则后面每加一个模块都在给前面的坑还债。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站