ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Java Swing的学生选课成绩管理系统设计与实现

基于Java Swing的学生选课成绩管理系统设计与实现 简介基于Java Swing构建的学生选课成绩信息管理系统项目包定位于Java桌面应用与数据库设计的综合实战案例适合有一定Java基础、正在做课程设计或毕业设计的开发者使用。系统以Swing完成登录、选课、成绩管理等界面数据层通过ER图规划并配合SQL脚本建表后端处理学生信息、课程容量校验等业务规则整体展示了从界面到数据库的典型开发链路。资源共142个文件压缩包约131MB主要包含36个Java源码、75个class编译文件、SQL数据库脚本、ER图Word文档、AVI操作录屏、GIF及图片预览可帮助不同阶段的读者分别用于源码研读、效果查看和环境复现。目前已有746人学习下载配套视频教程对初学者尤其友好能直观演示项目运行流程与关键操作。通过分析此完整项目读者可掌握Swing组件的布局与事件处理、实体关系图到数据表结构的转换以及选课成绩管理系统中常见业务逻辑的编码组织方式为独立开发类似管理系统提供扎实参考。1. 为什么学生选课成绩管理还值得用Java Swing重做一遍期末排课、成绩录入、学生临时查分教务老师最常干的三件事。用Excel当然能顶但多人同时改、数据不统一时就会把表格玩坏。学生选课成绩信息管理系统这套经典题目高校里这么多年一直有人选是因为它恰好覆盖了Java基础、JDBC、Swing组件和数据库设计四项能力复杂度又刚好够一个学期做完。标题里的“ER图文档视频源码”不是给你摆样子的实际交付时ER图是答辩讲解的骨架源码是核心视频则省了老师猜你怎么跑Demo的时间。这条路线适合正在做课程设计、毕设或想拿Swing练分层设计的人照着下面的顺序从画ER图到跑通程序半天能走完。2. 从ER图到关系模型选课成绩系统的表结构与Navicat落地2.1 先识别实体和属性学生、课程、成绩不是平级关系ER图画的不是一堆文本框而是“实体-联系-属性”三件套。学生是实体属性是学号、姓名、班级课程是实体属性是课程号、课程名、学分。学生和课程之间是选课关系这个关系本身携带学期和成绩所以它不能丢在一边必须画成菱形或者弱实体。很多人把成绩单独画成一个实体逻辑上也不算错但如果你要统计“某学生某学期所有课的总成绩”直接把成绩放在选课关系上操作起来最自然。实体属性说明Studentstudent_id, name, gender, class_name, enroll_year学生主键学号Coursecourse_id, course_name, credit, period, teacher_id课程主键课程号Teacherteacher_id, teacher_name, dept可选项课程归属教师ElectCoursestudent_id, course_id, semester, score选课联系成绩作为属性表里注意ElectCourse这种联系集既充当中间表又把它自己的属性semester、score带进来。你在画ER图时就把这一层想清楚后面转关系模型不会乱。2.2 从ER图转成关系模型三张表还是四张表ER转关系模型的通用规则并不复杂每个实体生成一张表多对多联系单独生成一张表联系的属性归到该表中一对多联系则把一端主键放进多端表里作为外键。按这个规则上面的ER图至少得到四张表学生表、教师表、课程表、选课表。教师和课程是一对多所以课程表保留teacher_id学生与课程多对多所以选课表保留student_id与course_id再加semester、score。三张表只看学生和课程也够用但我一般把教师独立出来因为选题要求里出现“成绩”和“管理”两个词教师角色往往是成绩录入者不建表你后面的权限设计和日志就无处挂钩。若你的成绩需要区分平时、期中、期末那选课表可以拆出单独的成绩表这里按最简单的录入一科一个分数的课程设计规格处理一个选课记录就对应最终成绩。2.3 建表SQL与关键参数主键、非空、外键级联先给一份可以直接在MySQL 8.0上跑的建表脚本使用utf8mb4编码避免中文乱码CREATE DATABASE IF NOT EXISTS stu_course_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE stu_course_db; CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 学生姓名, gender CHAR(1) DEFAULT 男 COMMENT 性别, class_name VARCHAR(50) COMMENT 班级, enroll_year YEAR DEFAULT NULL COMMENT 入学年份 ) ENGINEInnoDB COMMENT学生表; CREATE TABLE teacher ( teacher_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 教师ID, teacher_name VARCHAR(50) NOT NULL COMMENT 教师姓名, dept VARCHAR(100) COMMENT 所属院系 ) ENGINEInnoDB COMMENT教师表; CREATE TABLE course ( course_id VARCHAR(20) PRIMARY KEY COMMENT 课程号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT 学分, period INT COMMENT 课时, teacher_id INT COMMENT 授课教师ID, CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES teacher(teacher_id) ON DELETE SET NULL ) ENGINEInnoDB COMMENT课程表; CREATE TABLE elect_course ( student_id VARCHAR(20) COMMENT 学号, course_id VARCHAR(20) COMMENT 课程号, semester VARCHAR(20) COMMENT 学期如2024-2025-1, score DECIMAL(5,2) COMMENT 成绩NULL表示未录入, PRIMARY KEY (student_id, course_id, semester), CONSTRAINT fk_ec_student FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, CONSTRAINT fk_ec_course FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE ) ENGINEInnoDB COMMENT选课成绩表;这里重点说几个后面容易踩坑的参数。选课表的主键是(student_id, course_id, semester)联合主键这个设计允许同一学生同一课程在不同学期重修并有各自成绩如果你把主键只设成前两个字段重修记录就会冲突。外键级联上的区别也值得注意学生删除时选课记录用ON DELETE CASCADE一起清掉否则学籍删除会报外键约束错误课程删除时如果该课程已有历史成绩直接级联删除选课记录会丢数据所以这里在业务层要先判断。教师删除时课程表的teacher_id用ON DELETE SET NULL保留课程但解除教师关联这是数据完整性里比较稳妥的做法。score DECIMAL(5,2)允许0到999.99适用于及格和优秀率统计若按百分制你也可以写成DECIMAL(5,2) CHECK (score 0 AND score 100)不过MySQL 8.0.16之前会忽略CHECK约束所以程序里也要校验。2.4 用Navicat将SQL反向生成ER图表建好后最省事的是让工具直接帮你出图。打开Navicat for MySQL连接库后双击stu_course_db右键选“逆向数据库到模型”工具会把四张表、主外键关系和字段注释全部拉进模型画布。到这一步基本不用手点连线连完毕检查一下关系线上的一对多、多对多标识是否符合预期。如果你习惯PowerDesigner思路一样先创建Physical Data Model再用Database菜单里的Reverse Engineer连MySQL生成。区别在于PowerDesigner生成的是物理模型文件Navicat生成的是一张可以继续编辑的画布。导出时直接在模型窗口导出为PNG放进你的ER图文档。注意在图里关掉无关字段显示只留主键、外键和关联线答辩老师看起来才清楚。3. Java Swing实现选课成绩管理系统核心代码与关键参数3.1 项目目录结构按实体、Dao、Service、UI四层拆拿到源码压缩包后第一件事不是找Main方法而是看目录。我习惯按这种结构组织二次开发起来快src/ ├─ com/course/entity/ Student.java, Teacher.java, Course.java, ElectCourse.java ├─ com/course/dao/ BaseDao.java, StudentDao.java, CourseDao.java, ScoreDao.java ├─ com/course/service/ StudentService.java, ScoreService.java ├─ com/course/ui/ LoginFrame.java, MainFrame.java, ScorePanel.java ├─ com/course/util/ DBUtil.java, ExcelUtil.java └─ resources/ └─ db.properties实体层只放字段和getter/setterDao层只写JDBC操作不管登录逻辑Service层做成绩校验、事务控制UI层只负责组件摆放和事件转发。这种分层不是为好看而是你改Swing界面时不需要动SQL改统计逻辑时不需要碰JTable。Swing项目常见病是所有代码堆在JFrame里后面加一个“修改学分”功能要翻两千行分了层至少能定位到CourseDao.updateCredit()。3.2 数据库连接参数DriverManager还是连接池管理系统是桌面单机应用同时只有几个窗口用连接池是过度设计。直接用DriverManager配合配置文件就够了。// util/DBUtil.java public class DBUtil { private static final String URL; private static final String USER; private static final String PASSWORD; static { Properties props new Properties(); try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { props.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(读取db.properties失败); } String host props.getProperty(db.host, localhost); String port props.getProperty(db.port, 3306); String name props.getProperty(db.name, stu_course_db); URL jdbc:mysql:// host : port / name ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; USER props.getProperty(db.user, root); PASSWORD props.getProperty(db.password, ); } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }注意URL中的参数顺序和值参数值作用useUnicodetrue配合characterEncoding统一文本编码characterEncodingutf8与数据库utf8mb4保持一致的JDBC编码useSSLfalse本地连接不启用SSL握手降低报错率serverTimezoneAsia/Shanghai指定JVM与MySQL之间的时区规避8.x驱动校验useSSLfalse是为了本地开发不提示SSL握手serverTimezoneAsia/Shanghai必须加MySQL 8.0驱动的时区校验很严格不写经常抛Server time zone value ÖйúcharacterEncodingutf8要和数据库字符集一致否则查询结果里的中文会乱。props.getProperty第二个参数是默认值这样源码里即使缺少某一项配置也能兜底。3.3 登录窗口JFrame基础参数与事件写法Swing窗口的启动逻辑固定登录界面通常长这样// ui/LoginFrame.java public class LoginFrame extends JFrame { private JTextField userField; private JPasswordField passwordField; public LoginFrame() { setTitle(学生选课成绩信息管理系统 - 登录); setLayout(null); setBounds(600, 300, 380, 240); // 直接指定位置和大小 setDefaultCloseOperation(EXIT_ON_CLOSE); setLocationRelativeTo(null); JLabel userLabel new JLabel(用户名:); userLabel.setBounds(60, 50, 60, 30); add(userLabel); userField new JTextField(); userField.setBounds(130, 50, 160, 30); add(userField); // 登录按钮是JButton绑定事件用addActionListener JButton loginBtn new JButton(登 录); loginBtn.setBounds(140, 140, 100, 35); add(loginBtn); loginBtn.addActionListener(e - doLogin()); } private void doLogin() { String username userField.getText().trim(); String password new String(passwordField.getPassword()); // 调用Service去查用户表密码比对放在Service层 } }这里需要提醒几个Swing参数setLayout(null)是绝对定位配合setBounds用代码直观但窗口缩放时组件不会跟着动如果你想让界面跟着窗口大小走改用BorderLayout或GridBagLayout但放组件时要多写约束参数。setDefaultCloseOperation(EXIT_ON_CLOSE)是登录窗口的一半习惯登录成功后要dispose()关闭登录窗再打开主窗而不是继承EXIT_ON_CLOSE否则主窗也会被关掉。文本框拿内容用getText()密码框用getPassword()返回字符数组别直接调用toString()。3.4 成绩查询表JTable、DefaultTableModel、JScrollPane的参数成绩列表是最常见的展示场景逻辑是Dao查ListUI填充到TableModel里。关键代码// dao/ScoreDao.java public ListElectCourse selectScoresByStudent(String studentId) { String sql SELECT course_id, semester, score FROM elect_course WHERE student_id ?; ListElectCourse list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { ElectCourse ec new ElectCourse(); ec.setCourseId(rs.getString(course_id)); ec.setSemester(rs.getString(semester)); ec.setScore(rs.getBigDecimal(score)); list.add(ec); } } } catch (SQLException e) { e.printStackTrace(); } return list; }查询里用PreparedStatement而不是拼字符串?占位符由驱动预处理这样既防SQL注入也避免学号里的单引号把SQL截断。getBigDecimal读小数成绩比getDouble准确浮点型在显示和计算时会丢精度。界面侧填充JTable// ui/ScorePanel.java String[] columns {课程号, 学期, 成绩}; ListElectCourse scores scoreService.getScoresByStudent(studentId); Object[][] data new Object[scores.size()][3]; for (int i 0; i scores.size(); i) { ElectCourse ec scores.get(i); data[i][0] ec.getCourseId(); data[i][1] ec.getSemester(); data[i][2] ec.getScore() null ? 未录入 : ec.getScore(); } DefaultTableModel model new DefaultTableModel(data, columns); JTable table new JTable(model); table.setRowHeight(28); // 行高太小点击不方便 table.getColumnModel().getColumn(0).setPreferredWidth(80); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane scrollPane new JScrollPane(table); scrollPane.setBounds(10, 10, 600, 350); add(scrollPane);DefaultTableModel构造器的两个参数分别是二维数组和字段名数组数据行数可以传0后面用addRow()逐条加。setRowHeight影响操作手感默认高度在高分屏上特别难点中行。表格列宽用setPreferredWidth给建议值实际服从滚动面板剩余宽度的比例想固定列宽可以把表格放到autoResizeMode为JTable.AUTO_RESIZE_OFF的模式下。4. 把源码.zip导入IDE并跑通排错与参数调整4.1 打开项目前先确认JDK与驱动jar不管拿到的是直接可运行的源码还是需要自己导包的源码先用命令行确认环境java -version mysql --version这两个命令至少保证JDK和MySQL版本与项目预期一致。项目如果是老课程设计多用JDK 8配mysql-connector-java-5.1.x.jar如果用的是MySQL 8.0推荐JDK 8以上并配mysql-connector-j-8.0.x.jar。压缩包里一般会有lib目录放jar包没找到就去Maven仓库下载对应的Connector/J注意驱动不要搞混8.x驱动类名仍是com.mysql.cj.jdbc.Driver但底层连接逻辑比5.x严格许多。4.2 修改数据库连接配置统一走配置文件源码里如果直接硬编码用户名密码我建议改成配置驱动。新建resources/db.propertiesdb.hostlocalhost db.port3306 db.namestu_course_db db.userroot db.password123456再配合前面3.2节的DBUtil读取。改完之后不用重新编译只要替换resources目录下的properties文件即可。这里有一个容易忽略的细节IDE里getResourceAsStream(db.properties)默认去classpath找你把文件放src目录和放resources目录都能找到但如果你需要打包成可执行Jar记得在构建配置里把resources目录包含进去否则运行时报空指针。4.3 三个高频报错驱动、时区、字符集报错信息可能原因处理参数java.lang.ClassNotFoundException: com.mysql.jdbc.Driver没导入驱动包或驱动类名写错引入jar包8.x用com.mysql.cj.jdbc.Driverjava.sql.SQLException: Unknown database stu_course_db数据库名写错或没建库先执行第2章建表SQL或改db.namejava.sql.SQLSyntaxErrorException: Table xxx doesnt exist表名大小写或结构不一致Windows下MySQL表名大小写默认不敏感Linux下敏感统一小写java.sql.SQLException: Server time zone value Öйú时区参数问题在URL加serverTimezoneAsia/Shanghai?? ?? ?? 中文乱码JDBC编码与库表不一致URL加characterEncodingutf8建库用utf8mb4前两类最常发生在刚导入源码的项目里。驱动报错时检查项目的Module Dependencies或External Libraries里是否能看到mysql-connector-x.x.jar看不到就手动Add JAR。时区报错是MySQL 8.0升级后的新坑老教程里很少写serverTimezone照着加就行。4.4 跑通最小路径登录、选课、录成绩环境就绪后按“登录→查询→改分”的顺序走一遍功能验证。启动LoginFrame用管理员身份登录进入MainFrame学生模块输入学号能查到选课记录成绩模块能修改某条记录分数并保存。如果保存时日志里报Column score cannot be null去数据库看elect_course的score字段是不是NOT NULL建表时我故意把score留空表示未录入如果你的建表脚本没给默认值插入带空成绩的数据就会失败这种情况把字段改成DEFAULT NULL即可。5. 让管理系统更耐看成绩统计SQL与ER图一致性校验5.1 用聚合SELECT实现统计行单看成绩列表没意思给主界面加一行平均分、最高分、最低分的统计能让整个系统看上去完整。统计不需要重新遍历List直接交给MySQLSELECT COUNT(*) AS total_cnt, COALESCE(ROUND(AVG(score), 2), 0) AS avg_score, COALESCE(MAX(score), 0) AS max_score, COALESCE(MIN(score), 0) AS min_score FROM elect_course WHERE student_id ?;COALESCE在处理全未录成绩时很关键AVG在空数据上返回NULLSwing里显示“null”很难看用COALESCE兜底成0。查询结果拿到后用DefaultTableModel.addRow()追加到表格最后一行。5.2 用information_schema核对ER图与实际表画ER图是一回事代码跑的数据库又是另一回事最怕两者不一致。用一条SQL就能把外键关系拉出来和ER图对照SELECT kcu.table_name, kcu.column_name, kcu.referenced_table_name, kcu.referenced_column_name FROM information_schema.key_column_usage kcu WHERE kcu.table_schema stu_course_db AND kcu.referenced_table_name IS NOT NULL ORDER BY kcu.table_name;这条查询会返回所有表的外键比如elect_course的student_id关联student的student_id。你对照ER图里的联线就能验证源码里建的库和文档里的设计是否一致。如果发现外部键缺失一般是建表时漏了CONSTRAINT语句可以用ALTER TABLE补上不建议重新建表避免数据丢失。5.3 把成绩录入做成批量尾插课程设计里最提效的一个功能期末一次性录入全班成绩。与其逐行点修改不如用PreparedStatement批量执行// ScoreDao.java String sql UPDATE elect_course SET score ? WHERE student_id ? AND course_id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (ScoreRow row : scoreRows) { ps.setBigDecimal(1, row.getScore()); ps.setString(2, row.getStudentId()); ps.setString(3, row.getCourseId()); ps.addBatch(); } int[] result ps.executeBatch();如果批次里有一行失败默认部分记录可能已经写入想保证全部成功或全部失败需要在for循环外面设conn.setAutoCommit(false)最后一并conn.commit()发生异常时conn.rollback()。这个细节在答辩演示时很加分因为老师特别喜欢问“你录错了怎么回滚”。如果你想追问批量的性能边界把executeBatch()换成每行单独提交对比一下时间差就能直观知道JDBC批量写值的价值。本文还有配套的精品资源点击获取
返回列表