简介:这是一套面向计算机相关专业毕设学生与Java项目实战练习者的学生管理系统完整资料,基于Servlet、JSP、JDBC与DbUtils搭建后台,前端采用EasyUI、jQuery与Ajax,数据库为MySQL,覆盖学生、教师、管理员三种角色的信息管理、成绩登记统计导出、考试管理等核心业务。资源包共15个文件,约13.49MB,包含项目源码压缩包、数据库脚本、项目文档(PDF与Markdown)、运行截图及软件工具下载说明,结构清晰,便于按模块查阅与二次开发。目前已有18530人学习下载,热度较高。项目经过严格调试,界面美观、操作简单,读者可直接获得可运行的完整源码与配套文档,用于毕设答辩、课程设计或JavaWeb实战练习,快速理解分层开发与数据库交互流程。
1. 基于JavaWeb的学生管理系统:从课程设计到毕设落地的完整路径
很多计算机专业的学生在做毕设时,第一个想到的题目就是学生管理系统。原因很直接:业务逻辑清晰、功能边界明确、技术栈成熟,而且网上能找到大量参考资料。但真正动手做的时候,问题就来了——用 JSP 还是 Spring Boot?数据库怎么设计?前端用不用 Vue?源码拿到手跑不起来怎么办?这些问题不解决,项目就只能停留在“能编译但跑不通”的状态。
基于 JavaWeb 实现的学生管理系统,核心就是围绕学生信息、课程、成绩、用户权限这几个模块,用 Java 技术栈完成一套可运行的 Web 应用。它适合两类人:一是刚学完 JavaWeb 课程、需要做一个完整项目练手的同学;二是正在准备毕设、需要一个结构清晰、可扩展、能写进论文的项目作为基础的同学。接下来的内容会从技术选型、数据库设计、核心功能实现、常见踩坑几个维度展开,让你能照着做出一套真正能跑的系统。
2. 技术选型:Servlet+JSP 还是 Spring Boot,先想清楚三件事
2.1 两种主流路线的真实差异
JavaWeb 学生管理系统的实现路线,目前主要有两种。第一种是经典 Servlet + JSP + JDBC 路线,这也是大多数高校 JavaWeb 课程的教学内容。第二种是 Spring Boot + MyBatis + Thymeleaf(或前后端分离)路线,更贴近企业开发实际。
先看一张对比表:
| 维度 | Servlet + JSP + JDBC | Spring Boot + MyBatis |
|---|---|---|
| 学习成本 | 低,课程已学过 | 中,需要理解 IoC、AOP |
| 代码量 | 大,每个功能都要写 Servlet | 小,注解+接口即可 |
| 配置复杂度 | web.xml 配置繁琐 | 自动配置,少量 application.yml |
| 适合场景 | 课程设计、JavaWeb 课设 | 毕设、需要体现技术深度 |
| 论文可写点 | 偏少,技术栈较老 | 丰富,可写架构设计、前后端分离 |
| 调试难度 | 低,流程透明 | 中,需要理解框架启动流程 |
如果你的毕设导师明确要求“体现 JavaWeb 技术”,Servlet + JSP 路线完全够用,而且代码逻辑透明,答辩时好讲。如果导师没有限制,或者你想让论文的技术章节更有内容可写,Spring Boot 路线更合适。我一般建议:课程设计用 Servlet + JSP,毕设用 Spring Boot,除非学校有明确要求。
2.2 数据库选型与表结构设计
数据库基本锁定 MySQL,版本用 5.7 或 8.0 都行,但要注意驱动包版本匹配。核心表至少需要以下几张:
-- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender TINYINT DEFAULT 1 COMMENT '1男 2女', class_id INT COMMENT '班级ID', phone VARCHAR(20), email VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 课程表 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(100) NOT NULL, credit DECIMAL(3,1) DEFAULT 0, teacher_id INT, semester VARCHAR(20) COMMENT '开课学期' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 成绩表 CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,1), exam_time DATE, UNIQUE KEY uk_student_course (student_id, course_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 用户表(用于登录) CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT 'student' COMMENT 'admin/teacher/student', ref_id INT COMMENT '关联学生或教师ID' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有几个设计决策需要说明。学号字段加了唯一索引,防止重复录入;成绩表用student_id + course_id做联合唯一键,避免同一学生同一课程出现多条成绩记录;用户表用role字段区分角色,ref_id关联到具体的学生或教师记录,这样登录后能直接定位到个人数据。
字符集统一用utf8mb4,不要用utf8,否则遇到生僻字或特殊符号会出问题。存储引擎用 InnoDB,支持事务和外键,虽然学生管理系统对事务要求不高,但养成习惯没坏处。
2.3 项目目录结构与依赖管理
不管选哪条路线,目录结构都要清晰。以 Maven 项目为例,推荐的结构如下:
student-management/ ├── src/main/java/ │ ├── controller/ # 控制层 │ ├── service/ # 业务层 │ ├── dao/ # 数据访问层 │ ├── entity/ # 实体类 │ └── util/ # 工具类 ├── src/main/resources/ │ ├── mapper/ # MyBatis映射文件(如果用) │ ├── application.yml # Spring Boot配置 │ └── db.properties # 数据库配置 ├── src/main/webapp/ │ ├── WEB-INF/ │ │ └── web.xml # Servlet路线需要 │ └── static/ # 静态资源 └── pom.xmlMaven 依赖方面,Servlet 路线需要javax.servlet-api、jsp-api、mysql-connector-java、jstl。Spring Boot 路线需要spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、lombok(可选)。版本号不要盲目追新,MySQL 驱动用 8.0.x 时,JDBC URL 要加serverTimezone=Asia/Shanghai,否则会报时区错误。
注意:如果用 IDEA 创建项目,Servlet 路线要选 “Java Enterprise”,Spring Boot 路线直接用 Spring Initializr。不要手动建普通 Java 项目再改,容易漏掉 Web 目录结构。
3. 核心功能实现:登录、CRUD、分页与权限控制
3.1 登录认证与 Session 管理
登录是系统的入口,也是权限控制的基础。Servlet 路线的实现思路是:接收用户名密码 → 查数据库 → 比对成功则把用户信息存入 Session → 跳转主页。Spring Boot 路线可以用拦截器或 Spring Security,但毕设级别用 Session + 拦截器就够了。
// Servlet 路线:LoginServlet @WebServlet("/login") public class LoginServlet extends HttpServlet { private UserDao userDao = new UserDao(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); // 密码建议用 MD5 或 BCrypt 加密后比对 SysUser user = userDao.findByUsernameAndPassword(username, password); if (user != null) { req.getSession().setAttribute("currentUser", user); resp.sendRedirect(req.getContextPath() + "/student/list"); } else { req.setAttribute("msg", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } }这段代码的关键点:Session 中存的是完整的用户对象,后续页面通过currentUser.role判断权限;密码不要明文存库,至少做一次 MD5,虽然 MD5 也不够安全,但比明文强;登录失败用 forward 而不是 redirect,这样能保留错误提示信息。
Spring Boot 路线可以用拦截器统一处理:
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if (session.getAttribute("currentUser") == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }然后在配置类中注册拦截器,排除登录页和静态资源路径。这样所有需要登录的接口都会被自动拦截,不用在每个 Controller 里重复判断。
3.2 学生信息的增删改查与分页
CRUD 是学生管理系统的核心。以查询为例,Servlet 路线需要在 Servlet 中接收分页参数、调用 DAO、把结果存入 request 域、转发到 JSP。Spring Boot 路线用 Controller + Service + Mapper 三层结构。
分页查询的 SQL 是关键:
SELECT s.*, c.class_name FROM student s LEFT JOIN class c ON s.class_id = c.id WHERE s.name LIKE CONCAT('%', #{keyword}, '%') ORDER BY s.id DESC LIMIT #{offset}, #{pageSize}offset的计算方式是(pageNum - 1) * pageSize。这里用 LEFT JOIN 是为了在列表页直接显示班级名称,避免前端再发一次请求。LIKE CONCAT('%', #{keyword}, '%')是 MyBatis 的写法,如果用 JDBC 就是字符串拼接,但要注意 SQL 注入问题,必须用 PreparedStatement。
对应的 Controller 方法:
@GetMapping("/student/list") public String list(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(defaultValue = "") String keyword, Model model) { PageInfo<StudentVO> page = studentService.queryPage(pageNum, pageSize, keyword); model.addAttribute("page", page); model.addAttribute("keyword", keyword); return "student/list"; }参数说明:pageNum默认 1,pageSize默认 10,这两个值前端可以传也可以不传。keyword用于模糊搜索,默认空字符串表示查全部。返回的PageInfo对象包含总记录数、总页数、当前页数据列表,前端根据这些信息渲染分页条。
删除操作要注意级联问题。如果学生表被成绩表引用,直接删学生会留下孤儿记录。两种处理方式:一是先删成绩再删学生,二是用逻辑删除(加is_deleted字段)。毕设级别建议用逻辑删除,简单且不会丢数据。
3.3 成绩录入与统计查询
成绩模块比学生信息复杂一些,因为它涉及多表关联和统计。录入成绩时,前端传学生 ID、课程 ID、分数,后端先检查是否已存在记录,存在则更新,不存在则插入。
@PostMapping("/score/save") public String saveScore(@RequestParam int studentId, @RequestParam int courseId, @RequestParam BigDecimal score) { Score existing = scoreService.findByStudentAndCourse(studentId, courseId); if (existing != null) { existing.setScore(score); scoreService.update(existing); } else { Score newScore = new Score(); newScore.setStudentId(studentId); newScore.setCourseId(courseId); newScore.setScore(score); scoreService.insert(newScore); } return "redirect:/score/list"; }统计查询是论文里可以写的亮点。比如查询某门课程的平均分、最高分、最低分、及格率:
SELECT c.course_name, COUNT(*) AS total_count, AVG(s.score) AS avg_score, MAX(s.score) AS max_score, MIN(s.score) AS min_score, SUM(CASE WHEN s.score >= 60 THEN 1 ELSE 0 END) / COUNT(*) * 100 AS pass_rate FROM score s JOIN course c ON s.course_id = c.id GROUP BY c.id, c.course_name这个查询用到了聚合函数和条件求和,答辩时如果被问到“你的系统有什么统计功能”,这就是一个很好的回答点。前端可以用 ECharts 把结果渲染成柱状图或饼图,视觉效果会加分不少。
3.4 角色权限的简单实现
学生管理系统通常有三种角色:管理员、教师、学生。管理员能管理所有数据,教师能录入成绩和查看自己课程的学生,学生只能查看自己的信息和成绩。
实现方式有两种。第一种是在每个请求处理前判断 Session 中的角色,不满足则跳转到无权限页面。第二种是用过滤器或拦截器统一处理。推荐第二种,代码更集中。
public class RoleFilter 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(); SysUser user = (SysUser) request.getSession().getAttribute("currentUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } // 学生角色不能访问 /student/delete 等管理接口 if ("student".equals(user.getRole()) && uri.contains("/delete")) { response.sendError(403, "无权限"); return; } chain.doFilter(req, resp); } }这段代码的逻辑是:先判断是否登录,再判断角色是否有权限访问当前 URI。实际项目中可以把权限规则配置化,比如用role-uri映射表,但毕设级别用硬编码判断也能接受,关键是要有这个意识。
4. 避坑指南:源码跑不起来、乱码、连接池耗尽怎么排查
4.1 导入源码后 404:先查这三个地方
现象:从网上拿到一份 JavaWeb 学生管理系统源码,用 IDEA 打开、配置 Tomcat、启动,浏览器访问首页显示 404。
原因通常有三个。第一,web.xml中的url-pattern和实际访问路径不匹配,比如 Servlet 配的是/login,但访问的是/LoginServlet。第二,项目的 Context Path 没设置或设置错了,IDEA 中 Tomcat 配置的 Deployment 页面要确认 Application context 的值。第三,JSP 文件放在WEB-INF下但没走 Controller 转发,直接访问WEB-INF下的 JSP 是访问不到的。
解决步骤:先看 Tomcat 启动日志有没有报错;再确认访问 URL 是否包含正确的 Context Path;最后检查web.xml或注解中的路径映射。如果是 Spring Boot 项目,检查application.yml中的server.servlet.context-path配置。
4.2 中文乱码:请求、响应、数据库三处都要设
现象:页面输入中文,保存到数据库变成???,或者从数据库查出来显示乱码。
原因:编码链路中有一处没设 UTF-8。请求参数编码、响应编码、数据库连接编码、表字符集,四个地方都要一致。
解决:Servlet 中在doGet/doPost开头加req.setCharacterEncoding("UTF-8")和resp.setContentType("text/html;charset=UTF-8")。Spring Boot 在application.yml中配置spring.http.encoding.charset=UTF-8和spring.http.encoding.force=true。数据库连接 URL 加useUnicode=true&characterEncoding=utf8。建表时指定DEFAULT CHARSET=utf8mb4。Tomcat 的server.xml中 Connector 加URIEncoding="UTF-8"。
提示:如果用了过滤器统一设编码,注意过滤器要放在所有其他过滤器之前,否则可能被覆盖。
4.3 数据库连接池耗尽:连接没关是主因
现象:系统运行一段时间后报Cannot get a connection, pool exhausted或Too many connections。
原因:DAO 层每次操作都DriverManager.getConnection()但没有close(),连接数只增不减。或者用了连接池但配置的最大连接数太小。
解决:确保每个 Connection、Statement、ResultSet 都在 finally 块中关闭,或者用 try-with-resources。如果用 Druid 或 HikariCP,检查maxActive配置,一般设 10-20 就够毕设用了。另外注意,事务中如果手动管理连接,提交或回滚后也要关闭。
4.4 MyBatis 映射文件不生效:namespace 和路径都要对
现象:Spring Boot + MyBatis 项目启动正常,但查询报Invalid bound statement (not found)。
原因:Mapper XML 文件的 namespace 和接口全限定名不一致,或者 XML 文件放在resources下但没被扫描到。
解决:检查 XML 中<mapper namespace="com.xxx.dao.StudentMapper">是否和接口路径完全一致。在application.yml中配置mybatis.mapper-locations=classpath:mapper/*.xml。如果 XML 和接口放在一起(都在src/main/java下),需要在pom.xml的resources中配置包含**/*.xml。
4.5 前端页面样式丢失:静态资源被拦截了
现象:JSP 或 HTML 页面能打开,但 CSS、JS、图片全部 404,页面变成纯文本。
原因:Spring Boot 的拦截器拦截了所有请求,包括静态资源路径。或者静态资源放在了WEB-INF下,浏览器无法直接访问。
解决:拦截器注册时排除/static/**、/css/**、/js/**、/images/**等路径。静态资源统一放在src/main/resources/static/下,不要放在WEB-INF里。如果是 Servlet 路线,检查web.xml中defaultservlet 的映射是否被覆盖。
5. 从能跑到能写进论文:三个让毕设加分的技术细节
5.1 用 AOP 做操作日志,答辩时有的聊
系统能跑通只是及格线,毕设要拿高分,得有一些“设计感”。操作日志是一个成本低、效果好的加分项。思路是用 Spring AOP 拦截所有增删改操作,把操作人、操作类型、操作时间、请求参数记到日志表。
@Aspect @Component public class LogAspect { @Autowired private LogService logService; @Around("@annotation(com.example.annotation.OperationLog)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long cost = System.currentTimeMillis() - start; SysUser user = (SysUser) RequestContextHolder.currentRequestAttributes() .getAttribute("currentUser", RequestAttributes.SCOPE_SESSION); OperationLog log = new OperationLog(); log.setUsername(user != null ? user.getUsername() : "anonymous"); log.setMethod(joinPoint.getSignature().toShortString()); log.setCost(cost); log.setCreateTime(new Date()); logService.save(log); return result; } }这段代码用@Around环绕通知,在目标方法执行前后记录耗时。RequestContextHolder可以在非 Controller 层拿到当前请求的 Session 信息。自定义注解@OperationLog加在需要记录的方法上,灵活控制粒度。论文里可以写“基于 AOP 的操作日志采集”,比单纯写 CRUD 有技术含量。
5.2 用 ECharts 做数据可视化,截图放论文里
成绩统计如果只输出表格,论文里不好看。用 ECharts 渲染成图表,截图放进论文,视觉效果直接提升一个档次。前端引入 ECharts 后,后端提供一个返回 JSON 的接口:
// 前端:请求统计数据并渲染柱状图 fetch('/api/score/statistics') .then(res => res.json()) .then(data => { const chart = echarts.init(document.getElementById('chart')); chart.setOption({ title: { text: '各课程平均分' }, tooltip: {}, xAxis: { data: data.courseNames }, yAxis: {}, series: [{ name: '平均分', type: 'bar', data: data.avgScores, itemStyle: { color: '#5470c6' } }] }); });后端接口返回{ "courseNames": ["Java", "数据库", "网络"], "avgScores": [78.5, 82.3, 75.1] }这样的结构。ECharts 的配置项很多,毕设不需要全部掌握,会改xAxis.data、series.data、type这几个关键属性就够了。图表类型可以换line、pie,根据数据特点选择。
5.3 用 Git 管理代码,论文里写版本控制
很多同学做毕设没有版本管理意识,代码改乱了想回退都回不去。用 Git 管理项目,不仅方便自己回退,论文里还可以写一节“版本控制与协作开发”。基本操作:
git init git add . git commit -m "完成学生信息CRUD" git branch feature/score-module git checkout feature/score-module # 开发成绩模块... git checkout master git merge feature/score-module分支策略不用太复杂,master放稳定版本,每个功能模块开一个feature分支,开发完合并回去。提交信息写清楚做了什么,不要写“update”或“修改”。如果学校要求代码查重,Git 的提交记录也能证明是你逐步开发出来的,不是一次性拷贝的。
注意:
.gitignore文件要加上target/、.idea/、*.iml、*.class,不要把编译产物和 IDE 配置提交上去。
5.4 我踩过的一个真实坑
最后说一个我当年做毕设时的血泪教训。系统在本地 Tomcat 跑得好好的,部署到服务器上就报ClassNotFoundException。排查了半天,发现是 MySQL 驱动包没有放到WEB-INF/lib下,IDEA 里配置的依赖在导出 WAR 包时没有自动打进去。后来养成习惯:每次导出 WAR 后,先解压检查WEB-INF/lib下有没有mysql-connector-java.jar,没有就手动加进去。这个坑不大,但卡了我整整一个下午,希望帮到你。
本文还有配套的精品资源,点击获取