
简介一份基于MySQLJava的数据库课程设计学生选课信息管理系统完整资源覆盖学生、教师、管理员三类角色适用于高校数据库课程设计或毕业设计参考。系统采用C/S架构功能涵盖学生选课退课、成绩查询、教师成绩录入、管理员课程与选课管理等典型业务模块。资源为rar压缩包约1.99MB主要包含Java源代码工程与配套设计报告设计报告中对数据库建表结构、表间关系进行了合理规范的设计说明方便对照学习从需求分析到表结构落地的完整流程。由于包体较小适合快速下载查阅目前已有6064人学习下载。除了可直接运行功能外配套设计报告还能帮助读者理清学生选课场景下的权限划分、数据表设计原则以及Java与MySQL的交互方式对完成同类课程设计具有较强的参考价值。1. 学生选课管理系统值不值得做先想清楚“选课”为什么是数据库课设的首选很多同学拿到“学生选课信息管理系统”这个题目第一反应是去搜源代码搜到一套 Java MySQL 的东西就开始跑。但实际交上去之后代码能跑、页面能开答辩却一问就卡壳——老师随口问一句“选课表为什么用联合主键”“两个人同时选同一门课会不会重复”你答不上来分数就下去了。这个系统的核心是 MySQL 里的表关系设计和 Java 侧的事务控制不是那几十个页面。学生选课本质上就是一个“先查容量、再插入记录、最后更新余量”的过程三个动作必须在一个事务里完成。数据库设计对了Java 代码写起来会很顺数据库设计错了后面怎么改都是补丁。这也是这门课设最有价值的点用最小规模的系统把现实系统里最常见的并发、约束、事务问题全暴露出来。本文从建表到 Java 实现、再到设计报告给你一条能直接复现的路径。2. 数据库模型设计MySQL 里这几张表的结构决定你后面好不好写代码写得糙一点后面还能修表建得不合理改了表结构就等于重写。选课系统涉及的角色就三类学生、教师、管理员但真正复杂的不是角色而是角色之间的选课关系。先想清楚实体和关系再落 DDL。2.1 从需求反推实体学生、教师、课程、选课记录一张都不能省常见的错误做法是把所有信息塞到一张大表里字段包含学号、姓名、密码、系别、课程名、老师、学分、成绩……这种设计在课设验收时一眼就会被看穿因为第三范式不过关。拆分之后最少也需要四张表student学生信息学号做主键teacher教师信息教师编号做主键course课程信息课程号做主键教师编号做外键sc选课记录学号 课程号做联合主键成绩字段允许为空课程表里存 teacher_no 而不是直接写死老师姓名这是典型的规范化思路。如果老师调动只需要改 teacher 表如果直接在 course 表里写“张三”那张三改名或者换人所有课程记录都要跟着改。课设里老师喜欢问这类“为什么这么拆”的问题答案就一句话减少数据冗余消除修改异常。选课记录表 sc 是这套系统的核心它的字段设计决定了后面所有业务好不好写。最关键的三个字段是 sno、cno、scoresno 和 cno 联合作为主键。为什么不用自增 id因为业务上“一个学生选一门课只能有一条记录”是硬约束用自增 id 无法阻止同一对学号课程号重复出现而联合主键天然防重复。2.2 为什么选课表必须加联合唯一约束很多同学的做法是插入之前先 SELECT 查一下有没有选过没选过就 INSERT。这在单用户测试时完全正常但答辩老师只要问一句“两个浏览器同时点选课怎么办”这套逻辑就露馅了。查和插是两条独立语句中间任何一个瞬间都可能被另一个请求插进来造成重复选课。正确做法是让 MySQL 在数据库层面兜底sc 表用 (sno, cno) 做主键。这样无论 Java 代码怎么写数据库都不允许同一学生同一课程出现两条记录。业务层可以先查但那是为了给出友好提示数据库约束才是最后一道防线。另外一个容易被忽略的点联合主键是 (sno, cno)那按 cno 单独查索引时是走不了联合索引的。如果系统里有“按课程查选了哪些学生”的功能要给 cno 建一个普通索引。联合主键的最左前缀原则决定了 sno 开头的查询可以用索引但 cno 开头的查询是全表扫描。这个细节写进设计报告里答辩时价值很高。2.3 建库建表 SQL引擎、字符集与关键参数一次配好存储引擎选 InnoDB 而不是默认的 MyISAM理由有两条支持事务、支持外键。选课场景里“插入选课记录”和“更新已选人数”必须同时成功或同时失败MyISAM 不具备这个能力。字符集统一用 utf8mb4不要为了省空间用 latin1否则中文极容易变成乱码。CREATE DATABASE course_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE course_db; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE student ( sno VARCHAR(20) PRIMARY KEY, name VARCHAR(30) NOT NULL, password VARCHAR(64) NOT NULL, dept VARCHAR(50), grade VARCHAR(10) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE teacher ( tno VARCHAR(10) PRIMARY KEY, name VARCHAR(30) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( cno VARCHAR(10) PRIMARY KEY, cname VARCHAR(50) NOT NULL, credit DECIMAL(3,1), teacher_no VARCHAR(10), capacity INT NOT NULL DEFAULT 40, chosen INT NOT NULL DEFAULT 0, start_time VARCHAR(100), CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_no) REFERENCES teacher(tno) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sc ( sno VARCHAR(20) NOT NULL, cno VARCHAR(10) NOT NULL, score DECIMAL(5,1) DEFAULT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (sno, cno), KEY idx_cno (cno), 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 RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个参数说明course 表里 capacity 表示课程容量chosen 表示当前已选人数这两个字段是后面选课事务的核心。chosen 其实属于冗余字段因为通过 COUNT(*) 也能算出来但课设里保留它能让事务逻辑更直观。sc 表的外键策略分成两种学生删除时级联删除选课记录因为学生没了选课记录没意义课程删除时用 RESTRICT因为如果还有人选着这门课强制删除会破坏选课记录必须先处理选课记录再删课程。表建好后需要塞入一些模拟数据供 Java 侧联调。手写几十条 INSERT 太慢可以用 MySQL 的存储过程批量生成。下面这段生成 500 个学生的脚本在课设里很实用一方面能真实测出分页效果另一方面也向老师证明你做过数据量层面的验证。DROP PROCEDURE IF EXISTS generate_students; DELIMITER $$ CREATE PROCEDURE generate_students(IN stu_count INT) BEGIN DECLARE i INT DEFAULT 1; WHILE i stu_count DO INSERT INTO student (sno, name, password, dept, grade) VALUES (CONCAT(2024, LPAD(i, 4, 0)), CONCAT(学生, i), MD5(123456), 计算机学院, 2024); SET i i 1; END WHILE; END$$ DELIMITER ; CALL generate_students(500); INSERT INTO admin (username, password) VALUES (admin, MD5(admin123));学号用 CONCAT(2024, LPAD(i, 4, 0)) 生成结果就是 20240001 到 20240500格式统一且排序自然。LPAD 的作用是左侧补零保证 1 变成 0001而不是出现 20241 这种四不像的学号。密码统一用 MD5(123456) 存储下面第三部分会讲为什么密码不能存明文。3. 用 JDBC 把 Java 和 MySQL 连起来连接参数、DAO 分层与事务边界数据库部分定下来之后Java 侧最核心的就是 JDBC 连接管理与 SQL 访问方式。课设里用纯 JDBC Servlet 就能完成不需要上 MyBatis 或 Hibernate。把数据库访问单独封装一层业务层只面向 DAO 接口编程这是代码结构上最实用的习惯。3.1 驱动选择与 JDBC 连接参数在 Java 代码里怎么写MySQL 5.x 和 8.x 的 JDBC 驱动类名不一样。5.x 时代用的是 com.mysql.jdbc.Driver8.x 之后变成了 com.mysql.cj.jdbc.Driver。很多同学搜到老教程把自己的 MySQL 8.0 配上了旧驱动类名启动时报 ClassNotFoundException这是课设里出现频率最高的报错之一。如果你是在 Windows 上自己装的 MySQL 8.0驱动 jar 用 mysql-connector-java 8.x类名必须写带 cj 的那个。连接参数是另一处容易翻车的地方。同样一张表同样的 JDBC URL在 MySQL 8.0 上如果不带时区参数连接时会直接报错。下面这段是标准的连接写法private static final String URL jdbc:mysql://localhost:3306/course_db ?useSSLfalse serverTimezoneAsia/Shanghai characterEncodingutf8mb4 connectTimeout5000 rewriteBatchedStatementstrue; private static final String USER root; private static final String PASSWORD your_password;每个参数都对应一个真实的问题useSSLfalse 是因为本地开发没必要做 SSL 握手加上去反而慢serverTimezoneAsia/Shanghai 解决 MySQL 8.x 时差报错characterEncodingutf8mb4 保证中文写入不乱码connectTimeout5000 让连接失败时快速报错而不是让页面卡在半分钟rewriteBatchedStatementstrue 是给批量导入学生数据用的让 addBatch 真正走批量模式而不是逐条执行。这些参数最好不要写死在类里而是放在 src 下的 jdbc.properties 文件里用 java.util.Properties 读取。答辩时老师翻代码看到配置项集中管理而不是散落在各个类里印象分会差不少。3.2 DBUtil 与连接释放每个方法都必须关干净课设里最常见的 JDBC 工具类写法是提供一个静态方法返回连接同时提供一个 close 方法接收 Statement、ResultSet、Connection。关键点是 close 必须放在 finally 或 try-with-resources 里否则数据库连接会被耗尽运行一段时间后所有查询全部卡住。public class DBUtil { private static String URL; private static String USER; private static String PASSWORD; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(jdbc.properties)) { Properties props new Properties(); props.load(in); URL props.getProperty(url); USER props.getProperty(username); PASSWORD props.getProperty(password); Class.forName(com.mysql.cj.jdbc.Driver); } catch (Exception e) { throw new ExceptionInInitializerError(数据库初始化失败检查配置文件); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) { } } } } }这里每调用一次 getConnection 就新建一个连接用完立即关闭适合课设这种低并发场景。不要在工具类里定义一个静态 Connection 字段然后所有方法共用那样一旦一个请求忘记关闭整个应用就瘫痪了。DAO 层代码用 try-with-resources 或者 finally 里调用 DBUtil.close二选一但必须做到位。3.3 PreparedStatement为什么拼字符串是最容易翻车的写法很多初学者习惯用字符串拼接 SQLSELECT * FROM student WHERE sno sno 。这种写法在值里出现单引号时就会破坏 SQL 语法更严重的是存在注入风险。课程设计的登录功能如果被老师用 OR 11试一下直接以管理员身份登录进去这个答辩基本就挂了一半。PreparedStatement 的好处有三个SQL 结构预先编译参数通过 setString 传入特殊字符会被转义可读性比拼接字符串强参数一目了然同一个 SQL 多次执行时能利用数据库服务端的预编译能力。唯一要注意的是参数索引从 1 开始这个和数组下标习惯不一样。String sql SELECT sno, name, dept, grade FROM student WHERE sno ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, sno); ps.setString(2, md5Password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Student stu new Student(); stu.setSno(rs.getString(sno)); stu.setName(rs.getString(name)); return stu; } } }setString 永远处理的是字符串类型如果查询条件是 int 就用 setInt不要依赖 MySQL 的隐式类型转换。还有一种隐蔽的问题是查出来的列名大小写MySQL 在 Windows 上列名不区分大小写但代码里最好按建表时的字段名原样书写少给自己留排查负担。另外这一段的 try-with-resources 写法会自动关闭 Connection、PreparedStatement、ResultSet比手写 finally 再层层判空简洁得多推荐直接使用。4. 核心流程的 Java 实现登录、选课、退课、成绩录入与课表查询表结构和 DAO 层就位后剩下就是把业务逻辑串起来。这套系统里有几条核心链路必须走事务选课要更新两张表退课同样。登录和查询不需要事务但要注意密码处理和 session 权限控制。4.1 登录与角色分发密码不能存明文session 里的身份标记怎么用登录功能涉及两个表admin 表管理管理员student 表管理学生。实际做法是写一个统一认证的 DAO先后查 admin 和 student命中哪个角色就跳转到对应的首页。但有一个底线是密码不能明文存储即使课设里不要求加密也要用 MD5 摘要或至少加盐哈希。前面建表时我们存储的就是 MD5(123456)登录时把用户输入的密码也做 MD5 再比较这样数据库被翻出来也不会直接暴露密码。public User login(String username, String password) { String md5Pwd MD5Util.md5(password); User user findAdminByUsernameAndPwd(username, md5Pwd); if (user ! null) { return user; } return findStudentBySnoAndPwd(username, md5Pwd); }登录成功后一定要把用户身份放进 session例如session.setAttribute(role, admin)和session.setAttribute(loginName, username)。重点是受限页面必须校验 session 里的 role。Java 里最简单的方式是写一个过滤器对所有 /admin/ 开头的请求检查 role 是否 admin不是就重定向到登录页。如果不做这一步任何知道页面 URL 的人都能绕过登录直接访问管理员功能。4.2 选课的完整事务先锁行、再查容量、后插入、最后提交选课是整个系统含金量最高的一个功能也是答辩时最容易提问的点。业务要求是学生选课时不能超过课程容量同一学生不能重复选同一课程。先查容量再插入中间要求原子性因此必须用事务。这里有一个非常关键的细节事务边界要放在 Service 层而不是 DAO 层。如果 DAO 里每个方法分别获取连接和提交事务那“查容量”和“插入选课记录”就分属两个事务中途断电或抛异常就会产生错乱数据。public boolean selectCourse(String sno, String cno) { Connection conn null; PreparedStatement lockStmt null; PreparedStatement insertStmt null; PreparedStatement updateStmt null; PreparedStatement checkStmt null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); checkStmt conn.prepareStatement( SELECT COUNT(*) FROM sc WHERE sno ? AND cno ?); checkStmt.setString(1, sno); checkStmt.setString(2, cno); try (ResultSet rsCheck checkStmt.executeQuery()) { rsCheck.next(); if (rsCheck.getInt(1) 0) { conn.rollback(); return false; } } lockStmt conn.prepareStatement( SELECT capacity, chosen FROM course WHERE cno ? FOR UPDATE); lockStmt.setString(1, cno); rs lockStmt.executeQuery(); if (!rs.next()) { conn.rollback(); return false; } int capacity rs.getInt(capacity); int chosen rs.getInt(chosen); if (chosen capacity) { conn.rollback(); return false; } insertStmt conn.prepareStatement( INSERT INTO sc (sno, cno) VALUES (?, ?)); insertStmt.setString(1, sno); insertStmt.setString(2, cno); insertStmt.executeUpdate(); updateStmt conn.prepareStatement( UPDATE course SET chosen chosen 1 WHERE cno ?); updateStmt.setString(1, cno); updateStmt.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { rollbackQuietly(conn); e.printStackTrace(); return false; } finally { DBUtil.close(rs, lockStmt, insertStmt, updateStmt, checkStmt); closeQuietly(conn); } }代码逻辑分四步先查是否重复选课再用 FOR UPDATE 锁住课程行然后插入选课记录最后把已选人数加一。FOR UPDATE 锁行的目的是让并发的选课请求排队两个学生同时选同一门还有 1 个余量的课时第一个事务锁住 course 行第二个事务必须等第一个提交或回滚后才能读到数据从而避免把最后 1 个名额超卖。这个点写进设计报告或答辩陈述是很大的加分项。有两处细节要说明。第一UPDATE 语句写的是chosen chosen 1而不是先在 Java 里把 chosen 读出来加一再写回去。写在 SQL 里的自增是原子的不会覆盖别人的修改。第二检查重复选课用的是 COUNT 子查询但真正的硬约束仍然是 sc 表的联合主键。一旦两个事务同时通过 COUNT 检查插入时也会有一个事务因主键冲突而抛出 DuplicateKeyException从而回滚。数据库约束兜底比代码判断更可靠。4.3 退课与成绩录入外键在这两条路径上扮演的角色退课的逻辑是选课的逆过程删除 sc 记录同时把 course.chosen 减一。这两步同样要包在事务里否则会出现选课记录删了但人数没减。退课 SQL 简单但要注意删除顺序先删 sc再更新 course。反过来操作就会短暂出现课程人数多算一人的中间状态如果第二步失败不一致就固化了。成绩录入是管理员功能选中学号、课程号把成绩写进 sc 表。这个操作不涉及容量变更但有一个常见的错误是把成绩更新到了 course 表。成绩是属于某个学生选某门课这个关系的属性必须存 sc 表。录入前要查当前选了这门课的学生列表这个查询是三表关联中最经典的一类SELECT s.sno, s.name, s.dept, sc.score FROM sc JOIN student s ON sc.sno s.sno WHERE sc.cno ? ORDER BY s.sno;注意这里 JOIN 的顺序先 sc 和 student 关联用 cno 过滤。sc 表是事实表student 表是维度表。很多同学写这个查询时会先从 sc 查 cno再帮每个 sno 查一次 student造成 N1 次查询。课设代码里看不出性能差异但老师追问时你的回答会暴露是否真正理解 JOIN 的作用。4.4 课表与成绩单查询JOIN 和 ORDER BY 怎么配合学生在首页要看到自己的课表这个查询是按 sno 查 sc 再 JOIN course。一个更经典的查询是“可选课程列表”从 course 表里过滤掉这个学生已经选过的课程。SELECT c.cno, c.cname, c.credit, t.name AS teacher_name, c.capacity, c.chosen FROM course c JOIN teacher t ON c.teacher_no t.tno WHERE c.cno NOT IN ( SELECT cno FROM sc WHERE sno ? ) ORDER BY c.cno;NOT IN 子查询在课设数据量下没有任何问题直接使用即可。如果以后数据量到几万门课可以改成 LEFT JOIN 加 IS NULL 的写法但那是后话。这里重点说 ORDER BY数据库排序发生在查询阶段而不是 Java 拿到结果后再排序。如果在 Java 侧用 Collections.sort 排序说明 SQL 本身就写歪了。课设里课程列表要有默认排序按 cno 排即可保证每次刷新顺序一致。分页是另一个躲不开的功能MySQL 的分页基本形态是 LIMIT 加 OFFSETSELECT c.cno, c.cname, c.credit, c.capacity, c.chosen FROM course c ORDER BY c.cno LIMIT ? OFFSET ?;第一页是 LIMIT 10 OFFSET 0第二页是 LIMIT 10 OFFSET 10。注意 OFFSET 过大时性能会下降因为 MySQL 必须跳过越多的行才能拿到目标位置这是 LIMIT 分页的固有限制课设里不需要优化但答辩时被问到要知道问题在哪。5. 从“能跑”到“不翻车”MySQLJava 课设最常见的 5 个坑这段内容全是血泪经验。每个坑我都按“现象 → 原因 → 解决”的顺序写你迟早会遇到至少其中两个。5.1 中文乱码页面显示“???”或方框现象学生姓名、课程名存入 MySQL 后变成问号页面上读出来也是乱码。原因通常有三种数据库或表本身是 latin1 字符集JDBC URL 里没带 characterEncodingutf8mb4连接时客户端字符集不匹配。解决第一步确认建库语句是不是带 DEFAULT CHARACTER SET utf8mb4已经建好的库用 ALTER DATABASE course_db CHARACTER SET utf8mb4 转一下第二步检查 JDBC URL必须带编码参数第三步在连接后执行SET NAMES utf8mb4或者给连接初始化 SQL。这三步做完乱码基本消失。最隐蔽的一种是 MySQL 5.7 的配置文件里 character_set_server 还是 latin1这种情况要去 my.ini 里改配置重启服务。5.2 重复选课同一学生同一课程出现两条记录现象学生选课时快速点了两次提交数据库里出现两行完全相同的选课记录课程已选人数也翻倍了。原因业务层先 SELECT 再 INSERT 的流程存在时间窗口两个请求同时通过检查。解决sc 表必须用 (sno, cno) 联合主键这是数据库层面的硬约束。就算代码漏了查重第二条 INSERT 也会因为主键冲突直接抛异常事务回滚后数据不会坏。另外如果项目里用了 MyBatis 之类的框架记住框架的缓存不会帮你处理数据库约束约束必须建在表上。5.3 外键导致删不掉、改不了课程现象管理员想删除一门课控制台报 Error 1451Cannot delete or update a parent row。原因sc 表里还有学生选着这门课外键约束默认的 ON DELETE RESTRICT 不允许直接删除父表记录。解决按业务逻辑先删除选课记录再删除课程两步包在事务里。或者建表时把 sc 表的课程外键设成 ON DELETE CASCADE这样删课程时自动清理选课记录。从数据安全角度我推荐前者因为 CASCADE 会在你只是想删一门课时悄悄把所有学生的选课记录抹掉出问题后没有后悔药。5.4 更换 MySQL 版本后 JDBC 驱动加载失败现象MySQL 从 5.7 升到 8.0项目启动时报 ClassNotFoundException: com.mysql.jdbc.Driver或者报 Communications link failure。原因MySQL 8.x 把驱动类名改成了 com.mysql.cj.jdbc.Driver旧版驱动类路径已经不存在同时 8.x 要求 URL 中必须有时区参数。解决确认 mysql-connector-java 的 jar 版本与 MySQL 服务端对应8.x 的 MySQL 必须用 8.x 的驱动并在 URL 末尾补上serverTimezoneAsia/Shanghai。这里要注意不要把高版本驱动 jar 用在 5.7 服务端上通常也会报协议不兼容的错误。5.5 选课成功但“已选人数”不变现象sc 表里选课记录插入成功但 course.chosen 字段始终是初始值。原因有两个方向第一个是事务里 UPDATE 语句执行失败但异常被吞掉外层 commit 提交了一个不完整的变更第二个是 UPDATE 写成了chosen ?而问号绑定的值是从旧数据查出来的两个学生同时选课时互相覆盖。解决UPDATE 语句必须写chosen chosen 1让 MySQL 自己原子计算同时把事务改成先插入 sc再更新 course最后 commit任何一个步骤失败都 rollback。测试时开两个浏览器无痕窗口同时选同一门课看是否出现人数多算或漏算。6. 设计报告怎么写才能让答辩老师挑不出毛病整套系统的代码只是完成了一半课设另一半是设计报告。报告既不是把代码截图贴满几十页也不是写一堆百度来的概念。评审老师通常会在五分钟内翻完报告直接看你是否理解了表结构设计、事务边界和功能测试结果。6.1 设计报告里的六个必写板块与它们对应的细节按照课程设计的常见要求报告从需求分析到总结通常有六块需求分析、概念结构设计、逻辑结构设计、功能设计、系统实现、测试与分析。我建议在概念结构设计里画一张简单的 ER 图实体就四个学生、教师、课程、选课记录关系就是“学生选课程”“教师教课程”把主键和联系属性标清楚即可不需要画得非常专业。逻辑结构设计部分要放一张表结构清单列名、类型、是否主键、是否外键、约束说明都要有。测试分析部分不要只写“测试通过”要给出一张测试用例表包含测试项目、输入数据、预期结果、实际结果、是否通过。我一般会在测试表里专门加两条重复选课是否被拒绝、课程容量满员后是否还能选成功。这两条对应的是数据库唯一约束和事务逻辑是答辩老师最喜欢追问的点。6.2 三个低成本加分改造分页、唯一约束、连接池如果想让成绩从“良”变“优”下面这三个点选一个做进去就够。分页已经在上一章写了 SQL 形态把它做进课程列表即可。唯一约束不用改代码把第 2 章 DDL 里的联合主键展示在报告里并写明它的作用。连接池是第三个方向课设里不引入也没问题但可以在报告中写一句正式生产环境会用 HikariCP 或 Druid 管理连接代替 DriverManager 每次新建连接的模式。这样表达出你知道 JDBC 直连的局限性就够了不一定真的改代码。我自己的习惯是代码提交前用 Navicat 或命令行的 source 重新执行一遍全部 DDL确认数据库可以从零重建然后把设计报告里的表结构和实际数据库导入出来对比。这一步能避免答辩时老师现场要求重置数据库结果你发现脚本根本跑不通。最后记住一个关键原则所有功能列表里写的功能代码里必须有对应路径报告里写的表结构数据库里必须一模一样。对不上比做得少更扣分。希望这些内容对你有帮助。做课设的时候先别急着找现成代码把第 2 章的建表语句手敲一遍再动手写 Java很多问题就会在设计阶段消失。本文还有配套的精品资源点击获取