简介这份数据库课程设计资源面向高校计算机相关专业学生与课程设计指导教师聚焦学生选课信息管理系统的完整实现采用Java语言与MySQL数据库、C/S架构开发可作为课程设计、期末大作业或数据库综合实践的参考方案。压缩包为rar格式整体约1.99MB内含源代码与配套设计报告建表合理规范涵盖学生、教师、管理员三类角色的功能划分学生可修改个人信息、查询课程、选课退课、查询成绩与奖惩信息并打印成绩单教师可查看所授课程、学生选课情况并录入成绩管理员负责学生、教师、课程、选课、成绩、院系及奖惩信息等管理。设计报告对需求分析、E-R模型与表结构设计有较完整说明便于读者理解系统架构与数据库设计思路并在此基础上二次开发或撰写自己的课程设计文档。目前已有6064人学习下载适合需要快速获取可运行案例与规范设计文档的读者参考借鉴。1. 从一份能跑通的选课系统说起MySQLJava 的课设到底该长什么样很多数据库课程设计的翻车点不在 SQL 写不出来而在于交上去的东西跑不起来——建表脚本缺外键、Java 端连不上库、角色权限全糊在一起。这份「学生选课信息管理系统」源代码加设计报告走的是 Java MySQL 的 C/S 架构把学生、教师、管理员三类角色的权限边界切得比较清楚建表也按范式做了约束。它适合两类人一是正在赶数据库课设、需要一份结构完整可参考的模板二是想拿一个真实业务场景练手 SQL 和 JDBC 的初学者。下面我按「先看懂表结构 → 再跑起来 → 再改得动 → 最后避坑」的顺序拆一遍中间会给出可直接抄的建表、连接和查询代码。2. 表结构与角色权限三类用户背后的数据模型怎么落地2.1 核心实体与关系梳理选课系统的本质是几个实体之间的多对多关系学生和课程通过选课记录关联教师和课程通过授课关系关联成绩挂在选课记录上。设计报告里建表合理规范核心表大致是这几张表名作用关键字段约束要点student学生基本信息学号(主键)、姓名、院系、密码学号唯一院系外键teacher教师基本信息工号(主键)、姓名、院系、密码工号唯一course课程信息课程号(主键)、课程名、学分、教师工号教师工号外键sc选课记录学号、课程号、成绩联合主键(学号,课程号)department院系设置院系编号(主键)、院系名院系名唯一reward_punish奖惩信息记录号、学号、类型、说明学号外键这里最容易被忽略的是sc表的联合主键——它同时保证了「一个学生同一门课只能选一次」这是选课业务里最基础的约束。很多同学用自增 id 做主键结果退课再选就出现重复记录成绩查询直接出两条这就是没理解业务约束的后果。2.2 建表脚本与约束写法下面这段是核心表的建表语句可以直接拿去改-- 院系表先建被学生和教师表引用 CREATE TABLE department ( dept_id VARCHAR(10) PRIMARY KEY, dept_name VARCHAR(50) NOT NULL UNIQUE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 学生表dept_id 外键指向院系 CREATE TABLE student ( stu_id VARCHAR(20) PRIMARY KEY, stu_name VARCHAR(30) NOT NULL, dept_id VARCHAR(10), password VARCHAR(50) NOT NULL, CONSTRAINT fk_stu_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 选课表联合主键防止重复选课成绩允许为空未录入 CREATE TABLE sc ( stu_id VARCHAR(20), course_id VARCHAR(20), score DECIMAL(5,1) DEFAULT NULL, PRIMARY KEY (stu_id, course_id), CONSTRAINT fk_sc_stu FOREIGN KEY (stu_id) REFERENCES student(stu_id), CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明ENGINEInnoDB是必须的因为只有 InnoDB 支持外键和事务选课和退课涉及多表操作时需要事务保证一致性。utf8mb4而不是utf8是为了避免姓名里出现生僻字或特殊字符时插入失败。score设为DECIMAL(5,1)而不是FLOAT因为成绩是精确值浮点数会出现 89.99999 这种玄学问题。参数上VARCHAR长度按实际业务给学号一般不超过 20 位密码字段如果做加密存储要留够长度。2.3 三类角色的权限边界学生、教师、管理员的功能差异本质是 SQL 操作范围不同。学生只能操作自己的记录WHERE stu_id ?教师只能操作自己所授课程的成绩WHERE course_id IN (SELECT ... WHERE teacher_id ?)管理员才有全表增删改权限。这个边界如果在 Java 端只用界面隐藏而不在 SQL 层加条件就是典型的越权漏洞——学生改个请求参数就能查别人成绩。常见做法是在 DAO 层把当前登录用户的 id 作为强制条件拼进 SQL而不是信任前端传来的参数。3. 把项目跑起来环境、连接与第一个可执行查询3.1 环境准备与数据库初始化先确认本机环境JDK 8 或以上、MySQL 5.7/8.0、一个能连数据库的 IDE。MySQL 8.0 要注意驱动包版本用mysql-connector-java8.x老版本 5.x 的驱动连 8.0 会报时区或认证错误。初始化步骤登录 MySQL创建数据库CREATE DATABASE course_system DEFAULT CHARSET utf8mb4;执行设计报告附带的建表脚本按「院系 → 教师/学生 → 课程 → 选课 → 成绩/奖惩」的顺序建否则外键会报错。插入测试数据至少每个角色一条方便登录验证。# 命令行导入建表脚本的常见做法 mysql -u root -p course_system schema.sql # 导入测试数据 mysql -u root -p course_system data.sql参数说明-u后跟用户名-p会提示输入密码不要直接在命令里写密码会留在历史记录里。是输入重定向把脚本喂给 mysql 客户端执行。如果脚本里有USE语句数据库名可以省略。3.2 JDBC 连接与配置抽取Java 端连库的代码如果写死用户名密码换台机器就翻车。正确做法是抽到配置文件里// db.properties 放在 src 根目录 // urljdbc:mysql://localhost:3306/course_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 // userroot // passwordyour_password public class DBUtil { private static String url; private static String user; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); url props.getProperty(url); user props.getProperty(user); password props.getProperty(password); // 显式加载驱动兼容老版本 Class.forName(com.mysql.cj.jdbc.Driver); } catch (Exception e) { throw new RuntimeException(数据库配置加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }逻辑说明serverTimezoneAsia/Shanghai是 MySQL 8.0 必须的参数不写会报The server time zone value is unrecognized。useSSLfalse在本地开发时关掉避免证书警告。characterEncodingutf8保证中文不乱码。把连接信息放配置文件而不是硬编码是为了换环境时只改一处。参数上连接池在生产环境是必须的但课设级别用DriverManager直接拿连接也能跑只是每次都要新建连接性能差。3.3 一个完整的选课查询链路以「学生查询可选课程」为例从界面到 SQL 的完整链路// 查询该学生尚未选修的课程 public ListCourse queryAvailableCourses(String stuId) { String sql SELECT c.course_id, c.course_name, c.credit, t.teacher_name FROM course c LEFT JOIN teacher t ON c.teacher_id t.teacher_id WHERE c.course_id NOT IN ( SELECT course_id FROM sc WHERE stu_id ? ); ListCourse list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, stuId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Course c new Course(); c.setCourseId(rs.getString(course_id)); c.setCourseName(rs.getString(course_name)); c.setCredit(rs.getBigDecimal(credit)); c.setTeacherName(rs.getString(teacher_name)); list.add(c); } } } catch (SQLException e) { e.printStackTrace(); } return list; }逻辑说明用NOT IN子查询排除已选课程这是选课界面最核心的查询。LEFT JOIN是为了课程即使没分配教师也能显示出来。用PreparedStatement而不是字符串拼接一是防 SQL 注入二是预编译后重复执行效率更高。参数上setString(1, stuId)的下标从 1 开始这是 JDBC 的规矩写 0 会报错。try-with-resources保证连接和语句自动关闭避免连接泄漏——课设里最常见的「跑一会儿就卡死」多半就是连接没关。4. 改得动才算真会用成绩录入与事务处理4.1 成绩录入的更新逻辑教师录入成绩本质是更新sc表的score字段但前提是这条选课记录存在。如果学生没选课教师直接插入成绩就会破坏数据一致性。正确做法是先判断再更新public boolean inputScore(String stuId, String courseId, BigDecimal score) { String checkSql SELECT COUNT(*) FROM sc WHERE stu_id ? AND course_id ?; String updateSql UPDATE sc SET score ? WHERE stu_id ? AND course_id ?; try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 开启事务 try (PreparedStatement check conn.prepareStatement(checkSql)) { check.setString(1, stuId); check.setString(2, courseId); try (ResultSet rs check.executeQuery()) { rs.next(); if (rs.getInt(1) 0) { conn.rollback(); return false; // 没有选课记录拒绝录入 } } } try (PreparedStatement update conn.prepareStatement(updateSql)) { update.setBigDecimal(1, score); update.setString(2, stuId); update.setString(3, courseId); update.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); return false; } }逻辑说明setAutoCommit(false)开启事务检查和更新要么都成功要么都回滚。虽然这里只有一条更新但加上事务是习惯——如果后续要同时更新奖惩或统计表事务就是后悔药。参数上BigDecimal对应数据库的DECIMAL不要用double否则 89.5 可能变成 89.499999。成绩范围校验0-100应该在 Java 端和数据库端都做数据库端可以加CHECK (score BETWEEN 0 AND 100)MySQL 8.0 支持。4.2 退课与级联影响退课不是简单删sc表记录要考虑如果成绩已录入还能不能退奖惩信息是否关联常见做法是成绩非空则不允许退课在 SQL 层加条件DELETE FROM sc WHERE stu_id ? AND course_id ? AND score IS NULL;如果返回影响行数为 0说明要么记录不存在要么成绩已录入。这个判断比在 Java 端先查再删更可靠因为查和删之间可能有并发。参数上executeUpdate()的返回值就是影响行数用它来判断操作是否生效比再查一次数据库省一次往返。4.3 打印成绩单的查询组装打印成绩单需要把学生信息、所选课程、成绩、学分汇总。一条 SQL 搞定SELECT s.stu_name, c.course_name, c.credit, sc.score, CASE WHEN sc.score 60 THEN 及格 ELSE 不及格 END AS status FROM sc JOIN student s ON sc.stu_id s.stu_id JOIN course c ON sc.course_id c.course_id WHERE sc.stu_id ? ORDER BY c.course_id;逻辑说明CASE WHEN在 SQL 层直接算及格状态避免 Java 端再循环判断。JOIN而不是子查询是因为要同时取多张表的字段。参数上ORDER BY保证成绩单顺序稳定不然每次打印顺序不一样用户会以为数据错了。5. 避坑与排查课设里最容易翻车的五个点5.1 中文乱码现象是姓名显示问号现象插入中文姓名后查询出来是???或乱码。原因通常是三处字符集不一致数据库、表、连接串。解决建库时DEFAULT CHARSET utf8mb4建表时也指定JDBC url 加characterEncodingutf8。三处都对齐基本不会乱。5.2 外键约束报错现象是插入课程失败现象插入课程记录时报Cannot add or update a child row。原因是teacher_id在教师表里不存在。解决要么先插教师要么把该字段设为允许 NULL。课设里常见的是测试数据顺序不对按「院系 → 教师 → 课程 → 学生 → 选课」的顺序插就没问题。5.3 连接泄漏现象是跑几十次后卡死现象程序运行一段时间后无响应MySQL 里SHOW PROCESSLIST看到大量 Sleep 连接。原因是Connection、Statement、ResultSet没关闭。解决全部用try-with-resources或者 finally 里按「ResultSet → Statement → Connection」的逆序关闭。这是血泪经验课设演示时卡死很尴尬。5.4 时区报错现象是连接直接抛异常现象MySQL 8.0 下连接报The server time zone value ... is unrecognized。原因是驱动和服务器时区不匹配。解决url 里加serverTimezoneAsia/Shanghai或者升级驱动到 8.0.23 以上。这个坑在新版 MySQL 上几乎必踩。5.5 权限越界现象是学生能查到别人成绩现象改一下请求里的学号就能看到其他学生数据。原因是 SQL 没加当前用户条件只靠界面隐藏。解决所有涉及个人数据的查询在 DAO 层强制拼WHERE stu_id 当前登录用户不信任前端传参。这是设计报告里应该体现的安全意识也是答辩时容易被问的点。6. 进阶技巧用存储过程和视图把复杂查询收进数据库课设做到后面会发现 Java 端拼 SQL 越来越长成绩统计、选课人数汇总这些逻辑散在各处。一个让代码干净起来的做法是把复杂查询做成视图把批量操作做成存储过程。先建一个成绩单视图CREATE VIEW v_score_report AS SELECT s.stu_id, s.stu_name, d.dept_name, c.course_id, c.course_name, c.credit, sc.score, CASE WHEN sc.score 60 THEN 及格 WHEN sc.score IS NULL THEN 未录入 ELSE 不及格 END AS status FROM sc JOIN student s ON sc.stu_id s.stu_id JOIN course c ON sc.course_id c.course_id LEFT JOIN department d ON s.dept_id d.dept_id;Java 端查成绩单就变成SELECT * FROM v_score_report WHERE stu_id ?逻辑收进数据库改起来只动一处。视图的代价是每次查询都实时计算数据量大时慢但课设数据量下完全够用。再写一个统计某课程选课人数的存储过程DELIMITER // CREATE PROCEDURE count_course_students(IN p_course_id VARCHAR(20), OUT p_count INT) BEGIN SELECT COUNT(*) INTO p_count FROM sc WHERE course_id p_course_id; END // DELIMITER ;调用方式CALL count_course_students(C001, num); SELECT num;。参数上IN是入参OUT是出参Java 端用CallableStatement注册出参类型。存储过程的好处是减少网络往返坏处是调试麻烦、移植性差课设里用来展示「会用数据库高级特性」就够了不必过度使用。验证视图和存储过程是否生效我一般会先在 MySQL 命令行里跑一遍确认结果对了再接到 Java 里。这样出问题时能快速定位是 SQL 的锅还是 Java 的锅。从那以后我每次接新库都强制先在命令行把核心查询跑通再写代码省得在 IDE 里反复试错。希望帮到你。本文还有配套的精品资源点击获取