ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JavaWeb学生信息管理系统实战:Servlet+JSP+Bootstrap+MySQL全解析

JavaWeb学生信息管理系统实战:Servlet+JSP+Bootstrap+MySQL全解析 简介面向Java课程设计与期末大作业场景的完整学生信息管理系统源码包基于ServletJSPBootstrapMysql技术栈实现覆盖学生信息管理、教师管理、登录注册、验证码发送等常见教学模块。源码已在本地编译并成功运行评审分达到98分难度适中内容经助教审定适合初学者借鉴或直接作为课程设计成果。压缩包共82个文件大小约12.03MB包括20个Java源文件、15个JSP视图页面、21个编译后的class文件以及CSS/JS前端样式脚本、数据库初始化SQL脚本、jar依赖包和项目说明文档目录按servlet、dao、vo、web等分层组织结构清晰便于按模块深入研读。资源一并提供使用说明与README笔记帮助快速完成环境配置、数据库导入和项目部署目前已有162人学习下载是完成期末大作业或复习Java Web开发的实用参考资料。1. JavaServletJSPBootstrapMySQL这套学生信息管理系统的组合拳怎么打每年的 JavaWeb 课程设计和毕业设计学生信息管理系统都是出现频率最高的题目而 JavaServletJSPBootstrapMySQL 又是这类项目里最经典的选型。它不比 Spring Boot 全家桶那样上来就甩一堆注解让人摸不着头脑也不像纯 JSPJDBC 那样把业务代码和页面展示糊成一锅粥。Servlet 管请求分发JSP 管页面渲染Bootstrap 把界面拉到能见人的水平MySQL 负责数据落盘每一层都有明确的位置改起来知道去哪改。这套系统适合两类人。一类是正在准备 JavaWeb 课设或毕设的学生需要一条从需求分析、数据库设计到编码部署都能完整跑通的链路另一类是刚学完 Java 基础、想搞清楚 Servlet 和 JSP 到底怎么配合的入门开发者——把这一套吃透再去看 SSM 或 Spring Boot 里的 MVC 设计思路会顺畅很多。这篇文章就围绕这套系统从数据库建模讲到 Servlet 核心代码再到 Bootstrap 页面整合和部署上线中间穿插这几年带项目积累下来的踩坑记录。2. 项目骨架与数据库设计建表之前先把目录结构想清楚2.1 开发环境选型与版本匹配这套组合对环境的要求不高但版本搭配有讲究。JDK 1.8 依然是兼容性最好的选择Tomcat 8.5 或 9.0 对 Servlet 3.1/4.0 的支持很成熟和网上能找到的绝大多数源码包都能对上号。MySQL 选 5.7 还是 8.0 取决于你本地的安装习惯——5.7 的驱动类是 com.mysql.jdbc.Driver8.0 换成了 com.mysql.cj.jdbc.Driver连接 URL 里的参数也不一样。我一般建议直接用 MySQL 8.0因为现在新装的机器基本不会再去找 5.7 的老安装包而且 8.0 的驱动向后兼容 5.7 的协议只是要注意时区参数。除了这三件套还需要一个 IDEA 或 Eclipse以及 Maven。Maven 不是必须的很多课设源码用的是手工导 jar 的方式但如果你的源码包里有 pom.xml用 Maven 管理依赖会省掉不少手动找 jar 的麻烦。版本匹配这件事容易被忽略但它决定了你八成以上的报错来源。JDK 版本太高比如用 JDK 17 跑老项目Tomcat 8.5 可能直接起不来MySQL 驱动版本和数据库版本不一致连接阶段就会出现各种花式报错。如果整套环境是第一次搭按照 JDK 8 Tomcat 9 MySQL 8.0 这个组合来是踩坑最少的路径。2.2 数据库表设计学生表、用户表、班级表怎么拆学生信息管理系统听起来简单但表设计得合理不合理直接决定后面 Servlet 代码写起来顺不顺手。最少需要三张表用户表 t_user 管登录学生表 t_student 存核心信息班级表 t_class 做归属关系。有些源码把班级信息直接冗余到学生表里比如用一个 class_name 字段——这种做法在小项目里可以接受但如果你要在毕设答辩里讲数据库设计合理性拆成两张表、用外键关联会更有说法。先看用户在 MySQL Workbench 里执行建库的脚本。用 Workbench 的 SQL Editor 贴进去跑就行注意选对数据库实例别跑到系统库里去。CREATE DATABASE IF NOT EXISTS student_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_manager; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5加密后的密码, real_name VARCHAR(20) COMMENT 真实姓名, role TINYINT DEFAULT 1 COMMENT 1管理员2教师, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_class ( id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(50) NOT NULL UNIQUE, grade_year VARCHAR(10) COMMENT 入学年份, head_teacher VARCHAR(20) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(20) NOT NULL, gender CHAR(2), birthday DATE, phone VARCHAR(11), email VARCHAR(50), class_id INT, address VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES t_class(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本里有两个细节值得注意。第一是 utf8mb4 和 utf8 的区别——utf8 在 MySQL 5.7 及以下存不了生僻字和 emoji学生名字里万一有个冷僻字入库直接报错所以统一用 utf8mb4。第二是 password 字段的长度MD5 加密后是 32 位十六进制字符串所以 varchar(64) 留了余量将来如果升级成 SHA-256 也放得下。学号字段设置 UNIQUE 约束是必须的这是学生维度最自然的唯一标识。2.3 初始化数据与连接池参数建完表之后需要灌入初始数据。管理员账号必须有不然系统部署完连门都进不去。密码这里用了 MD5 加密明文是 admin123你可以用 MySQL 自带的 MD5() 函数在插入时直接计算。INSERT INTO t_user (username, password, real_name, role) VALUES (admin, MD5(admin123), 系统管理员, 1); INSERT INTO t_class (class_name, grade_year, head_teacher) VALUES (计算机2101班, 2021, 王老师), (软件2102班, 2021, 李老师); INSERT INTO t_student (student_no, name, gender, birthday, phone, email, class_id, address) VALUES (2021001, 张伟, 男, 2003-05-12, 13800138001, zhangweiexample.com, 1, 北京市海淀区), (2021002, 李娜, 女, 2003-08-23, 13800138002, linaexample.com, 2, 上海市浦东新区);一个常见的做法是把这段 SQL 存成 init.sql 放在项目根目录的 sql 文件夹里这样换机器时不用重新在 Workbench 里复制粘贴。执行时有个小坑如果你之前手动执行过部分语句再次执行会因表已存在而报错建议在 Workbench 里选中整个文件一次性执行或者先跑 DROP TABLE IF EXISTS 语句。连接数据库的 URL 参数同样关键。MySQL 8.0 的 JDBC 连接串长这样jdbc:mysql://localhost:3306/student_manager?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8。serverTimezone 不加连接时会直接报 The server time zone value Öйú±ê׼ʱ¼ä is unrecognizeduseSSLfalse 是为了避免本地开发时 SSL 握手带来的额外警告信息。这些参数不理解也没关系记住照抄就能少踩一半坑。3. ServletJSP 核心业务代码从登录到学生列表的完整链路3.1 MVC 分层Model、DAO、Servlet 各自管什么许多学生拿到源码后的第一反应是打开每个 Java 文件逐个看这是效率最低的方式。先明确这套项目的分层逻辑比读任何一行代码都重要。经典的三层结构是JavaBean 承载数据Model 层DAO 类封装数据库访问数据访问层Servlet 接收请求、调用 DAO、转发到 JSP控制层。JSP 只负责展示里面不应该出现大段的 Java 代码更不应该出现 JDBC 操作。怎么判断一个项目的分层是否合格看两点第一JSP 里有没有出现 % 开头的脚本片段如果是纯 JSTL 和 EL 表达式说明控制层做得好第二Servlet 里有没有 SQL 语句如果直接写 JDBC说明 DAO 层被架空了。合格的划分是 Servlet 里只做三件事——解析参数、调用 DAO、往 request 里塞数据然后 forward。把这条主线抓住再看任何源码包都能快速定位到关键代码。3.2 从登录功能入手Servlet 接收请求、校验用户的完整代码登录功能是学生管理系统的入口也是最能把 Servlet 生命周期讲清楚的一段代码。用户在 Bootstrap 页面输入用户名密码表单 POST 到 LoginServletServlet 从请求里取出参数调用 UserDao 查库比对成功就写入 Session 并重定向到学生列表页失败则回登录页并带一个错误提示。WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); if (username null || password null || username.trim().isEmpty()) { request.setAttribute(error, 用户名和密码不能为空); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } User user userDao.findUserByUsernameAndPassword( username.trim(), MD5Util.md5(password)); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); response.sendRedirect(request.getContextPath() /student/list); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这里面有四个点要讲清楚。request.setCharacterEncoding(UTF-8) 必须放在取参数之前否则 POST 过来的中文参数会乱码密码在 Servlet 层做 MD5 加密再传给 DAO数据库里存的是密文这样即使 SQL 被拖库用户明文密码也不会直接泄露Session 里存入 loginUser 对象后续每个页面的权限拦截都通过判断这个对象是否为空来实现最后重定向用的是 response.sendRedirect 带上 contextPath 前缀直接写 /student/list 在部署到根路径之外的上下文时会 404。前端的登录表单 action 要写login而不是/login否则路径会错乱。3.3 学生列表页Servlet 查询数据 JSTL 渲染的配合登录成功后进入学生列表页这条链路完整地展示了 Servlet 和 JSP 的协作方式。StudentListServlet 从 DAO 拿到学生集合塞进 request 作用域然后 forward 到 student_list.jsp。JSP 里用 JSTL 的 c:forEach 遍历集合并渲染成表格行配合 Bootstrap 的 table 样式就能得到一张还不错的列表页。WebServlet(/student/list) public class StudentListServlet extends HttpServlet { private StudentDao studentDao new StudentDao(); private static final int PAGE_SIZE 5; Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(false); if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } int page 1; String pageStr request.getParameter(page); if (pageStr ! null !pageStr.isEmpty()) { page Integer.parseInt(pageStr); } int totalCount studentDao.countAll(); int totalPages (int) Math.ceil((double) totalCount / PAGE_SIZE); if (page totalPages totalPages 0) page totalPages; ListStudent studentList studentDao.findByPage(page, PAGE_SIZE); request.setAttribute(studentList, studentList); request.setAttribute(currentPage, page); request.setAttribute(totalPages, totalPages); request.setAttribute(totalCount, totalCount); request.getRequestDispatcher(/jsp/student_list.jsp).forward(request, response); } }这里的分页逻辑是手写的 LIMIT 方式page 从 1 开始计算出偏移量后传给 DAO。需要注意对 page 参数的异常处理——用户手动在 URL 上输入 pageabc 或 page-1 会导致 Integer.parseInt 抛异常严谨的做法是加 try-catch 并回退到第一页上面代码里只做了空字符串判断实际部署时建议补上。Session 拦截的写法用的是 request.getSession(false)如果当前没有 Session 就返回 null不会像 getSession() 那样自动创建一个新 Session避免未登录用户访问页面时留下垃圾 Session。JSP 端的代码是这套系统的核心展示层也是区分会写 JSP和会写 JavaWeb的分水岭% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html langzh head meta charsetUTF-8 title学生列表 - 学生信息管理系统/title link relstylesheet href${pageContext.request.contextPath}/css/bootstrap.min.css /head body div classcontainer mt-4 h2 classmb-3学生信息列表/h2 table classtable table-bordered table-hover thead classthead-dark tr th序号/th th学号/th th姓名/th th性别/th th班级/th th手机号/th th操作/th /tr /thead tbody c:forEach items${studentList} vars varStatusst tr td${st.count}/td td${s.studentNo}/td td${s.name}/td td${s.gender}/td td${s.className}/td td${s.phone}/td td a href${pageContext.request.contextPath}/student/edit?id${s.id} classbtn btn-sm btn-primary编辑/a a href${pageContext.request.contextPath}/student/delete?id${s.id} classbtn btn-sm btn-danger onclickreturn confirm(确定删除该学生)删除/a /td /tr /c:forEach /tbody /table /div /body /htmlEL 表达式 ${s.studentNo} 实际上调用的是 Student 类里的 getStudentNo() 方法所以 JavaBean 的属性命名必须规范。这里有个容易翻车的地方——数据库字段是 student_noJava 属性是 studentNo映射关系要在 DAO 里手动处理用别名或者逐一 set。${pageContext.request.contextPath} 这个 EL 表达式会自动拼上项目的部署路径比如 http://localhost:8080/student_manager/这样页面里的 CSS 引用和链接无论部署到哪里都不会断。3.4 新增和修改学生POST 请求处理与参数封装列表页能看能删还不够得能新增和修改才能完整覆盖增删改查。新增和修改的核心逻辑很相似都是从表单拿到参数、封装成 Student 对象、调用 DAO 的 insert 或 update 方法。但有一个细节一个 Servlet 同时处理 GET 和 POST 时doGet 负责跳转页面doPost 负责处理提交这个约定能让代码结构更清晰。WebServlet(/student/add) public class StudentAddServlet extends HttpServlet { private StudentDao studentDao new StudentDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher(/jsp/student_add.jsp).forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String studentNo request.getParameter(studentNo); String name request.getParameter(name); String gender request.getParameter(gender); String birthday request.getParameter(birthday); String phone request.getParameter(phone); Integer classId Integer.parseInt(request.getParameter(classId)); Student student new Student(); student.setStudentNo(studentNo); student.setName(name); student.setGender(gender); student.setBirthday(DateUtil.parse(birthday)); student.setPhone(phone); student.setClassId(classId); studentDao.insert(student); response.sendRedirect(request.getContextPath() /student/list); } }这段代码在课设项目里很典型但它暴露出的问题同样典型没有对 studentNo 做重复校验数据库里的 UNIQUE 约束会抛 DuplicateKeyException页面会直接 500。正确做法是在 DAO 里加一个 findByStudentNo 方法插入前先查一次存在就返回错误提示而不走数据库插入。Integer.parseInt(request.getParameter(classId)) 如果拿到空串或非数字也会抛异常参数校验不能省。birthday 这类日期字段用字符串直接接收再在 DAO 的 SQL 里通过 PreparedStatement 的 setDate 方法做转换避免手工拼 SQL 带来的注入和格式问题。4. Bootstrap 前端整合把原生 JSP 页面改造成像样的管理后台4.1 Bootstrap 的正确引入方式与本地化Bootstrap 的引入方式有 CDN 和本地文件两种。很多源码包用的是 CDN 链接比如 cdn.jsdelivr.net 上的 bootstrap.min.css这在本地开发时如果恰好断网整个页面就是裸奔状态。我在做这类课设项目时习惯把 Bootstrap 3.4.1 或 4.6 的 css 和 js 文件下载到项目 webapp/css 和 webapp/js 目录下引入路径用 ${pageContext.request.contextPath} 拼接。引入方式直接决定了页面显示正不正常!-- 推荐本地文件方式离线也能跑 -- link relstylesheet href${pageContext.request.contextPath}/css/bootstrap.min.css script src${pageContext.request.contextPath}/js/jquery-3.5.1.min.js/script script src${pageContext.request.contextPath}/js/bootstrap.min.js/script !-- 不推荐CDN 方式部署到内网环境直接白屏 -- link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/bootstrap4.6.2/dist/css/bootstrap.min.css这里提一下 Bootstrap 3 和 4 的区别。Bootstrap 3 的栅格类是 col-md-44 变成了 col-md-4 但新增了 col-xxl 系列Bootstrap 3 的按钮类是 btn btn-primary这个在 4 里保留但 3 的 input-group 结构和 4 不兼容。如果你的源码包用的是 Bootstrap 4写 CSS 选择器或排错时脑子里要有4 使用了 Flexbox 布局这根弦不要拿 3 的习惯去套。jQuery 必须在 bootstrap.min.js 之前引入Bootstrap 4 的很多组件依赖 jQuery顺序不对控制台会直接报 jQuery is not defined。4.2 表格美化与分页Bootstrap 组件的核心用法学生列表页如果只有一张干巴巴的 table那和十年前用 Dreamweaver 做出来的页面没什么区别。Bootstrap 的价值在于用最少的代码量实现像样的视觉层次。table 加 table-bordered 和 table-hover 就能得到带边线和悬停高亮的表格thead-dark 让表头变成黑底白字和内容行形成对比。分页组件同样简单Bootstrap 4 的 pagination 类和 JSP 端用 JSTL 计算出的页码配合nav aria-label学生列表分页 ul classpagination justify-content-center li classpage-item ${currentPage 1 ? disabled : } a classpage-link href${pageContext.request.contextPath}/student/list?page${currentPage - 1}上一页/a /li li classpage-item disabled span classpage-link第 ${currentPage} / ${totalPages} 页共 ${totalCount} 条/span /li li classpage-item ${currentPage totalPages ? disabled : } a classpage-link href${pageContext.request.contextPath}/student/list?page${currentPage 1}下一页/a /li /ul /nav这段代码里用了 EL 条件表达式 ${currentPage 1 ? disabled : } 动态控制上一页按钮的禁用状态比在 Java 代码里拼 HTML 字符串干净得多。分页链接直接写在 JSP 里href 指向 StudentListServlet 的同一个 URLServlet 通过 page 参数决定返回哪一页的数据。这里的分页只能做到翻页没有页码数字序列如果想加页码用 c:forEach 的 begin 和 end 属性从 1 循环到 totalPages 即可关键是要给当前页的 li 加 active 类。4.3 表单弹窗与前端校验模态框改造新增页面很多课设源码的新增学生页面是一个独立的 student_add.jsp每次新增都要跳转一次页面体验很割裂。用 Bootstrap 模态框可以把新增表单直接嵌入列表页点新增按钮弹窗提交后重新查询列表体验和 ajax 几乎没差别但开发和理解成本低一个数量级。模态框的结构分为三部分modal-header 放标题和关闭按钮modal-body 放表单modal-footer 放操作按钮。表单字段的 name 属性必须和 Servlet 里 request.getParameter 的参数名严格对应这是前后端唯一的契约。!-- 新增按钮 -- button typebutton classbtn btn-success style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表