恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

数据库课程设计:学生成绩管理系统从ER图到JDBC完整实现

  • 首页
  • 资讯中心
  • /
  • 数据库课程设计:学生成绩管理系统从ER图到JDBC完整实现

相关资讯

XGBoost特征重要性为0的真相与实战排查指南 2026/10/3 13:17:19
uniapp微信小程序真机调试正常但预览/体验版请求失败?域名校验排查指南 2026/10/3 13:12:19
Python+SVM构建舆情分析系统:从数据采集到模型落地 2026/10/3 13:12:19

最新资讯

模型三剑客:工具、函数调用与MCP协同之道
开源项目可视化--一键生成架构图丨Github项目分享
【人工智能】卡片笔记法:从原子化思考到知识网络构建
不会代码也能搭建报销系统,普通员工一句话搞定
EtherCAT协议:从“飞读飞写”机制到分布式时钟同步的实时以太网架构深度解析
teach - LEARNING-RECORD-FORMAT

今日推荐

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成
编译原理实验:递归下降分析器消除左递归与避坑指南
Python协议级爬取Shopee商品数据实战

本周热门

从像素到笔画:srt-whiteboard-animation骨架笔迹追踪实现(Zhang-Suen细化+8邻接追踪)
网站建设的英语怎么说?别只背单词,看完这套安全完整流程才敢上线
新手入门看这篇:建设网站加盟避坑指南与SEO实操

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

数据库课程设计:学生成绩管理系统从ER图到JDBC完整实现

