简介:这是一份面向高校计算机专业学生的《Java学生成绩管理系统》课程设计报告,适用于软件工程、计算机科学等专业的课程设计参考。报告以学生成绩管理为业务场景,完整覆盖学生信息管理、课程成绩维护、按学号/姓名查询、分数段统计、报表输出、用户权限管理等功能模块,并涉及C/S模式架构、SQL Server 2000数据库设计与Java/VB混合开发等内容。资源包内共1个doc文档,整体大小205KB,文件为完整的课程设计报告文本,包含任务书、进度安排、中英文摘要、需求分析、概要设计、详细设计及成绩评定表等栏目,结构清晰、格式规范,可直接作为撰写课程设计报告的结构模板和内容参考。该资源已有786人学习下载,适合需要快速了解学生成绩管理系统设计流程、报告撰写规范或答辩准备的学生使用。
1. 这个课设每年都有人挂,挂在哪
学生成绩管理系统是 Java 课程设计里出现频率最高的题目,但它恰恰是每年答辩翻车最多的题目之一。多数人的失败不在功能上,而在需求边界不清、数据库表设计不合理、代码和文档对不上这三件事上。比如把“学生管理”做成一个包含密码找回、头像上传、短信通知的大杂烩,再比如成绩表里不存学期字段,导致同一个学生同一门课出现多条记录。写全套 Java 增删改查并不难,难的是让数据模型经得起追问:某学生某学期某门课为什么只能有一条成绩记录?排名为什么不用自写的冒泡排序?这篇讲一条适合课程设计交付的路线:需求分析、5 张表、JDBC 连接池、核心 CRUD 与统计 SQL,以及答辩时最容易被问到的几个边界问题。
2. 成绩系统的需求分析:用例、ER 图、5 张表定边界
2.1 先把功能边界画出来:管理员、学生、老师各自能做什么
课程设计最忌讳功能越做越多。合理的做法是先列角色,再给每个角色列 3~5 个用例,画出用例图之后冻结需求。常见角色划分如下。
| 角色 | 核心用例 | 说明 |
|---|---|---|
| 管理员 | 维护学生、教师、班级、课程信息;重置账号密码 | 不做角色细粒度授权,一个 admin 账号即可 |
| 教师 | 录入成绩、修改成绩、按课程统计平均分与及格率 | 不做批量导入 Excel,用页面录入即可 |
| 学生 | 查询本人全部课程成绩、查看自己的单科排名与平均分 | 只能查自己,不能查别人,这里涉及鉴权 |
用例图用 ProcessOn、StarUML 或 draw.io 画都可以,导出 PNG 直接贴到课程设计报告里。要注意的是报告的图评委会对照代码检查:用例图上写了“成绩批量导入”,代码里却没有对应入口,这会成为答辩减分项。因此不打算做的功能,一开始就不要画进用例图。
2.2 数据库设计:为什么是这 5 张表,而不是 1 张表
很多初学者只建两张表:student 和 score,班级、课程信息全用字符串塞进去。这在功能演示上没问题,但被问到“怎么统计某教师教的课程平均分”时就会卡住。常见的 5 张表设计是:user(登录账号)、student(学生信息)、teacher(教师信息)、course(课程信息)、score(成绩记录)。
关键点在于score表要把学期作为业务主键的一部分。成绩本质上是“学生 + 课程 + 学期”三维度确定唯一记录,如果表里没有学期字段,补考记录就无法表示——这个点能现场解释清楚,说明你确实理解关系建模。user表与student、teacher各自通过user_id关联,避免学生表和教师表里重复维护密码字段。
2.3 建表 SQL 与约束取舍
下面是用 MySQL 8.0 建表的参考 SQL,字符集统一 utf8mb4,排序规则 utf8mb4_unicode_ci。
CREATE DATABASE IF NOT EXISTS grade_system DEFAULT CHARACTER SET utf8mb4; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role ENUM('ADMIN', 'TEACHER', 'STUDENT') NOT NULL ) ENGINE=InnoDB; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL UNIQUE, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender ENUM('M', 'F') DEFAULT 'M', class_name VARCHAR(50) NOT NULL, CONSTRAINT fk_student_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINE=InnoDB; CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL UNIQUE, teacher_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, title VARCHAR(20) ) ENGINE=InnoDB; CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, teacher_id INT NOT NULL, credit DECIMAL(3,1) DEFAULT 2.0, CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(id) ) ENGINE=InnoDB; CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, semester VARCHAR(20) NOT NULL, score DECIMAL(5,2) NOT NULL, CHECK (score >= 0 AND score <= 100), CONSTRAINT uk_score_one UNIQUE (student_id, course_id, semester), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINE=InnoDB;这段 SQL 里值得在报告里写清楚的是uk_score_one这个唯一约束:它保证同一个学生、同一门课、同一个学期只能有一条成绩记录。代码层面的拦截是最后一道防线,数据库约束才是兜底。外键在课程设计项目中建议保留,虽然会影响一点插入性能,但能体现参照完整性意识。
3. 技术选型与工程结构:JDBC 还是 MyBatis,目录怎么摆
3.1 三条路线怎么选:JDBC+控制台、JSP+Servlet、Spring Boot
课程设计的技术栈由课程要求决定,但可以从三档里选。
| 技术路线 | 适用场景 | 实现成本 | 答辩风险 |
|---|---|---|---|
| 纯 JDBC + 控制台 / Swing | 课程只要求 Java SE | 最低 | 界面简陋,容易被追问“成绩单打印格式” |
| Servlet + JSP + JDBC | 课程要求 Java Web | 中等 | JSP 已老,但报告好写 |
| Spring Boot + MyBatis | 课程允许框架 | 较高 | 老师追问框架原理时需要能答上 |
我一般建议课程设计优先考虑 Servlet + JSP + JDBC 路线。原因有两个:一是它把请求处理、数据库访问、页面渲染三层都暴露出来,答辩时能讲的东西多;二是如果课程不允许用框架,这条路不会撞红线;三是后面学 Spring Boot 时,能理解框架到底封装了什么。你如果已经熟练 Spring Boot,直接用它也行,但报告里至少要写一段“为什么不用 JDBC”的比较分析。
3.2 分层目录结构与依赖清单
无论哪条路线,包结构都可以统一。下面是一个 Maven 风格的 Java Web 工程目录,包名可以换成你自己的反域名。
src/main/java/com/school/grade/ ├── entity/ -- User, Student, Teacher, Course, Score ├── dao/ -- UserDao, ScoreDao, CourseDao ├── service/ -- ScoreService, LoginService ├── servlet/ -- LoginServlet, ScoreAddServlet, ScoreQueryServlet ├── util/ -- DBUtil, Md5Util src/main/resources/ ├── db.properties -- 数据库连接配置 └── hikari.properties src/main/webapp/ ├── WEB-INF/ ├── css/ js/ └── login.jsp index.jsp这个结构对应“实体类、数据访问、业务逻辑、控制层”四层。课程设计报告里画一张这样的包图,胜过写一段“系统采用分层设计”的套话。实体类只是属性加 getter/setter;DAO 层只写 SQL;Service 层处理事务和业务判断;Servlet 只做参数接收和页面跳转。
3.3 用 HikariCP 替换 DriverManager 的理由与配置
课程设计默认教的是DriverManager.getConnection(),但它有个硬伤:每次请求都新建物理连接,数据显示不了,等到答辩现场开多个页面就会卡。更严重的是很多人的代码里连接不关闭,导致 MySQL 报Too many connections。常见做法是在课程设计里引入 HikariCP 连接池,代码量增加很少,但性能和稳定性都上来了。
# src/main/resources/hikari.properties jdbcUrl=jdbc:mysql://localhost:3306/grade_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username=root password=你的密码 driverClassName=com.mysql.cj.jdbc.Driver maximumPoolSize=10 minimumIdle=2 connectionTimeout=30000package com.school.grade.util; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static final HikariDataSource dataSource; static { try (InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("hikari.properties")) { Properties props = new Properties(); props.load(in); HikariConfig config = new HikariConfig(props); dataSource = new HikariDataSource(config); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static DataSource getDataSource() { return dataSource; } }代码的逻辑是:类加载时通过HikariConfig(props)读取配置并初始化连接池,之后所有 DAO 都从getConnection()拿连接。maximumPoolSize=10对课设足够,minimumIdle=2避免空闲时占用数据库资源。这里不用DriverManager的直接收益是,即使你只在 DAO 里写了Class.forName前的加载逻辑,连接复用也会让整个系统稳定很多。
4. 核心代码:登录、成绩录入与统计的 JDBC 实现
4.1 登录实现:PreparedStatement 防注入与密码处理
登录是整套系统的入口,也是评委最爱看的代码之一。最容易弄出问题的就是用了字符串拼接 SQL,以及密码明文存放。下面是登录校验的 DAO 写法。
public User login(String username, String password) throws SQLException { String sql = "SELECT id, username, role FROM user WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, Md5Util.md5(password)); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setRole(rs.getString("role")); return user; } } } return null; }这段代码有两个必须能在答辩时说清楚的点。第一,PreparedStatement用?占位,由 JDBC 驱动处理特殊字符转义,从机制上堵住 SQL 注入;不要写WHERE username = '" + username + "'。第二,密码不存明文,Md5Util.md5()对输入做摘要后和库里比对,即使数据库被脱库,拿到的也不是原始明文。课程设计做到 MD5 加盐就足够了。
4.2 成绩批量录入:addBatch 与事务边界
教师按班级录入一门课的成绩时,通常会一次提交几十条记录,这时一条条 executeUpdate 性能不好,而且如果第 20 条失败,前面 19 条已经写进库里,数据就脏了。正确的做法是用批处理加事务。
public void batchAddScores(List<Score> scores) throws SQLException { String sql = "INSERT INTO score (student_id, course_id, semester, score) VALUES (?, ?, ?, ?)"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps = conn.prepareStatement(sql)) { for (Score s : scores) { ps.setInt(1, s.getStudentId()); ps.setInt(2, s.getCourseId()); ps.setString(3, s.getSemester()); ps.setBigDecimal(4, s.getScore()); ps.addBatch(); } ps.executeBatch(); } conn.commit(); } catch (SQLException e) { if (conn != null) { conn.rollback(); } throw e; } finally { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } }关键点是setAutoCommit(false)放在获取连接之后,executeBatch()执行完统一commit(),一旦出错则rollback()回滚整批。注意finally里要把自动提交恢复为 true,否则连接归还连接池后状态被污染,下一个使用者会掉进隐式事务里。这个坑调试起来比较隐蔽,值得在报告的测试部分单独写上一条“批量录入失败时数据未残留”。
4.3 统计查询:SQL 聚合与班级排名的两种写法
成绩统计是课程设计的重头戏,很多人的做法是把所有成绩查到 List 里,再用 Java 循环算平均分和排名。数据量小看不出问题,但被问到“几千个学生时怎么办”就会卡住。合理的做法是让数据库完成聚合计算。
-- 某门课程各班级平均分与及格率 SELECT s.class_name, ROUND(AVG(sc.score), 2) AS avg_score, ROUND(SUM(CASE WHEN sc.score >= 60 THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS pass_rate FROM score sc JOIN student s ON sc.student_id = s.id WHERE sc.course_id = ? GROUP BY s.class_name ORDER BY avg_score DESC;CASE WHEN在 MySQL 里可以用于条件计数,这里统计及格人数再除以总数就是及格率。另一个高频需求是“学生想看自己这门课排第几”,MySQL 8.0 可用ROW_NUMBER() OVER,但如果课设环境是 MySQL 5.7,用下面这种自连接计数法更稳妥。
SELECT s.name, sc.score, 1 + (SELECT COUNT(*) FROM score sc2 WHERE sc2.course_id = sc.course_id AND sc2.semester = sc.semester AND sc2.score > sc.score) AS rank_no FROM score sc JOIN student s ON sc.student_id = s.id WHERE sc.course_id = ? AND sc.semester = ? AND s.class_name = ? ORDER BY rank_no ASC;子查询的含义是“统计同课程同班级里比我分数高的人数,再加 1 就是我的名次”。分数相同时会都排到同一个名次,行为与体育比赛并列排名一致。写这个 SQL 并能解释清楚子查询的执行顺序,比背一段堆排序代码更能赢得答辩好感。
5. 运行排错:驱动、连接串、乱码与环境变量的 5 个高频坑
5.1 五个高频异常一表排查
课程设计答辩前夜的报错有很强的共性,下面按出现频率排个表,每条对应最常见的诱因和处理路径。
| 报错信息 | 高频原因 | 处理方式 |
|---|---|---|
ClassNotFoundException: com.mysql.cj.jdbc.Driver | 驱动 jar 没放进WEB-INF/lib或 classpath | 检查 jar 是否在发布后的 lib 目录里 |
Access denied for user 'root'@'localhost' | 密码错误或用户无远程权限 | 核对 db.properties,确认密码是否含特殊字符 |
Unknown database 'grade_system' | 建库脚本没执行 | 用命令行先跑一遍建库 SQL |
Public Key Retrieval is not allowed | MySQL 8.0 默认 caching_sha2_password 认证 | 连接串加allowPublicKeyRetrieval=true |
Too many connections | 连接未关闭或连接池过大 | 检查所有 DAO 是否用了 try-with-resources,把最大连接数调小 |
表里最后一条最容易被忽视。很多人只在finally里 close 了 ResultSet,漏了 PreparedStatement 和 Connection。用 try-with-resources 最省心,它保证三个资源按声明逆序关闭。
5.2 连接串参数含义与乱码处理
乱码问题几乎是 Java 课设必考。MySQL 连接串里的characterEncoding=utf8只解决数据库读写编码,如果页面还是乱,要检查三层:页面ContentType、Servlet 接收请求的编码、MySQL 连接串。在 Servlet 里最直接的做法是写一个编码过滤器,对每个请求设置 UTF-8。
@WebFilter("/*") public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); chain.doFilter(request, response); } }setCharacterEncoding("UTF-8")把 POST 请求体按 UTF-8 解码,setContentType指定浏览器按 UTF-8 渲染页面。这里要说清楚的是:GET 请求的查询参数编码不受这个过滤器控制,中文参数要改 Tomcat 的server.xml连接器加URIEncoding="UTF-8"。如果你的系统只做了成绩录入,用 POST 就够,这个坑可以避开。
5.3 环境变量:java 与 javac 不一致时先查什么
如果是在本机跑起来,遇到java -version正常但javac报错找不到,通常是 PATH 里有个旧的 JRE 路径在前。课程设计环境里常见的情况是装过 JDK 8 又装了 JDK 17,多个版本并存。命令行里直接敲where java和where javac看解析顺序,再把 JDK 的 bin 目录提到 PATH 最前面。
需要在答辩前验证的事项:java -version与javac -version主版本号一致;JAVA_HOME指向 JDK 安装目录而不是 JRE 目录;如果用的 Maven,mvn -version显示的是同一个 JDK。用 IDE 的普遍存在一个问题:IDE 内置的 JRE 跑得挺好,命令行的 Java 编译不了你的源码。这个状态本身没问题,但要心里有数,别在答辩演示时敲命令行命令被环境问题卡住。
6. 从课设到简历:成绩统计的 Stream 写法与答辩加分点
6.1 把统计从 SQL 换成 Java Stream 的时机
答辩评委常会问“如果数据量不大,能不能用 Java 代码算统计”。这是一个展示 Java 8 特性的机会。把某学期全部成绩查出来后,用 Stream 分组做汇总,代码比循环累加清晰得多。
Map<Integer, DoubleSummaryStatistics> stats = scoreList.stream() .collect(Collectors.groupingBy( Score::getCourseId, Collectors.summarizingDouble(Score::getScore) )); stats.forEach((courseId, summary) -> { System.out.println("课程 " + courseId + " 平均分: " + summary.getAverage() + ", 最高分: " + summary.getMax() + ", 最低分: " + summary.getMin() + ", 参考人数: " + summary.getCount()); });DoubleSummaryStatistics一次遍历收集计数、均值、最大值、最小值,比手写四个累加变量少一半代码。但要注意它的边界:如果你还要算及格率和班级维度排名,Stream 代码会变成多层嵌套,可读性反而下降。所以合理的写法是:单课程统计用 SQL,全量数据在内存里做多维汇总用 Stream,两者在代码里都保留,答辩时分别解释各自的适用场景。
6.2 答辩必答的三类追问
第一类问数据完整性:uk_score_one唯一约束和CHECK (score >= 0 AND score <= 100)就是答案,要能说出“应用层拦截只是第一道防线”。第二类问性能:连接池的参数maximumPoolSize=10的依据,以及为什么排名用 SQL 子查询而不是把全表查到内存排序。第三类问安全:登录时PreparedStatement防止注入的原理,以及密码 MD5 摘要后的存储方式。把这三个问题想清楚,课设答辩基本就稳了。如果你想把这段代码写进简历,项目描述里建议写“使用 HikariCP 连接池管理连接,通过唯一约束保证成绩数据一致性,统计查询下沉到 SQL 聚合层”,对应的这些都是这次课设里真实实现过的内容。
本文还有配套的精品资源,点击获取