简介:这是一套面向高校计算机相关专业学生的Java Web课程设计资源,采用Servlet+JSP+Bootstrap+Mysql技术栈实现学生信息管理系统,适合作为期末大作业、课程设计或Java入门练手项目。压缩包共82个文件,约12.03MB,包含20个java源文件、21个class编译文件、15个jsp页面、6个css与5个js前端资源,以及数据库脚本、jar依赖、图片素材和说明文档,覆盖从后端逻辑到前端界面的完整结构。系统按servlet、dao、vo、web等分层组织,涵盖学生、教师、验证码、注册登录、找回密码等模块,源码经本地编译可运行,评审分达98分,内容经助教老师审定,难度适中。目前已有163人学习下载,适合需要快速搭建可运行项目、理解MVC分层与数据库交互的读者参考使用。
1. 从一份学生信息管理系统源码说起:Servlet+JSP 这套老架构为什么还值得动手跑一遍
很多人第一次接触 JavaWeb 项目实战,拿到的就是一份学生信息管理系统源码,技术栈写着 Java+Servlet+JSP+Bootstrap+MySQL。第一反应往往是:这都什么年代了,Spring Boot 满天飞,还看 Servlet?但真把这份源码跑起来、改一遍、再自己重写一遍,你会发现它反而是理解 JavaWeb 请求链路最省事的路径。Servlet 负责接收请求、调业务、转发数据,JSP 负责把数据渲染成页面,Bootstrap 负责让页面在浏览器里不难看,MySQL 负责把学生、班级、成绩这些数据存住。四样东西各管一段,边界清楚,没有自动配置的黑匣子。这篇文章就围绕这套组合,把环境搭建、数据库设计、Servlet 与 JSP 的配合、Bootstrap 页面改造、以及部署排错讲透,适合刚学完 Java 基础、想找一个完整案例练手的人,也适合工作几年但一直用框架、没亲手写过 Servlet 的人回头补课。
2. 环境与数据库:把 MySQL 和 Tomcat 先跑通
2.1 JDK、Tomcat、MySQL 的版本选择与安装顺序
这套技术栈对版本不挑剔,但版本选错会带来一堆莫名其妙的报错。我的建议是:JDK 用 8 或 11,Tomcat 用 8.5 或 9,MySQL 用 5.7 或 8.0。JDK 8 和 Tomcat 8.5 是当年这套源码最常跑的搭配,兼容性最好。MySQL 8.0 在 Windows 10 上安装时记得选Use Legacy Authentication Method,否则老版本 JDBC 驱动连不上。
安装顺序上,先装 JDK,配好JAVA_HOME和Path;再装 MySQL,记住 root 密码;最后解压 Tomcat,不需要安装,解压后在bin目录下双击startup.bat能弹出窗口且不闪退,就说明 Tomcat 正常。验证 JDK 用java -version,验证 MySQL 用mysql -u root -p能进命令行。
# 验证 JDK java -version # 输出类似 java version "1.8.0_xxx" # 验证 MySQL 服务是否启动(Windows) net start | findstr MySQL # 启动 Tomcat(Windows) cd D:\apache-tomcat-8.5.xx\bin startup.bat这几条命令的作用分别是确认 Java 运行时、确认 MySQL 服务在跑、启动 Tomcat。参数上没什么可调的,唯一要注意的是 Tomcat 默认端口 8080,如果被占用,去conf/server.xml里改Connector port。
2.2 学生信息管理系统的库表设计:三张表就够用
学生信息管理系统的核心数据无非是学生、班级、用户(登录用)。不要一上来就设计十几张表,先把最小可用模型跑通。下面是我常用的三张表结构。
-- 学生表 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, class_id INT, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 班级表 CREATE TABLE class ( id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(50) NOT NULL, teacher VARCHAR(50) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 用户表(登录) CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(20) DEFAULT 'admin' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字段说明:sno加唯一索引防止学号重复,这是学生管理系统里最常见的脏数据来源;class_id是外键逻辑关联,不强制加外键约束,方便后期删班级不报错;password留 64 位是为了以后换成 SHA-256 存储。字符集统一utf8mb4,避免姓名里有生僻字或 emoji 时插入失败。
提示:MySQL 8.0 默认字符集已经是 utf8mb4,但 5.7 需要手动指定,建库时写
CREATE DATABASE student_db DEFAULT CHARSET utf8mb4;。
2.3 JDBC 连接配置与驱动版本对应关系
Servlet 里访问 MySQL 靠 JDBC。驱动包mysql-connector-java的版本要和 MySQL 服务端匹配:MySQL 5.7 用 5.1.x 驱动,MySQL 8.0 用 8.0.x 驱动。驱动类名也不一样,5.1.x 是com.mysql.jdbc.Driver,8.0.x 是com.mysql.cj.jdbc.Driver。URL 上 8.0 要加时区和 SSL 参数。
// DBUtil.java 连接工具类 public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/student_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); // 8.0 驱动 } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }serverTimezone=Asia/Shanghai不加会报时区错误,这是 MySQL 8.0 最常见的翻车点。useSSL=false是关掉 SSL 警告,本地开发无所谓。驱动 jar 包放到WEB-INF/lib下,不要只放在项目 Build Path 里,否则部署到 Tomcat 会报 ClassNotFound。
3. Servlet 与 JSP 分工:请求怎么进来,页面怎么出去
3.1 一个学生列表功能的最小 Servlet 实现
Servlet 的职责很明确:接参数、调数据库、把结果塞进 request、转发给 JSP。下面是一个查询学生列表的 Servlet。
@WebServlet("/student/list") public class StudentListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { List<Student> list = new ArrayList<>(); String sql = "SELECT s.*, c.class_name FROM student s " + "LEFT JOIN class c ON s.class_id = c.id ORDER BY s.id DESC"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Student stu = new Student(); stu.setId(rs.getInt("id")); stu.setSno(rs.getString("sno")); stu.setName(rs.getString("name")); stu.setGender(rs.getString("gender")); stu.setClassName(rs.getString("class_name")); list.add(stu); } } catch (SQLException e) { e.printStackTrace(); } req.setAttribute("studentList", list); req.getRequestDispatcher("/student/list.jsp").forward(req, resp); } }逻辑说明:用LEFT JOIN把班级名一起查出来,避免 JSP 里再查一次数据库。try-with-resources自动关闭连接,防止连接泄漏。查询结果放进request作用域,用forward转发而不是redirect,因为要带着数据过去。参数上,@WebServlet注解代替了 web.xml 配置,Tomcat 7 以上都支持。
3.2 JSP 页面用 JSTL 渲染数据,别写 Java 脚本片段
JSP 里最忌讳写<% %>脚本片段,可读性差还容易出错。用 JSTL 的c:forEach循环渲染。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>学生列表</title> <link rel="stylesheet" href="${pageContext.request.contextPath}/css/bootstrap.min.css"> </head> <body> <div class="container mt-4"> <h3>学生信息列表</h3> <table class="table table-striped table-hover"> <thead class="table-dark"> <tr> <th>学号</th><th>姓名</th><th>性别</th><th>班级</th><th>操作</th> </tr> </thead> <tbody> <c:forEach items="${studentList}" var="stu"> <tr> <td>${stu.sno}</td> <td>${stu.name}</td> <td>${stu.gender}</td> <td>${stu.className}</td> <td> <a href="${pageContext.request.contextPath}/student/edit?id=${stu.id}" class="btn btn-sm btn-primary">编辑</a> <a href="javascript:del(${stu.id})" class="btn btn-sm btn-danger">删除</a> </td> </tr> </c:forEach> </tbody> </table> </div> </body> </html>${pageContext.request.contextPath}是拿项目上下文路径,避免部署到 Tomcat 后路径写死导致 404。c:forEach的items对应 Servlet 里setAttribute的 key。JSTL 需要jstl.jar和standard.jar两个包,放在WEB-INF/lib下。
3.3 中文乱码的三个位置:请求、响应、数据库
中文乱码是这套技术栈的血泪经验,三个地方都要设。请求参数用req.setCharacterEncoding("UTF-8"),响应用resp.setContentType("text/html;charset=UTF-8"),数据库连接 URL 里带characterEncoding=utf8。JSP 页面头部pageEncoding="UTF-8"也不能少。Tomcat 8 以后 GET 请求默认 UTF-8,但 POST 必须手动设,否则插入的学生姓名全是问号。
注意:如果用了过滤器统一设编码,Servlet 里就不用重复设了。过滤器写法是在
doFilter里先request.setCharacterEncoding("UTF-8")再chain.doFilter。
4. Bootstrap 页面改造:让 JSP 看起来不像上世纪的产物
4.1 引入 Bootstrap 的两种方式与国内加载优化
Bootstrap 引入有两种:CDN 和本地文件。做学生管理系统这种内网或本地演示项目,建议下载到本地webapp/static目录下,避免网络问题导致样式加载不出来。Bootstrap 5 去掉了 jQuery 依赖,但很多老源码用的是 Bootstrap 3 或 4,需要 jQuery。看源码里有没有jquery.min.js就能判断。
<!-- Bootstrap 5 本地引入 --> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/bootstrap/css/bootstrap.min.css"> <script src="${pageContext.request.contextPath}/static/bootstrap/js/bootstrap.bundle.min.js"></script>bootstrap.bundle.min.js已经包含 Popper,不用单独引。路径用${pageContext.request.contextPath}开头,保证部署后能找到。
4.2 用 Bootstrap 栅格和表单组件重写学生新增页
新增学生页面用栅格布局,左边表单右边提示,比原始的一列到底好看得多。
<div class="container mt-4"> <div class="row"> <div class="col-md-6 offset-md-3"> <div class="card"> <div class="card-header bg-primary text-white">新增学生</div> <div class="card-body"> <form action="${pageContext.request.contextPath}/student/add" method="post"> <div class="mb-3"> <label class="form-label">学号</label> <input type="text" name="sno" class="form-control" required> </div> <div class="mb-3"> <label class="form-label">姓名</label> <input type="text" name="name" class="form-control" required> </div> <div class="mb-3"> <label class="form-label">性别</label> <select name="gender" class="form-select"> <option value="男">男</option> <option value="女">女</option> </select> </div> <button type="submit" class="btn btn-success">保存</button> </form> </div> </div> </div> </div> </div>col-md-6 offset-md-3让表单居中,card组件做卡片容器,form-control和form-select是 Bootstrap 5 的表单类名。Bootstrap 4 里 select 用的是form-control,5 改成了form-select,混用会导致下拉框样式丢失。
4.3 表格、分页与删除确认的交互细节
列表页的删除按钮不要直接跳转,用 JavaScript 弹确认框,防止误删。
function del(id) { if (confirm('确定删除这条学生记录吗?')) { window.location.href = '${pageContext.request.contextPath}/student/delete?id=' + id; } }分页用 Bootstrap 的pagination组件,后端 Servlet 接收page和pageSize两个参数,SQL 里用LIMIT做分页。pageSize一般设 10,太大页面加载慢,太小翻页频繁。分页查询要单独写一个COUNT(*)查总数,否则算不出总页数。
5. 避坑与排查:这套源码跑不起来时先看这几条
5.1 现象:访问页面报 404,控制台无报错
原因通常是 Servlet 的@WebServlet路径和 JSP 里请求的路径对不上,或者web.xml里metadata-complete="true"导致注解不生效。解决:检查@WebServlet("/student/list")和表单action是否一致;确认web.xml的web-app标签版本是 3.0 以上,且没有metadata-complete="true"。
5.2 现象:数据库连接报No suitable driver found
原因有两种:驱动 jar 没放进WEB-INF/lib,或者驱动类名写错。解决:确认mysql-connector-java-x.x.x.jar在WEB-INF/lib下;MySQL 8.0 用com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver;URL 里jdbc:mysql://不能写成jdbc:mysql//。
5.3 现象:插入中文变成问号或乱码
原因:请求编码、响应编码、数据库字符集三者有一个没设对。解决:按 3.3 节三个位置逐一检查;数据库和表的字符集用SHOW CREATE TABLE student;确认是utf8mb4;连接 URL 加characterEncoding=utf8。
5.4 现象:Tomcat 启动报端口被占用
原因:8080 端口被其他程序占用,常见的是另一个 Tomcat 或某些开发工具。解决:netstat -ano | findstr 8080找到占用进程 PID,任务管理器结束;或者改conf/server.xml里的Connector port为 8081。
5.5 现象:JSP 页面样式全丢,变成纯文本
原因:Bootstrap 的 CSS 路径写错,或者用了 CDN 但网络加载失败。解决:F12 看 Network 里 CSS 请求是不是 404;本地引入时确认static/bootstrap/css/bootstrap.min.css文件真实存在;路径用${pageContext.request.contextPath}开头。
6. 从能跑到能改:把这份源码变成自己的项目经验
跑通只是第一步,真正让这份源码产生价值的是改它。我一般会做三件事:第一,把登录密码从明文改成 SHA-256 加盐存储,顺手加一个登录过滤器,没登录的请求全部拦到登录页;第二,把 JDBC 裸写 SQL 抽成 DAO 层,每个实体一个 DAO 类,Servlet 里只调方法不写 SQL,这样以后换 MyBatis 也容易;第三,给列表页加分页和模糊搜索,搜索条件用PreparedStatement拼,防止 SQL 注入。
// 密码加密示例 public static String sha256(String raw, String salt) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update((raw + salt).getBytes(StandardCharsets.UTF_8)); byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); }验证方法很简单:注册一个用户,看数据库里存的是不是 64 位十六进制串;用错误密码登录应该失败,用正确密码登录应该成功。盐值可以每个用户随机生成存到用户表里,也可以全局固定,学生项目里固定盐就够用。
| 改造项 | 改动位置 | 验证方式 |
|---|---|---|
| 密码加密 | 注册/登录 Servlet | 数据库密码字段非明文 |
| 登录过滤器 | Filter 类 + web.xml | 未登录访问列表跳登录页 |
| DAO 分层 | 新增 dao 包 | Servlet 里无 SQL 字符串 |
| 分页搜索 | 列表 Servlet + JSP | 传 keyword 能过滤结果 |
这套东西做完,你对 Servlet 生命周期、请求转发与重定向的区别、JSP 作用域、JDBC 连接管理的理解会比看十篇教程都扎实。我自己的习惯是每接手一份老源码,先不改功能,只加日志和异常捕获,把每个请求的入参和出参打出来,跑一遍所有功能,心里有数了再动手。这个习惯帮我省了很多后悔药。希望帮到你。
本文还有配套的精品资源,点击获取