发布时间:2026/10/3 13:17:19
数据库课程设计:学生成绩管理系统从ER图到JDBC完整实现 简介一份数据库课程设计学生成绩管理系统的完整设计文档主要面向网络工程、计算机等专业学生适用于需要完成成绩管理类课程设计或强化数据库实践能力的场景。文档围绕学生、课程、教师、学院等核心实体系统梳理了需求分析、数据字典、概念模型、逻辑与物理结构设计等关键环节覆盖学生信息维护、成绩管理、课程管理、用户登录验证、数据备份恢复以及按班级或课程统计平均成绩、最高分、最低分、及格率等功能并给出学生表、课程表、选课表、成绩表的SQL建表语句便于对照练习和二次开发。包体内包含1个docx格式的说明文档大小约1.53MB内容完整、结构清晰。该资源已有344人学习下载适合作为课程设计说明书撰写和数据库原理复习的参考资料。1. 数据库课程设计“学生成绩管理系统”到底是什么一份docx背后的三条主线“数据库课程设计学生成绩管理系统.docx”这行标题对应的是高校数据库课设里出现频率几乎最高的题目交付物是文档、代码和答辩三样东西。很多人的做法是先找现成代码改跑不起来就回头修数据库最后docx用模板套一层答辩时被老师问到“成绩表为什么这么设计”直接卡壳。反直觉的结论是这道题拿高分的重点不在功能有多少而在关系模型是否严谨、并发下数据是否安全、文档里的数据字典和测试用例是否完整。这篇按一条可复现的路径讲从ER图到建库SQL从JDBC增删改查到docx排版最后给答辩前必查的验证SQL。适合正在做课设的学生也适合想快速把成绩系统这类CRUD项目写规范的初级开发。2. 从ER图到建库SQL把成绩管理系统拆成三张关系表2.1 实体梳理学生、课程、成绩三个实体怎么画ER图学生成绩管理系统的核心业务不复杂学生选课课程被学生选选完课产生成绩。这里天然存在三个实体学生和课程之间是多对多关系多对多联系在关系模型里必须拆成独立的关系表这就是成绩表存在的理由。画ER图时学生是矩形属性有学号、姓名、性别、年龄、系别课程是矩形属性有课程号、课程名、先修课程号、学分学生和课程之间的菱形联系就是“选修”联系本身带两个属性成绩和学期。如果你只会画“一个学生对应多门课”这种一对多图说明关系模型还没理清后面建表必乱。多对多关系转换为关系表的规则是联系变成一张新表两端实体的主键作为这张表的外键联系自身的属性也放进这张表。这张表就是sc表也是整个系统里最值得花时间设计的一张表。2.2 字段先定类型和约束主键、非空、唯一、外键该给谁建表之前先把字段和约束想清楚这步比写代码重要。学生表的主键是学号sno用CHAR定长因为学号位数固定定长比VARCHAR检索略快且避免尾随空格姓名sname必须非空性别可以用ENUM或CHECK约束MySQL 8.0.16以上支持CHECK课设里用ENUM(男,女)最省事年龄sage用TINYINT UNSIGNED足够再加一个默认值18。课程表的主键是课程号cnocname非空ccredit用TINYINT存学分cpno存先修课程号它引用课程表自身的主键这叫自引用外键。成绩表是最容易设计错的地方。常见两种做法一是用(sno, cno)做联合主键二是加一个自增id做主键再给(sno, cno)加唯一约束。联合主键的优点是能物理上防止同一课程重复录入但扩展性差比如以后要记录补考、重修、跨学期的成绩联合主键就装不下了。我一般用自增id做主键再给(sno, cno, semester)建唯一索引既防重复又能支持重修补考。成绩字段用DECIMAL(5,2)而不用FLOAT避免浮点精度问题百分制最大99.99完全够用。下面这个设计对应了三范式学生表不含课程信息课程表不含学生信息成绩表只依赖学号和课程号没有传递依赖。2.3 建库脚本一份可以直接抄作业的SQL把上面的设计落成SQL注意几点库和表统一用utf8mb4否则后面中文乱码从根上就没救外键约束要写清楚级联策略测试数据要造得能覆盖后续查询演示。CREATE DATABASE IF NOT EXISTS score_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE score_db; CREATE TABLE student ( sno CHAR(10) NOT NULL COMMENT 学号, sname VARCHAR(20) NOT NULL COMMENT 姓名, ssex ENUM(男,女) NOT NULL DEFAULT 男 COMMENT 性别, sage TINYINT UNSIGNED DEFAULT 18 COMMENT 年龄, sdept VARCHAR(30) DEFAULT NULL COMMENT 系别, PRIMARY KEY (sno) ) ENGINEInnoDB COMMENT学生表; CREATE TABLE course ( cno CHAR(6) NOT NULL COMMENT 课程号, cname VARCHAR(30) NOT NULL COMMENT 课程名, cpno CHAR(6) DEFAULT NULL COMMENT 先修课程号自引用, ccredit TINYINT UNSIGNED DEFAULT 3 COMMENT 学分, PRIMARY KEY (cno), CONSTRAINT fk_course_cpno FOREIGN KEY (cpno) REFERENCES course(cno) ) ENGINEInnoDB COMMENT课程表; CREATE TABLE sc ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 自增主键, sno CHAR(10) NOT NULL COMMENT 学号, cno CHAR(6) NOT NULL COMMENT 课程号, grade DECIMAL(5,2) DEFAULT NULL COMMENT 百分制成绩, semester VARCHAR(20) NOT NULL COMMENT 开课学期如2024-2025-1, PRIMARY KEY (id), UNIQUE KEY uk_sno_cno_semester (sno, cno, semester), CONSTRAINT fk_sc_student FOREIGN KEY (sno) REFERENCES student(sno) ON DELETE CASCADE, CONSTRAINT fk_sc_course FOREIGN KEY (cno) REFERENCES course(cno) ON DELETE CASCADE ) ENGINEInnoDB COMMENT成绩表;这段SQL里ENGINEInnoDB必须写InnoDB支持事务和外键MyISAM不支持课设里用MyISAM会被扣分。唯一索引uk_sno_cno_semester是关键它保证同一个学生同一学期同一门课只能有一条成绩记录后续JDBC里做重复录入自动覆盖也要依赖这个约束。外键级联策略用了ON DELETE CASCADE这意味着删学生时成绩一并删除业务上省事但如果你考试时被问到“删除学生前要不要先删成绩”要能答出另一种方案业务上先删成绩再删学生级联只是数据库层面的兜底。插入测试数据时有个坑course表的cpno自引用外键如果先插入子课程再插入先修课程外键校验会失败必须先插没有先修课的课程。INSERT INTO student (sno, sname, ssex, sage, sdept) VALUES (2023001001, 张三, 男, 20, 计算机系), (2023001002, 李四, 女, 19, 计算机系), (2023002001, 王五, 男, 21, 软件工程系); INSERT INTO course (cno, cname, cpno, ccredit) VALUES (C001, 数据库原理, NULL, 4), (C002, 数据结构, NULL, 3), (C003, 数据库课程设计, C001, 2); INSERT INTO sc (sno, cno, grade, semester) VALUES (2023001001, C001, 86.50, 2024-2025-1), (2023001001, C002, 92.00, 2024-2025-1), (2023001002, C001, 57.00, 2024-2025-1), (2023002001, C002, NULL, 2024-2025-1);注意sc表里grade允许NULL这是有意的学生选课但还没出成绩时记录先存在成绩为空。后面用LEFT JOIN查询时要特殊处理NULL否则统计会莫名少数据这个坑在避坑章单独讲。插入测试数据要故意造一条不及格和一条NULL不然你答辩时没法演示挂科名单和缺考查询。建完表先不急着写代码在Navicat或命令行里把这三条查询跑一遍验证数据查全体学生、查某学生选课列表、统计每门课平均分。如果这三条SQL出的结果对得上表结构基本就稳了后面写Java只是在消费这个数据模型。3. 用JDBC把增删改查写扎实连接池、PreparedStatement与事务边界3.1 项目结构分层搭对了答辩少挨一次骂课设项目虽然小但分层不能省。常见的Servlet/JSP项目里我看到太多人把所有代码塞进一个Servlet里数据库操作直接写在doGet里答辩被问“MVC三层分别在哪”直接沉默。我习惯的分包是entity放实体类dao放数据库操作service放业务逻辑util放连接工具类。实体类对应三张表Student、Course、Score属性名和表字段一一对应类型也对齐比如sage用Integergrade用BigDecimal不要把DECIMAL映射成double。一个能跑的最小项目结构长这样src/main/java com/coursework/entity/Student.java com/coursework/entity/Course.java com/coursework/entity/Score.java com/coursework/dao/StudentDao.java com/coursework/dao/CourseDao.java com/coursework/dao/ScoreDao.java com/coursework/service/ScoreService.java com/coursework/util/DBUtil.java src/main/resources/druid.propertiesDAO只做单表或跨表的SQL操作service负责组合逻辑util管连接谁该干什么清清楚楚。实体类在这里不贴全给一个Student的写法参考私有属性、无参构造、每个属性的getter/setter其他两个实体同理。属性类型里有一处容易翻车student表的sage在数据库是TINYINTJava里用Integer就行别用int否则从ResultSet取到NULL时拆箱直接空指针。3.2 连接配置放druid.properties为什么课设也值得用连接池很多课设代码是每次操作都DriverManager.getConnection用完再close。功能上能跑但经不起问“你的系统同时100个人访问会怎样”正确答案是你用了连接池。连接池不用懂内部实现但要知道它解决的核心问题复用连接而不是每次新建控制并发连接数而不是放任不管。它像一个黑匣子你用的时候只管借用完记得还。driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/score_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse usernameroot passwordyour_password initialSize2 maxActive8 maxWait3000url里characterEncodingutf8对应建库时的utf8mb4这一对不写全后面中文乱码是必然的。serverTimezoneAsia/Shanghai是给MySQL 8.0的驱动用的不写会报时区错误。initialSize是启动时预建的连接数maxActive是最大活跃连接数maxWait是拿不到连接时的等待毫秒数课设规模下这两个数字够了别调成100。对应DBUtil代码如下public class DBUtil { private static final DruidDataSource ds new DruidDataSource(); static { try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)) { Properties p new Properties(); p.load(in); ds.setDriverClassName(p.getProperty(driverClassName)); ds.setUrl(p.getProperty(url)); ds.setUsername(p.getProperty(username)); ds.setPassword(p.getProperty(password)); ds.setInitialSize(Integer.parseInt(p.getProperty(initialSize))); ds.setMaxActive(Integer.parseInt(p.getProperty(maxActive))); } catch (IOException e) { throw new RuntimeException(加载druid.properties失败, e); } } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }static块在类加载时执行一次所以druid.properties只被读取一次后面每次getConnection都是从池里借连接。如果你不想引Druid用HikariCP也行写法几乎一样。这里有个常见翻车点mysql-connector-java的jar没放进WEB-INF/lib只在IDE里加了依赖代码一部署就报ClassNotFoundException后面避坑章会专门讲。3.3 PreparedStatement做查询SELECT和JOIN的代码与逐行说明写DAO时统一用PreparedStatement不要用Statement字符串拼接。PreparedStatement预编译SQL参数用问号占位既防止SQL注入又避免拼接单引号带来的语法错误。查询所有学生的代码是最基础的模板public ListStudent findAll() { String sql SELECT sno, sname, ssex, sage, sdept FROM student ORDER BY sno; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setSno(rs.getString(sno)); s.setSname(rs.getString(sname)); s.setSsex(rs.getString(ssex)); s.setSage(rs.getInt(sage)); s.setSdept(rs.getString(sdept)); list.add(s); } } catch (SQLException e) { throw new RuntimeException(查询学生列表失败, e); } return list; }这里用了try-with-resourcesConnection、PreparedStatement、ResultSet三个资源自动关闭不用写finally手动close。ResultSet的getInt在字段为NULL时返回0但不会报错getString返回null所以实体类属性用包装类型更安全。rs.next()每调用一次游标下移一行第一次调用前游标指向第一条记录之前的位置这是新手最容易写错的地方。按学号查成绩需要跨三张表JOIN的写法比在Java里多次查询再组装要高效也更好答辩public ListScore findBySno(String sno) { String sql SELECT s.sno, s.sname, c.cname, sc.grade, sc.semester FROM sc JOIN student s ON sc.sno s.sno JOIN course c ON sc.cno c.cno WHERE sc.sno ? ORDER BY sc.semester, c.cno; ListScore list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, sno); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Score sc new Score(); sc.setSno(rs.getString(s.sno)); sc.setSname(rs.getString(s.sname)); sc.setCname(rs.getString(c.cname)); sc.setGrade(rs.getBigDecimal(sc.grade)); sc.setSemester(rs.getString(sc.semester)); list.add(sc); } } } catch (SQLException e) { throw new RuntimeException(查询成绩失败, e); } return list; }JOIN这里用的是INNER JOIN简写只返回sc表里有记录的行。如果你想演示“某门课有选课记录但成绩为空也是有效数据”就要在统计类查询里用LEFT JOIN这个区别是避坑章的重点。ResultSet里取别名的方式是按表名.字段名取getBigDecimal对应数据库的DECIMAL类型比getDouble精度更好。3.4 成绩录入要包事务校验和写入要么都成功要么都失败成绩录入这个操作业务上包含三步检查学生存在、检查课程存在、写入成绩。这三步是一个整体任何一步失败都不能让成绩表多出半截数据所以必须包在事务里。不包事务的后果是学生不存在的脏数据也能写进成绩表外键约束拦不住手写的垃圾数据吗外键能拦住非法学号但你没法区分“误操作”和“故意写入”事务要解决的是操作原子性。代码如下public void saveScore(String sno, String cno, BigDecimal grade, String semester) { String checkStudent SELECT 1 FROM student WHERE sno ?; String checkCourse SELECT 1 FROM course WHERE cno ?; String upsert INSERT INTO sc(sno, cno, grade, semester) VALUES(?, ?, ?, ?) ON DUPLICATE KEY UPDATE grade VALUES(grade); try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(checkStudent); PreparedStatement ps2 conn.prepareStatement(checkCourse); PreparedStatement ps3 conn.prepareStatement(upsert)) { ps1.setString(1, sno); try (ResultSet rs ps1.executeQuery()) { if (!rs.next()) { throw new RuntimeException(学生不存在: sno); } } ps2.setString(1, cno); try (ResultSet rs ps2.executeQuery()) { if (!rs.next()) { throw new RuntimeException(课程不存在: cno); } } ps3.setString(1, sno); ps3.setString(2, cno); ps3.setBigDecimal(3, grade); ps3.setString(4, semester); ps3.executeUpdate(); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } } catch (SQLException e) { throw new RuntimeException(保存成绩失败, e); } }setAutoCommit(false)之后连接进入手动提交模式直到commit才真正写库。任何一步抛异常rollback会把已执行的INSERT撤销。ON DUPLICATE KEY UPDATE依赖第2章建表时加的uk_sno_cno_semester唯一索引重复录入同一学期同门课时不报错而是覆盖旧成绩这叫幂等写入答辩时这么说很加分。如果建表时用的是(sno, cno)联合主键这里连semester一起update的语义会变复杂这也是我坚持自增主键加唯一索引的原因。事务这里要补一句MySQL的InnoDB默认隔离级别是REPEATABLE READ课设场景不用动它但要注意事务里查询到的是事务开始时的快照如果你在一个事务里先查学生再查成绩再写成绩中间别人提交的数据本事务看不到这是正常的不要当成bug查半天。4. 把docx报告写成加分项数据字典、测试用例和ER图的排版顺序4.1 课设文档的标准结构老师拿到docx先翻哪三页一份成绩管理系统课设的docx目录结构通常按这个顺序需求分析、概念结构设计ER图、逻辑结构设计关系模式、物理结构设计存储引擎、索引、应用实现界面截图核心代码、测试测试用例表、总结。老师一天看几十份docx不会逐页读我的经验是他重点看三处ER图画得对不对、关系模式转得规不规范、测试用例是不是真跑过。有些人的docx里放一堆自动生成的代码截图界面密密麻麻ER图却是贴的网图这种一翻就能看出来。docx里每一章要有编号图要有图题表要有表题。ER图推荐用draw.io或者Visio画导出PNG后插进Word别用手机拍手绘图。我在正文里只写设计思路代码块放关键片段不整段贴DAO代码整段贴会让文档显得像代码打印件老师翻到第40页还在看代码会直接失去耐心。4.2 数据字典表格字段、类型、约束要跟建表SQL完全一致数据字典是逻辑结构设计章节的主体也是一份docx含金量最高的部分。每个表一张表列出字段名、数据类型、约束、默认值、说明。重点是“说人话”比如sage的说明写“年龄范围0-127默认18”而不是只写一个“年龄”。成绩表的数据字典我一般这么填字段名数据类型约束默认值说明idINT UNSIGNED主键自增无成绩记录唯一编号snoCHAR(10)非空外键→student.sno无学号cnoCHAR(6)非空外键→course.cno无课程号gradeDECIMAL(5,2)允许NULLNULL百分制成绩NULL表示未出分semesterVARCHAR(20)非空联合唯一(sno,cno,semester)无开课学期写数据字典时有一个血泪教训文档里的表结构要和建表SQL保持同步很多人代码改了三遍文档还是初版答辩时老师对照一看就露馅。所以建表脚本改完第一件事就是同步docx里的数据字典这点小事我栽过现在每次改表都先改文档再改代码。4.3 测试用例设计正常、边界、异常三条路径都要有测试章节不是放两张截图就叫测试要用测试用例表来证明你验证过业务。用例表每行一个场景列是用例编号、测试名称、输入数据、预期结果、实际结果。课设规模不用写几十条覆盖核心业务就行大概列八个用例登录、查询学生、添加学生、删除学生、录入成绩、重复录入成绩、录入不存在的学生、统计不及格名单。这八个里正常、边界、异常都有老师一看就知道你系统状态是健康的。拿成绩录入来举例测试用例可以这么设计用例编号测试名称输入数据预期结果实际结果TC-05正常录入成绩sno2023001001, cnoC001, grade88.5, 学期2024-2025-1插入成功列表出现该记录通过TC-06重复录入同课程成绩同上第二次执行原成绩被覆盖为88.5不新增记录通过TC-07录入不存在学生的成绩sno9999999999提示“学生不存在”不写库通过TC-08录入越界成绩grade101拒绝插入提示成绩范围0-100通过TC-07和TC-08这两条能过说明第3章的事务代码和数据库约束在真实生效。如果grade101也能插进去说明代码里少了校验或者建表时没加CHECK约束这正好暴露你没写第2章说的字段约束逻辑。测试用例表后面除了写“通过”还要配一张对应界面的截图截图里要能看到是你自己数据库里的真实数据别用网图。5. 学生成绩管理系统踩坑实录5个高频问题与排查路径5.1 中文乱码或全是问号先查连接串和字符集两层现象界面上显示的学生姓名是“???”或者Navicat里中文正常但Java查出来乱码。原因两层问题。一是数据库连接串没带characterEncodingutf8JDBC驱动用了默认的latin1解码二是建库时字符集不是utf8mb4表字段继承了库的字符集数据本身就已经坏了。前者是代码问题后者是库的根问题有时候两个同时存在改一个不够。解决先把建库SQL改成第2章的写法库、表、连接串三层统一utf8mb4。如果是已经存在的库可以执行ALTER DATABASE score_db CHARACTER SET utf8mb4再对每张表ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4但注意这只能修复字符集定义已经写入的乱码数据需要重新插入。课设阶段最稳的做法是删库重建反正是测试数据。5.2 删除学生报外键约束失败顺序应该是先成绩后学生现象执行DELETE FROM student WHERE sno2023001001报错Cannot delete or update a parent row但数据库里sc表明明用的是ON DELETE CASCADE。原因有些同学建表时压根没写外键CASCADE或者在建SQL里把外键挪到了ALTER TABLE语句里执行顺序不同导致约束没生效。这个时候删父表记录子表还引用着它数据库当然拒绝。解决不依赖级联在业务代码里先删成绩再删学生两步包在一个事务里写成一个deleteStudent方法。如果数据库外键约束确实存在也可以检查是不是表引擎用了MyISAMMyISAM不支持外键约束是写进去了但不生效。事后验证用SHOW CREATE TABLE student看外键是否真实存在。5.3 查询成绩记录变少或统计数字对不上LEFT JOIN和NULL的锅现象统计每门课选课人数时某门课明明有学生选了但成绩是NULL结果人数就是不对或者JOIN查询出来的成绩列表少了几行。原因常用的INNER JOIN只返回两表都匹配的行成绩为NULL的行如果关联条件成立照样会返回问题往往出在统计时COUNT(grade)不统计NULL值。如果你用的是COUNT(sc.sno)那选课人数量是对的但如果你用COUNT(grade)统计已出分人数NA会直接丢弃NULL两门课比较时数字就差一截。另一个情况是选课表里根本没有这门课的记录JOIN自然查不到。解决明确每种查询的业务含义。查“选课名单”用LEFT JOIN从course表出发左表是课程右表是成绩这样没选过课的课程也会显示出来grade为NULL代表无人选或未出分。统计人数时用COUNT(sc.sno)统计平均分时AVG会自动忽略NULL但你别忘了在docx里注明“平均分只统计已出成绩”。这个坑的排查思路先分别查选课表和外键表记录数再对比JOIN结果差多少一目了然。5.4 MySQL连接数越来越多程序卡住连接没还回去现象系统跑一会儿后所有页面都卡死看MySQL的SHOW PROCESSLIST发现几十个Sleep连接堆积Navicat也连不上了。原因代码里每次DBUtil.getConnection()借来的连接用完后没有close或者框架里手动打开的连接在return之前又执行了一些查询导致连接对象没有回到池里。每借一个不还池子最终被借空maxActive等于几个并发系统就彻底不响应。这不是数据库死锁是连接泄漏。解决所有数据库操作一律用try-with-resources或者手动在finally里close。写一个公共的close方法传入Connection、Statement、ResultSet三个资源判空关闭。还有一个常见误用有些人把Connection对象塞到Servlet的成员变量里多线程共享同一个连接这也是泄漏和并发错乱的来源。连接必须是方法内的局部变量用完即还。5.5 并发录入同一门成绩报死锁重试一次就好现象两个窗口同时给同一个学生录入同一门课成绩一个报Deadlock found when trying to get lock; try restarting transaction。原因成绩表有唯一索引uk_sno_cno_semester两个事务并发插入相同唯一键时InnoDB要检测重复会在唯一索引上加插入意向锁两个事务互相等对方的锁触发死锁被检测后一个事务被回滚。这不是你的代码写错了是并发场景下的正常现象只是课设没人考虑并发所以一遇到就懵。解决捕获死锁异常重试一次。用Spring的Retryable最方便不想引框架就在catch里判断异常消息含不含Deadlock含就延迟50毫秒重新调用saveScore。要注意的是重试前必须把原事务回滚干净否则第二次操作会基于脏数据继续。还有一种规避方案是先查再插而不是直接ON DUPLICATE KEY UPDATE但先查再插仍有并发窗口不彻底重试才是正确姿势。答辩时能主动说出“我在唯一索引冲突场景下做了死锁重试”比跑通一个CRUD感人多了。6. 答辩前的SQL自检三条查询验证数据完整性与索引设计这一套做完代码已经能跑docx也写好了但我不建议直接交。答辩老师最喜欢问的是“你怎么验证你的系统数据是对的”这时候口头说“我测试了”没有说服力你要现场跑几条SQL证明你的表结构经得起推敲。第一条挂科名单验证业务闭环每个不及格学生和他不及格的课程一起列出来。这条SQL用到了三表JOIN、条件过滤和排序能跑通说明外键关系没配错数据也是真实落库的。SELECT s.sno, s.sname, c.cname, sc.grade FROM sc JOIN student s ON sc.sno s.sno JOIN course c ON sc.cno c.cno WHERE sc.semester 2024-2025-1 AND sc.grade 60 ORDER BY sc.grade;第二条统计每门课的选课人数和及格人数用来验证LEFT JOIN和GROUP BY的配合。注意用了LEFT JOIN课程表在左边这样就算一门课没有任何成绩记录也能显示选课人数为0不会把整行丢掉。SELECT c.cno, c.cname, COUNT(sc.sno) AS total_students, COALESCE(SUM(CASE WHEN sc.grade 60 THEN 1 ELSE 0 END), 0) AS pass_count FROM course c LEFT JOIN sc ON sc.cno c.cno GROUP BY c.cno, c.cname ORDER BY total_students DESC;第三条用EXPLAIN看索引是否生效。这条要刻意录一条成绩为NULL的学号然后解释计划里type是不是从ALL变成了refkey是不是用到了外键索引。EXPLAIN SELECT * FROM sc WHERE sno 2023001001;EXPLAIN结果里如果type是ALL说明全表扫描数据量小看不出差别但你在docx里写上这条验证记录就相当于主动告诉老师“我做过数据库优化”。要造大量数据测试的话可以用存储过程循环插入几千行成绩或者写个Java批处理跑几百次saveScore我一般用后者因为事务代码一起被验证了。这套自检是我踩过几次答辩坑之后固化的流程。以前我总在交之前拼命调界面样式后来发现老师真正问的都是数据层面的问题成绩表为什么加唯一索引、LEFT JOIN和INNER JOIN的区别、事务回滚的条件。现在我做这类课设最后半天永远留给SQL自检而不是改截图这个习惯帮我避掉了不少翻车。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号