简介本资源是一套完整的数据库课程设计实践项目面向计算机专业本科生及Java Web初学者聚焦数据库应用开发核心能力训练解决学生从理论建模到CS架构系统落地的实践断层问题。压缩包共98个文件含21个Java源码、66个编译后Class字节码、1个MySQL建表脚本educate.sql、1个SQLite数据库文件educateSystem.db、2个Word设计报告文档及配套XML配置、JAR依赖库等完整覆盖需求分析、ER建模、SQL实现、三层架构编码与测试全流程。资源包仅1.98MB轻量易部署目录结构规范src与bin分离清晰便于理解MVC分层逻辑与数据库交互机制。已有11832人学习下载提供可直接运行的选课系统含学生/教师/管理员三角色权限体系、完整设计报告及合理建表规范是数据库原理、Java桌面应用与课程设计答辩的优质参考范例。1. 为什么学生选课系统不是“练手小项目”而是数据库与Java工程能力的交叉验证场你写过CRUD也连过JDBC但真把「学生选课」四个字落地成一个能跑通、能查重、能防并发、能回滚、能被教务老师当真用的系统——90%的人卡在第三步数据一致性崩了。这不是简单的“增删改查”堆砌一个学生同一学期不能重复选同一门课一门课容量满了就不能再加人退课后学分要实时更新教师课表和学生课表必须双向同步……这些业务规则全靠数据库约束Java事务控制双保险兜底。我带过三届课程设计最常翻车的不是SQL写错而是Java里没关PreparedStatement、MySQL没设READ_COMMITTED隔离级别、外键没加ON DELETE CASCADE导致级联失败。本篇不讲理论模型只拆解从ER图到可部署war包的完整链路怎么用MySQL原生特性而非Java硬编码守住数据底线怎么让Java代码真正成为数据库的“合规操作员”而不是“越权代理”。适合正在做数据库课设、想避开答辩翻车、或需要交付可演示系统的本科生与初阶开发者。2. 用MySQL建模从ER图到带约束的物理表每一步都在为Java减负2.1 先画清三张核心表的依赖关系再动手建库学生选课系统本质是典型的“多对多”关系实体化场景。必须拆出三张基础表student学生、course课程、selection选课记录。关键不是字段罗列而是约束前置——把校验逻辑交给MySQL而不是等Java抛异常再回滚。例如student.student_id必须是主键且自增同时作为外键出现在selection中course.course_id同样为主键且course.credit需设CHECK(credit BETWEEN 1 AND 8)selection表必须设联合主键(student_id, course_id)并添加UNIQUE(student_id, course_id)防止重复选课所有外键必须显式声明ON DELETE CASCADE和ON UPDATE CASCADE否则删除学生时选课记录残留教务查账直接报错。提示别用Navicat图形界面点点点建表。所有约束必须写进CREATE TABLE语句否则导出SQL脚本时约束丢失答辩现场还原环境必崩。2.2 建库脚本带注释的最小可行DDL复制即用-- 创建数据库UTF8MB4支持中文姓名/课程名 CREATE DATABASE IF NOT EXISTS student_selection DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_selection; -- 学生表学号为主键姓名非空入学年份带检查 CREATE TABLE student ( student_id VARCHAR(12) PRIMARY KEY COMMENT 学号如20230001, name VARCHAR(20) NOT NULL COMMENT 姓名, gender ENUM(男,女) DEFAULT 男, enrollment_year YEAR NOT NULL CHECK (enrollment_year 2018 AND enrollment_year YEAR(NOW())), major VARCHAR(30) COMMENT 专业 ) ENGINEInnoDB COMMENT学生基本信息表; -- 课程表课程号为主键学分带范围检查开课学期用字符串避免日期计算复杂度 CREATE TABLE course ( course_id VARCHAR(10) PRIMARY KEY COMMENT 课程号如CS101, course_name VARCHAR(50) NOT NULL COMMENT 课程名称, credit TINYINT NOT NULL CHECK (credit BETWEEN 1 AND 8) COMMENT 学分, semester VARCHAR(10) NOT NULL COMMENT 开课学期如2023-2, teacher VARCHAR(20) COMMENT 授课教师 ) ENGINEInnoDB COMMENT课程信息表; -- 选课记录表联合主键外键级联唯一约束时间戳 CREATE TABLE selection ( student_id VARCHAR(12) NOT NULL COMMENT 学生学号, course_id VARCHAR(10) NOT NULL COMMENT 课程号, selection_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, grade TINYINT NULL CHECK (grade BETWEEN 0 AND 100) COMMENT 成绩NULL表示未录入, PRIMARY KEY (student_id, course_id), UNIQUE KEY uk_student_course (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(student_id) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (course_id) REFERENCES course(course_id) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINEInnoDB COMMENT学生选课记录表;逻辑说明ENGINEInnoDB是强制要求——只有它支持外键和事务MyISAM在此场景下等于裸奔CHECK约束在MySQL 8.0.16才完全生效若用旧版如5.7需用触发器替代但本篇默认环境为MySQL 8.0semester VARCHAR(10)不用DATE类型因学期格式为“2023-2”而非标准日期避免Java端解析歧义selection_time设DEFAULT CURRENT_TIMESTAMP确保每条记录自带时间戳后续统计选课高峰、查重审计全靠它。2.3 插入测试数据用INSERT SELECT生成真实感数据避开手动造脏别一行行INSERT填100个学生——用MySQL内置函数批量生成-- 批量插入20个测试学生学号20230001~20230020姓名用随机汉字组合 INSERT INTO student (student_id, name, gender, enrollment_year, major) SELECT CONCAT(2023, LPAD(seq, 4, 0)) AS student_id, CONCAT(ELT(FLOOR(1 RAND() * 3), 张, 李, 王), ELT(FLOOR(1 RAND() * 5), 伟, 芳, 娜, 敏, 静)) AS name, ELT(FLOOR(1 RAND() * 2), 男, 女) AS gender, 2023 AS enrollment_year, ELT(FLOOR(1 RAND() * 4), 计算机科学, 软件工程, 数据科学, 人工智能) AS major FROM ( SELECT 1 AS seq UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL SELECT 20 ) AS nums; -- 插入10门测试课程 INSERT INTO course (course_id, course_name, credit, semester, teacher) VALUES (CS101, 数据库原理, 3, 2023-2, 刘教授), (CS102, Java程序设计, 4, 2023-2, 陈老师), (MA201, 高等数学, 5, 2023-2, 赵教授), (EN101, 大学英语, 2, 2023-2, 孙老师), (PH301, 大学物理, 4, 2023-2, 周教授), (DS201, 数据结构, 4, 2023-2, 吴老师), (OS301, 操作系统, 4, 2023-2, 郑教授), (NW401, 计算机网络, 3, 2023-2, 王老师), (SE501, 软件工程, 3, 2023-2, 冯教授), (AI601, 人工智能导论, 3, 2023-2, 杨教授); -- 随机生成选课记录每个学生最多选5门避免超容 INSERT INTO selection (student_id, course_id) SELECT s.student_id, c.course_id FROM student s CROSS JOIN course c WHERE RAND() 0.3 -- 控制选课密度约30% AND NOT EXISTS ( SELECT 1 FROM selection s2 WHERE s2.student_id s.student_id AND s2.course_id c.course_id ) LIMIT 150; -- 总记录数上限参数说明LPAD(seq, 4, 0)确保学号后四位补零如20230001ELT()函数从预设列表中随机取值比RAND()直接生成数字更可控CROSS JOIN配合RAND() 0.3模拟真实选课分布避免全连接爆炸NOT EXISTS子查询防止同一学生重复选同一门课——这是MySQL层面对UNIQUE约束的二次校验增强鲁棒性。3. Java层设计用ConnectionPreparedStatement守住事务边界拒绝裸连3.1 数据源配置别用Class.forName()用HikariCP管理连接池MySQL直连DriverManager.getConnection()在课设阶段看似简单但一旦加入并发选课比如模拟10人同时抢课连接泄漏、超时、锁表问题立刻暴露。必须上轻量级连接池。HikariCP是当前事实标准配置极简!-- pom.xml 添加依赖 -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency// DataSourceUtil.java单例数据源全局复用 public class DataSourceUtil { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/student_selection?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue); config.setUsername(root); config.setPassword(your_password); config.setDriverClassName(com.mysql.cj.jdbc.Driver); // 关键参数连接池大小设为CPU核心数*2课设环境设为4足够 config.setMaximumPoolSize(4); config.setMinimumIdle(2); config.setConnectionTimeout(30000); // 30秒超时 config.setIdleTimeout(600000); // 空闲10分钟回收 config.setMaxLifetime(1800000); // 连接最长存活30分钟 dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }逻辑说明serverTimezoneAsia/Shanghai必须显式指定否则Java读取DATETIME字段时区错乱选课时间显示为1970年allowPublicKeyRetrievaltrue是MySQL 8.0新认证协议所需否则连接报Public Key Retrieval is not allowedmaximumPoolSize4是课设安全值本地MySQL默认最大连接数151留足余量给其他工具如Navicat所有DAO方法必须通过DataSourceUtil.getConnection()获取连接禁止new Connection()。3.2 DAO层用PreparedStatement预编译防注入用try-with-resources自动释放以“学生选课”核心操作为例Java代码必须体现三点事务控制、参数绑定、资源释放。// SelectionDAO.java public class SelectionDAO { // 选课检查容量插入记录更新课程已选人数需另建view或trigger此处简化 public boolean selectCourse(String studentId, String courseId) { String sql INSERT INTO selection (student_id, course_id) VALUES (?, ?); try (Connection conn DataSourceUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 关键开启事务 ps.setString(1, studentId); ps.setString(2, courseId); int rows ps.executeUpdate(); if (rows 1) { conn.commit(); // 成功则提交 return true; } else { conn.rollback(); // 理论上不会进这里但保险起见 return false; } } catch (SQLIntegrityConstraintViolationException e) { // 捕获唯一键冲突重复选课、外键不存在学号/课号错误 System.err.println(选课失败 e.getMessage()); return false; } catch (SQLException e) { // 其他SQL异常如连接中断、锁等待超时 System.err.println(数据库操作异常 e.getMessage()); return false; } } // 查某学生所有选课含课程名、学分、成绩 public ListSelectionDetail getStudentSelections(String studentId) { String sql SELECT s.student_id, s.course_id, c.course_name, c.credit, s.grade, s.selection_time FROM selection s JOIN course c ON s.course_id c.course_id WHERE s.student_id ? ORDER BY s.selection_time DESC ; ListSelectionDetail list new ArrayList(); try (Connection conn DataSourceUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, studentId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { SelectionDetail detail new SelectionDetail(); detail.setStudentId(rs.getString(student_id)); detail.setCourseId(rs.getString(course_id)); detail.setCourseName(rs.getString(course_name)); detail.setCredit(rs.getByte(credit)); detail.setGrade(rs.getByte(grade)); detail.setSelectionTime(rs.getTimestamp(selection_time)); list.add(detail); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }参数说明conn.setAutoCommit(false)是事务起点commit()/rollback()是终点缺一不可try-with-resources自动关闭PreparedStatement和ResultSet避免连接池耗尽SQLIntegrityConstraintViolationException专门捕获MySQL约束违规如重复选课、学号不存在比泛用SQLException更精准SQL使用多行字符串Java 15提升可读性JOIN写法比子查询更高效ORDER BY s.selection_time DESC确保最新选课排最前。3.3 Service层业务逻辑下沉Java只做“协调者”很多课设代码把校验逻辑全写在Java里如“先查课程是否存在再查是否已选再查容量”这会导致多次数据库往返性能差并发时出现“检查-执行”时间窗漏洞A查到有空位B也查到两人同时插入代码臃肿违反“数据库负责数据正确性Java负责流程协调”原则。正确做法把强一致性校验交给MySQL存储过程或触发器。例如用存储过程封装选课逻辑DELIMITER $$ CREATE PROCEDURE sp_select_course( IN p_student_id VARCHAR(12), IN p_course_id VARCHAR(10), OUT p_result INT, -- 0成功-1课程不存在-2已选过-3容量满 OUT p_message VARCHAR(100) ) BEGIN DECLARE v_count INT DEFAULT 0; DECLARE v_capacity INT DEFAULT 0; -- 检查课程是否存在 SELECT COUNT(*) INTO v_count FROM course WHERE course_id p_course_id; IF v_count 0 THEN SET p_result -1; SET p_message 课程不存在; LEAVE proc_label; END IF; -- 检查是否已选 SELECT COUNT(*) INTO v_count FROM selection WHERE student_id p_student_id AND course_id p_course_id; IF v_count 0 THEN SET p_result -2; SET p_message 您已选过该课程; LEAVE proc_label; END IF; -- 检查容量此处简化假设每门课最多50人选实际可加capacity字段 SELECT COUNT(*) INTO v_count FROM selection WHERE course_id p_course_id; IF v_count 50 THEN SET p_result -3; SET p_message 课程已满; LEAVE proc_label; END IF; -- 执行插入 INSERT INTO selection (student_id, course_id) VALUES (p_student_id, p_course_id); SET p_result 0; SET p_message 选课成功; END$$ DELIMITER ;Java调用只需一行public boolean selectCourseWithSP(String studentId, String courseId) { String sql {CALL sp_select_course(?, ?, ?, ?)}; try (Connection conn DataSourceUtil.getConnection(); CallableStatement cs conn.prepareCall(sql)) { cs.setString(1, studentId); cs.setString(2, courseId); cs.registerOutParameter(3, Types.INTEGER); cs.registerOutParameter(4, Types.VARCHAR); cs.execute(); int result cs.getInt(3); String message cs.getString(4); System.out.println(message); return result 0; } catch (SQLException e) { e.printStackTrace(); return false; } }优势所有校验在数据库内原子执行彻底规避并发漏洞Java代码极度精简只负责传参、取结果、展示提示存储过程可被其他语言如Python脚本复用系统扩展性更强。4. 避坑课设答辩高频翻车点血泪经验总结4.1 现象Navicat能连MySQLJava程序连不上报错Communications link failure原因MySQL服务未启动或端口被占用常见于WampServer/XAMPP冲突防火墙拦截3306端口Windows Defender或第三方安全软件JDBC URL中localhost在Docker或WSL环境下解析异常应改用127.0.0.1。解决命令行执行netstat -ano | findstr :3306确认MySQL进程PID任务管理器结束冲突进程临时关闭防火墙测试或添加入站规则放行TCP 3306JDBC URL改为jdbc:mysql://127.0.0.1:3306/...排除DNS解析问题。4.2 现象插入中文姓名/课程名显示为??数据库存的是乱码原因MySQL服务器默认字符集不是utf8mb4如5.7默认latin1JDBC URL未指定characterEncodingutf8mb4表/字段未显式设CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci。解决检查MySQL配置文件my.cnf在[mysqld]下添加character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重启MySQL服务重建数据库DROP DATABASE student_selection; CREATE DATABASE ...已有数据需导出再导入JDBC URL补全参数?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4。4.3 现象学生退课后选课记录删了但课程表里“已选人数”没减教务查账不一致原因仅用DELETE FROM selection删除记录未同步更新course表的统计字段如selected_count未用触发器或存储过程维护统计一致性。解决方案1推荐在selection表上建AFTER DELETE触发器DELIMITER $$ CREATE TRIGGER tr_after_delete_selection AFTER DELETE ON selection FOR EACH ROW BEGIN UPDATE course SET selected_count selected_count - 1 WHERE course_id OLD.course_id; END$$ DELIMITER ;方案2Java层在deleteSelection()方法中先DELETE再UPDATE course但需保证事务原子性。4.4 现象多人同时选同一门热门课部分人提示“选课成功”查数据库发现重复记录原因未设UNIQUE(student_id, course_id)联合唯一索引Java层用SELECT COUNT(*)检查是否已选再INSERT存在并发间隙race condition。解决立即执行建索引语句ALTER TABLE selection ADD UNIQUE KEY uk_student_course (student_id, course_id);删除Java中所有“先查后插”的逻辑全部改用INSERT IGNORE或ON DUPLICATE KEY UPDATE或直接调用存储过程如3.3节。4.5 现象Tomcat启动报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因Maven依赖scope为test未打入war包MySQL驱动jar未放在WEB-INF/lib下传统Web项目Java版本与驱动不匹配如Java 17用mysql-connector-java 5.x。解决检查pom.xml确保scope标签未出现或设为compile默认若用IDEA右键项目→Add Framework Support→勾选Web Application确认lib目录被标记为Library升级驱动Java 11必须用8.0版本mysql-connector-java:8.0.33兼容性最佳。5. 真实可演示用Swing快速搭出教务老师能操作的GUI附核心交互逻辑5.1 主窗口设计Tab页分隔学生/课程/选课三大功能区课设答辩不求炫酷UI但必须体现“能用”。Swing足够轻量无需Web容器双击jar即可运行。核心是三个Tab学生管理Tab表格展示学生列表按钮支持新增/修改/删除课程管理Tab同上含“查看已选人数”按钮选课操作Tab左侧学生下拉框 右侧课程列表带剩余容量 “选课”/“退课”按钮。// MainFrame.java主窗口骨架 public class MainFrame extends JFrame { private JTabbedPane tabbedPane; private StudentPanel studentPanel; private CoursePanel coursePanel; private SelectionPanel selectionPanel; public MainFrame() { setTitle(学生选课信息管理系统); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); tabbedPane new JTabbedPane(); studentPanel new StudentPanel(); coursePanel new CoursePanel(); selectionPanel new SelectionPanel(); tabbedPane.addTab(学生管理, studentPanel); tabbedPane.addTab(课程管理, coursePanel); tabbedPane.addTab(选课操作, selectionPanel); add(tabbedPane, BorderLayout.CENTER); pack(); setLocationRelativeTo(null); // 居中显示 } }5.2 选课Tab核心逻辑实时显示课程剩余容量点击即执行存储过程关键体验点用户看到“数据库原理剩余32/50”点“选课”后立即刷新为“剩余31/50”。这需要查询时关联selection表统计已选人数选课后重新加载课程列表。// SelectionPanel.java 片段 private void loadCourseList() { String sql SELECT c.course_id, c.course_name, c.credit, c.semester, c.teacher, COALESCE(s.selected_count, 0) AS selected_count, 50 - COALESCE(s.selected_count, 0) AS remaining FROM course c LEFT JOIN ( SELECT course_id, COUNT(*) AS selected_count FROM selection GROUP BY course_id ) s ON c.course_id s.course_id ORDER BY c.course_id ; // 执行查询填充JTable... } private void onEnrollButtonClicked() { String studentId studentComboBox.getSelectedItem().toString(); String courseId getSelectedCourseIdFromTable(); // 从JTable获取选中行course_id boolean success selectionDAO.selectCourseWithSP(studentId, courseId); if (success) { JOptionPane.showMessageDialog(this, 选课成功); loadCourseList(); // 刷新剩余容量 loadStudentSelections(studentId); // 刷新学生已选列表 } }参数说明COALESCE(s.selected_count, 0)处理无选课记录的课程避免NULL干扰计算50 - ...是硬编码容量实际可扩展为course.capacity字段loadCourseList()和loadStudentSelections()必须成对调用保证两端数据一致。5.3 导出Excel功能用Apache POI生成带格式的成绩单教务老师刚需导出某班学生选课及成绩表。POI比手写CSV更专业支持合并单元格、字体加粗。public void exportSelectionReport(String studentId, String filename) { ListSelectionDetail list selectionDAO.getStudentSelections(studentId); try (XSSFWorkbook workbook new XSSFWorkbook(); FileOutputStream fileOut new FileOutputStream(filename)) { XSSFSheet sheet workbook.createSheet(选课成绩单); // 表头样式 XSSFCellStyle headerStyle workbook.createCellStyle(); Font font workbook.createFont(); font.setBold(true); headerStyle.setFont(font); // 写表头 String[] headers {学号, 课程号, 课程名称, 学分, 成绩, 选课时间}; XSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { XSSFCell cell headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); } // 写数据 for (int i 0; i list.size(); i) { XSSFRow row sheet.createRow(i 1); SelectionDetail d list.get(i); row.createCell(0).setCellValue(d.getStudentId()); row.createCell(1).setCellValue(d.getCourseId()); row.createCell(2).setCellValue(d.getCourseName()); row.createCell(3).setCellValue(d.getCredit()); row.createCell(4).setCellValue(d.getGrade() null ? 未录入 : d.getGrade().toString()); row.createCell(5).setCellValue(d.getSelectionTime().toString()); } // 自动列宽 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } workbook.write(fileOut); JOptionPane.showMessageDialog(this, 导出成功 filename); } catch (IOException e) { e.printStackTrace(); } }避坑提示XSSFWorkbook对应.xlsx格式若需.xls用HSSFWorkbook但已过时不推荐autoSizeColumn()必须在写完所有数据后调用否则无效成绩为NULL时显示“未录入”避免Excel报错。6. 答辩前最后 checklist让老师一眼看出你懂“为什么这么设计”6.1 数据库层面必须能答出的3个问题问题标准答案要点为什么考这个为什么用InnoDB而不用MyISAMInnoDB支持事务选课必须原子性、行级锁并发选课不锁整表、外键约束保证student/course数据完整性MyISAM只支持表锁高并发下性能崩溃。考察是否理解存储引擎选型依据不是背概念selection表为什么设联合主键而非自增ID选课关系本质是“学生课程”唯一标识自增ID无业务意义联合主键天然满足UNIQUE(student_id,course_id)且外键引用更简洁避免冗余字段。考察ER模型到物理表的映射能力CHECK(credit BETWEEN 1 AND 8)在MySQL 5.7失效怎么办5.7不支持CHECK需用触发器BEFORE INSERT ON course检查credit值非法则SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 学分必须1-8;考察向下兼容方案体现工程思维6.2 Java层面必须演示的2个关键动作现场演示事务回滚故意输入不存在的学号观察SQLIntegrityConstraintViolationException被捕获控制台打印“选课失败Cannot add or update a child row”证明外键约束生效现场演示并发安全开两个窗口同一学生同时点两次“选课”第二次必提示“您已选过该课程”证明UNIQUE索引存储过程双重防护。6.3 你该主动提的1个优化点加分项“当前课程容量是硬编码50下一步可扩展为course.capacity字段并在selection表增加AFTER INSERT触发器自动更新course.selected_count这样教务老师后台改容量前端实时生效。” —— 这句话表明你已思考系统演进不是交完作业就扔。我带学生做课设十年最深的教训是别把课设当作业要当产品原型来打磨。答辩老师不关心你写了多少行代码只关心“这个系统能不能真用、哪里会崩、你怎么防崩”。把MySQL的约束当盾牌把Java的事务当缰绳把每一次INSERT都当成对数据契约的签字。希望帮到你。本文还有配套的精品资源点击获取