尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

学生选课系统Java+MySQL课设全攻略:从E-R图到事务避坑

发布时间:2026/9/26 9:02:25

资讯中心
01
ARTICLE

学生选课系统Java+MySQL课设全攻略:从E-R图到事务避坑

学生选课系统Java+MySQL课设全攻略:从E-R图到事务避坑
简介面向数据库课程设计的完整学生选课信息管理系统源码与设计报告适用于高校计算机相关专业学生完成数据库课程设计、Java 综合实训也可供初学者参考 MySQL 与桌面应用开发的结合方式。资源采用 C/S 架构覆盖学生、教师、管理员三类角色包含学生个人信息修改、课程查询、选课退课、成绩查询与打印成绩单查询、奖惩信息查看教师成绩录入与所授课程/选课信息查看以及管理员对学生、教师、课程、选课、成绩、院系和奖惩信息的管理等典型业务模块。压缩包约 1.99MB共 112 个文件其中 21 个 Java 源文件与 66 个 class 文件构成源代码主体附 SQL 建表脚本、2 份 doc 设计报告及 Eclipse 工程配置便于直接导入运行和阅读。已有 16470 人学习/下载资源同时提供规范合理的数据库建表方案与完整设计报告可帮助读者快速完成课程报告撰写并深入理解数据库设计流程。1. 学生选课信息管理系统数据库课设经典题为什么年年有人翻车每年数据库课程设计选题学生选课信息管理系统都是最热门的方向热门到十个组里有七个选它。我见过太多人拿到这个题目时觉得简单无非是学生表、课程表、选课表再加一堆增删改查。结果真正动手Java 连不上 MySQL、选课超容量、中文乱码、驱动加载失败每一步都能卡住一整天最后交上去的代码能跑通的没几个。这套方案的核心其实很清晰用 MySQL 建好三张关系表用 Java 写一个能登录、能选课退课、能查课表的完整流程交付物是能运行的源代码和一份能讲清楚设计过程的课程设计报告。它适合正在准备数据库课设、想走一条稳定可复现路线、又不希望答辩被问倒的人。这个题目热度高的原因也简单——它覆盖了数据库设计、JDBC 编程、事务处理、界面交互和文档写作是典型的 Java 课程设计案例也是从 mysql 安装到真正连接成功才算入门的综合题。2. 先设计再建表选课系统的 E-R 模型与 MySQL 表结构六个关键点一次说清2.1 为什么这题的破局点是选课表而不是学生表和课程表很多人的第一版设计是两张表学生表存学生信息课程表存课程信息然后就没有然后了。选课记录往哪放有人塞进学生表的一个字符串字段里有人建了一张包含所有字段的宽表。这两种做法在前期看不出问题一旦开始统计「这门课多少人选」「这个学生这学期几门课」SQL 就会写得非常痛苦。正确的做法是先画 E-R 图再写建表语句。这个系统涉及三个实体学生、课程以及二者之间的多对多联系——选课。多对多联系在关系模型里必须独立成表这就是选课表存在的原因。选课表本身还带着两个重要属性选课时间和成绩。所以表结构设计的核心决策是学生表、课程表负责描述实体选课表负责描述「谁在什么时候选了哪门课、考了多少分」。我一般建议在报告里画三张图全局 E-R 图、分实体 E-R 图、关系模式转换图。用 draw.io 或者任意绘图工具都行重点是让老师看到你确实分析过实体和联系而不是上来就敲 CREATE TABLE。这个分析过程会直接影响后面所有代码的写法选课表设计对了后面的查重、冲突检测、成绩录入都是顺水推舟的事。2.2 MySQL 建表字段类型、默认值与唯一约束老师重点挑的就是这些先建库再建三张表。字符集统一用 utf8mb4避免中文乱码这是 mysql 安装完成后第一件要做的事。CREATE DATABASE IF NOT EXISTS course_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE course_system; CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY COMMENT 学号, password VARCHAR(64) NOT NULL COMMENT 登录密码, name VARCHAR(50) NOT NULL COMMENT 姓名, major VARCHAR(50) DEFAULT 未填写 COMMENT 专业, enroll_year INT DEFAULT YEAR(CURDATE()) COMMENT 入学年份, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间 ) ENGINEInnoDB; CREATE TABLE course ( course_id VARCHAR(20) PRIMARY KEY COMMENT 课程号, course_name VARCHAR(100) NOT NULL COMMENT 课程名, teacher VARCHAR(50) NOT NULL COMMENT 授课教师, credit DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT 学分, capacity INT NOT NULL DEFAULT 60 COMMENT 课程容量, selected_count INT NOT NULL DEFAULT 0 COMMENT 已选人数, class_time VARCHAR(100) COMMENT 上课时间如 周一3-4节, class_room VARCHAR(50) COMMENT 上课教室 ) ENGINEInnoDB; CREATE TABLE enroll ( student_id VARCHAR(20) NOT NULL COMMENT 学号, course_id VARCHAR(20) NOT NULL COMMENT 课程号, enroll_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, score DECIMAL(5,1) DEFAULT NULL COMMENT 成绩成绩录入时填写, PRIMARY KEY (student_id, course_id), KEY idx_course (course_id), CONSTRAINT fk_enroll_student FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE, CONSTRAINT fk_enroll_course FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE ) ENGINEInnoDB;几个参数说明也是答辩时老师喜欢问的点。学号和课程号用 VARCHAR 而不是 INT因为高校真实编号往往带字母前缀用 INT 会在录入「CS101」时直接报错。学分用 DECIMAL(3,1) 而不是 FLOAT因为浮点数做精度比较会有误差成绩字段同理。选课表的主键用联合主键 (student_id, course_id)天然保证同一个学生不能重复选同一门课这比在代码里 if 判断要可靠得多是兜底手段。外键我这里加了物理外键。课设场景里这没问题但你要知道它的副作用删除学生或课程时会联动删除选课记录。如果老师要求「学生删除后选课记录保留」就需要把 ON DELETE CASCADE 改成 ON DELETE RESTRICT或者在代码里做逻辑删除。另一个常见方案是不建物理外键靠 Java 业务层维护一致性很多生产系统就是这么干的。课设里我倾向于加物理外键报告里可以写「利用数据库的完整性约束保证数据一致」这句话在答辩时很加分。2.3 选课查重与容量校验业务规则写在 Java 里比写在触发器里好调表建完后就要处理最核心的业务规则一个学生不能重复选同一门课课程已选人数不能超过容量同一学生选的课时间不能冲突。这三条规则我建议全部放在 Java 的 service 层做而不是写数据库触发器。原因很现实课设代码要给人看业务逻辑放在 Java 方法里老师能直接读代码逻辑放在触发器里代码里看不到答辩时你还得额外解释。先解决查重和容量两个问题对应的 SQL 是这样-- 查重判断该学生是否已经选过这门课 SELECT COUNT(*) FROM enroll WHERE student_id ? AND course_id ?; -- 查容量读取当前课程容量和已选人数 SELECT capacity, selected_count FROM course WHERE course_id ?;这两条 SELECT 单独跑都没问题但它们和后续的 INSERT、UPDATE 必须放在同一个事务里否则会出现「两个人同时选最后一个名额」的超卖问题。这个事务怎么组织第三章写 Java 代码时会给出完整实现这里只需要记住一个原则先查后改的操作查和改之间绝对不能让别的请求插进来。时间冲突的检测稍微复杂一点。course 表里我存的是「周一3-4节」这种字符串Java 拿到目标课程的 class_time再查学生已选课程列表逐条比较字符串是否重叠。这种方式对课设足够用因为课表安排基本都是整块节次少有人设计到「周一第2节到第3节」这种细粒度。如果老师希望更严谨可以拆出 weekday、start_section、end_section 三个字段但整表设计会复杂不少没有特殊要求不推荐。3. 用 Java 把选课流程跑通三层分包、连接池与事务控制的最小实现3.1 工程结构entity/dao/service/ui 各管什么别把类全怼进默认包Java 课设最常见的翻车现场是打开项目看到十几个 .java 文件全躺在默认包下Main 类里有几百行代码数据库操作写在按钮的点击事件里。这种写法不是不能跑但报告里写不出「分层设计」四个字答辩被问到「你的项目结构为什么这么设计」时只能沉默。我一般按这样的包结构组织对应经典的 MVC 思想com.course.system ├── entity // 实体类Student、Course、Enroll对应三张表 ├── dao // 数据访问封装 JDBC 操作只处理 SQL 和结果集 ├── service // 业务逻辑选课、退课、成绩录入、登录验证 ├── ui // 界面层Swing 窗口和事件监听 ├── util // 工具类DbUtil 数据库连接 └── Main.java // 程序入口为什么这样分entity 里的类和表字段一一对应DAO 层只负责「把数据取出来/写进去」service 层专注业务规则UI 层只做界面显示和用户交互。好处是出了问题你知道去哪找连不上数据库看 util 和 dao选课逻辑不对看 service界面没反应看 ui。答辩时老师随便指一个类你都能说清它的职责——这是课设拿高分的通用技巧比代码写得多更有价值。3.2 DbUtil 连接工具与连接池参数连接串、时区、字符集是最大变量JDBC 连接这一步几乎是整个项目里报错最密集的地方你可能会同时遇到 java 环境变量配置不对、mysql 驱动 jar 没导入、时区报错三个问题叠加。基础版本直接用 DriverManager先保证连通import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DbUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://127.0.0.1:3306/course_system ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码里面的参数一个个说。驱动类名和 MySQL 版本强相关本地如果是 MySQL 5.7驱动名写成 com.mysql.jdbc.Driver如果是 MySQL 8.0 及以上必须写成 com.mysql.cj.jdbc.Driver写错就是 ClassNotFoundException。连接串里的 serverTimezoneAsia/Shanghai 是 MySQL 8 之后的硬性要求不写它启动时直接抛时区异常。useSSLfalse 是为了关闭 SSL 握手课设本地环境不需要加密连接关了能省掉一大堆警告和潜在超时。characterEncodingutf8 配合建库时的 utf8mb4才能保证界面和数据库之间中文不乱码。连接串里的地址我习惯写 127.0.0.1 而不是 localhost后者在某些 mysql 安装配置场景下会走 Unix socket 而不是 TCP导致 Java 进程连不上——这是第 4 章要展开的经典坑。跑通 DriverManager 之后如果你想让报告里多一个亮点可以换成 HikariCP 连接池。课设引入连接池能在「系统性能分析」这一节多写两页答辩时也能回答「频繁开关连接有什么问题」。最小配置长这样jdbcUrljdbc:mysql://127.0.0.1:3306/course_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 driverClassNamecom.mysql.cj.jdbc.Driver usernameroot password123456 maximumPoolSize10 connectionTimeout30000 idleTimeout600000参数含义是maximumPoolSize 控制并发时最多同时持有的连接数课设给 10 够用给太大反而浪费内存connectionTimeout 是等待获取连接的毫秒数本地环境 30 秒足够idleTimeout 是空闲连接最长存活时间默认 10 分钟。这些参数写在报告的性能优化部分比单纯说「用了连接池」更有说服力。3.3 选课事务代码commit、rollback 与 FOR UPDATE 行锁现在实现整个系统最核心的选课操作。前面说过查容量、判重、插入选课记录、更新已选人数这四步必须在一个事务里。否则用户双击两次选课按钮就可能插入两条记录或者已选人数和真实记录对不上。请看这段代码public boolean enroll(String studentId, String courseId) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 第一步锁定课程行读取容量和当前已选人数 ps conn.prepareStatement( SELECT course_id, capacity, selected_count FROM course WHERE course_id ? FOR UPDATE); ps.setString(1, courseId); rs ps.executeQuery(); if (!rs.next()) { conn.rollback(); return false; // 课程不存在 } int capacity rs.getInt(capacity); int selectedCount rs.getInt(selected_count); if (selectedCount capacity) { conn.rollback(); return false; // 已满 } // 第二步判重防止同一个学生重复选课 ps conn.prepareStatement( SELECT COUNT(*) FROM enroll WHERE student_id ? AND course_id ?); ps.setString(1, studentId); ps.setString(2, courseId); rs ps.executeQuery(); rs.next(); if (rs.getInt(1) 0) { conn.rollback(); return false; // 重复选课 } // 第三步插入选课记录 ps conn.prepareStatement( INSERT INTO enroll(student_id, course_id) VALUES (?, ?)); ps.setString(1, studentId); ps.setString(2, courseId); ps.executeUpdate(); // 第四步课程已选人数加一 ps conn.prepareStatement( UPDATE course SET selected_count selected_count 1 WHERE course_id ?); ps.setString(1, courseId); ps.executeUpdate(); conn.commit(); // 全部成功提交事务 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { try { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这段代码里有三个容易被忽略的地方。setAutoCommit(false) 是事务开启的开关开启后所有 SQL 不会立即生效直到调用 commit() 才会真正写入数据库所以它后面的任何一步抛异常rollback 都能把已做的修改全部撤销——这就是「后悔药」。SELECT ... FOR UPDATE 是行级锁它把课程那一行锁住其他事务修改或再读这行时都要等当前事务结束彻底解决并发抢最后一个名额的问题这个知识点写在报告里懂的人会眼前一亮。UPDATE 那一步用了 selected_count selected_count 1 而不是先 SELECT 再赋值这本身是原子操作在事务里更安全。结合 mysql update 语法里 WHERE 条件的重要性退课时对应改成 selected_count selected_count - 1也要在同一事务里先 DELETE enroll 记录再更新数量顺序不能反否则删了记录但人数没减数据就脏了。4. 课设避坑指南MySQL 连不上、中文乱码和选课冲突五个高频翻车现场4.1 ERROR 2002 与 Access denied本地 MySQL 连不上的三种常见原因现象Java 程序跑起来后控制台抛 java.sql.SQLException或者你直接在命令行敲 mysql -u root -p 就报错 ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。不是 MySQL 没装对就是连接方式不对。原因有三种MySQL 服务没启动连接串里用了 localhost 导致客户端尝试走 Unix socket 文件而不是 TCP 端口root 密码和代码里不一致。其中第二种在 macOS 和 Linux 上特别普遍Java 的 JDBC 驱动拿到 localhost 会尝试连 socket 文件而那个文件路径可能不存在或没有权限。解决先确认服务状态命令行能连上 MySQL 再排查 Java 侧。连接串地址统一改成 127.0.0.1强制走 TCP 3306 端口。密码不一致就把 DbUtil 里的 PASSWORD 和实际密码对齐或者直接 reset 一个本地专用账号避免改系统 root 密码。记住原则Java 连不上时先用命令行客户端验证 MySQL 本身是好的再回来查代码。4.2 中文乱码界面和数据库里全是问号现象Swing 表格里学生姓名显示成 ???数据库里 SELECT 出来也是问号。原因字符集在三个环节不统一。建库时用了默认 latin1或者连接串没指定 characterEncodingutf8或者建表语句没带 CHARSETutf8mb4。三个环节只要有一个是 latin1中文就保不住。解决把三层全部统一。建库语句用 DEFAULT CHARACTER SET utf8mb4建表语句加 ENGINEInnoDB DEFAULT CHARSETutf8mb4连接串加 characterEncodingutf8。已经建错的库可以 ALTER DATABASE course_system CHARACTER SET utf8mb4但表里已经有乱码数据的直接删掉重建更省事不要纠结那几条测试数据。4.3 ClassNotFoundException驱动类名写错还是 jar 没进 classpath现象static 代码块里 Class.forName 抛 ClassNotFoundException: com.mysql.jdbc.Driver或者运行时报找不到 mysql-connector-java。原因两个。一是 jar 包没导入项目或者导入了但没进 classpath。二是 MySQL 版本和驱动类名不匹配——MySQL 8 的驱动类名已经改成 com.mysql.cj.jdbc.Driver你还在用旧名字。解决先在项目的 lib 目录确认有没有 mysql-connector-java 的 jar 包没有就去下载对应版本的连接器放到 lib 后通过 IDE 的 Add as Library 或 Build Path 加进去。如果确定 jar 存在还报错再去确认驱动类名和 MySQL 版本是否对应。这个小节卡了很多人通常是 mysql 安装教程看了半天jar 包却忘了下。4.4 选课按钮连点两次超容量还能选进去现象用户快速双击选课按钮或者两个终端同时选同一门课的最后一个名额结果选课记录插进去了selected_count 显示比实际记录少。原因选课的四步操作没有放在事务里执行或者虽然开了事务但在开启事务之前就执行了 SELECT。没有 FOR UPDATE 行锁的 SELECT 不会锁住任何行两个请求同时读到剩余名额为 1各自都认为还有位置于是都插入成功。解决直接用 3.3 节那段代码把整个流程包进 setAutoCommit(false) 和 commit/rollback 里SELECT 后必须跟 FOR UPDATE。再加一道联合主键兜底即使代码漏判数据库层也会拒绝重复的 (student_id, course_id)。如果你被问到「数据库怎么防止超卖」就把这三件事讲清楚事务、行锁、唯一约束这是标准答案。4.5 密码含特殊字符连接串解析失败现象配置文件里写上 passwordabc123启动时 JDBC 报连接串格式错误或者干脆 Access denied。原因连接串 jdbc:mysql://host:port/db?userrootpasswordabc123 里 和 都是 URL 保留字符解析时会把密码截断或误解析。解决密码里不要用 , , # 这类字符本地课设账号改一个纯字母数字密码最省心。如果密码必须保留特殊字符就用 Java 的 URLEncoder.encode 对密码编码后再拼进连接串或者在配置文件里单独写 username 和 password 属性让驱动自己解析。实际课设里我推荐前者别在密码上给自己加戏。5. 把报告写出工作量E-R 图、测试用例和三个必问问题5.1 报告骨架与文件清单代码跑通了报告还占课设分数的四成。我见过的写法是按教科书章节堆概念复制粘贴 E-R 图定义老师翻十页看不到你的系统长什么样。标准结构应该是七节并且每一节都贴自己系统的内容不要写通用的数据库理论。第五节测试部分强烈建议用表格逐项记录输入、预期、实际结果老师会觉得你的系统真的被认真验证过模块测试项输入预期结果登录错误密码学号 2021001密码 000000提示密码错误不进入主界面选课正常选课学号 2021001课程 CS101选课成功已选人数 1选课重复选课同一学号再选 CS101提示已选过拒绝插入选课课程已满选课容量 1 的课程第 2 人提示已满拒绝插入退课正常退课已选课程 T02删除选课记录已选人数 -1查询个人课表学号 2021001显示该生全部课程与上课时间5.2 答辩前更值得准备的是这三个问题第一问你的系统怎么防止重复选课答案是联合主键兜底业务层先查后插两层保障。第二问为什么把选课记录单独建表因为学生和课程是多对多关系单独建表符合第三范式不产生冗余同时可以挂靠选课时间和成绩属性。第三问InnoDB 和 MyISAM 你了解吗为什么选 InnoDB因为 InnoDB 支持事务、外键和行级锁这三样恰好是本系统的核心需求。稍微展开说一句老师如果追问事务隔离级别你说出「默认的 REPEATABLE READ 已经能满足课设场景生产环境高并发会考虑 READ COMMITTED」就算超纲答对。这其实和 java 面试八股文里的事务隔离等级是同一个知识点你把它当成一次迷你面试去准备远比死背报告里的「新时代数据库特点」有价值。坦白讲数据库课设这套流程我前前后后做过不止一遍接手的每届学生碰到的问题都惊人地相似。我现在接手这类题目的习惯是先花小半天把数据库建好、把 JDBC 最小连接跑通再开始写任何界面代码。因为连接串一旦有隐性问题越到后期越难定位界面写完再回头查驱动版本那种血泪经验经历过一次就够。项目的源代码和报告只是结果真正值钱的是你亲手踩一遍这些坑下次遇到同样的错能在五分钟内定位。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。