从零实现工艺卡片系统:数据库设计与Java课设全攻略
简介数据库课设“工艺卡片系统”是面向高校计算机、软件工程等专业学生的完整课程设计项目围绕生产环境中的工艺卡片管理展开覆盖数据库需求分析、E-R概念模型、关系模型、物理设计到SQL实现与测试的全过程并设计了管理员与普通员工两类角色权限。压缩包共27个文件大小4.07MB主要包含6个aspx页面及其cs后端代码、SQL Server数据库文件mdf为数据文件、ldf为日志文件、CSS样式与config配置文件以及sln解决方案和jpg/png界面预览图项目结构一目了然可通过Visual Studio直接打开并附加数据库运行。目前已有246人学习下载。资源对工艺卡片表的字段设计、外键约束、用户登录与权限控制都给出了可运行示例适合参考其表结构、CRUD操作和分层实现无论是课程设计冲刺还是日常自学都能对照修改能显著节省课设开发时间帮助顺利通过老师检查。1. 数据库课设选“工艺卡片系统”是稳过老师检查的一条熟路真到交数据库课设的时候最怕的往往不是SQL写不出来而是选题没选好系统做大了进度拖死做小了只能跑一个表老师问两句就没得说。工艺卡片系统恰好落在这个最舒服的位置上——数据关系是标准的一卡多工序演示时能看到主表、明细表、外键、统计聚合一层层联动起来。它面向制造业里真实的工艺过程管理场景业务逻辑不悬空老师一看就知道这个题目是有工程背景的。整个系统要拆的功能也很收敛零件管理、设备管理、工艺卡片维护、工序查询、编号自动生成。用这几章我会从表结构设计、核心SQL、Java界面到避坑清单按可以照着复现的顺序展开把课设一次做完。2. 工艺卡片系统的数据库设计从一张业务图到五张可落地的表2.1 先想清楚工艺卡片这个业务表结构才不会拍脑袋工艺卡片在制造业里的角色是描述“一个零件从毛坯到成品要经过哪些加工步骤”的工艺文件。一张卡片只针对一个零件的一条工艺路线卡片下面按工序号排着一串工序常见的有下料、车削、铣削、钻孔、检验。工序之间有序号递增关系工序本身又和具体设备挂钩。这个业务场景一出来数据库结构其实就已经定型了卡片是主表工序是明细表零件是主表的引用对象设备是工序的引用对象系统用户负责登录和权限控制。我记得第一次做这个题目时差点把工序塞进卡片主表里变成一个长文本字段那样查询“所有用到某台设备的工序”就完全没法用SQL表达。后来按规范拆成主表和明细表加外键约束整个系统才真正变成“能回答业务问题”的数据库。课设最忌讳的就是把数据库当成单纯存数据的盒子老师真正想看的是你能不能从业务流程里推导出表结构并给出合理性解释。所以这一章不只给建表语句更要把为什么这样拆讲清楚。2.2 五张核心表的字段规划职责分开关系清晰工艺卡片系统的数据范围不大我最终规划了五张表每张表只承担一个明确职责表名业务职责主键核心外键典型字段t_user系统登录与人员user_id无username, real_name, rolet_part零件档案part_id无part_code, part_name, materialt_equipment设备字典equip_id无equip_code, equip_name, modelt_process_card工艺卡片主表card_idpart_idcard_no, version_no, statust_operation工序明细op_idcard_id, equip_idop_seq, op_name, work_hours这种拆分方式是最常见的做法。设备单独拆成字典表而不是直接在工序表里存一个设备名文本好处是设备信息能复用、能统一维护。零件单独建表是为了让一张卡片挂到一个确定的零件上卡片和零件是多对一的关系。用户表在这个项目里可以撑起登录功能顺便满足课设里“有用户权限”的常规要求。字段命名我统一用下划线风格比如card_no、part_code。这里有个容易被忽视的小细节物理表名和字段名不要用驼峰MySQL在Windows下不区分大小写但Linux下区分多写几次JOIN就知道统一小写能省多少事。工艺卡片主表的card_no字段要加unique约束不能只靠primary key因为业务编号在界面上要作为查询入口重复了演示时直接穿帮。2.3 建表SQL主键、外键、唯一约束和字符集一次写对直接给完整的MySQL建表脚本用的库名是craft_card_db中间件统一InnoDB字符集utf8mb4。这段SQL可以原样复制到Navicat或命令行里执行。CREATE DATABASE IF NOT EXISTS craft_card_db DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE craft_card_db; DROP TABLE IF EXISTS t_operation; DROP TABLE IF EXISTS t_process_card; DROP TABLE IF EXISTS t_part; DROP TABLE IF EXISTS t_equipment; DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, real_name VARCHAR(50) NOT NULL COMMENT 姓名, password VARCHAR(64) NOT NULL COMMENT 密码演示时用MD5存储, role VARCHAR(20) NOT NULL DEFAULT user COMMENT 角色admin/user, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT系统用户表; CREATE TABLE t_part ( part_id INT PRIMARY KEY AUTO_INCREMENT, part_code VARCHAR(30) NOT NULL UNIQUE COMMENT 零件编号, part_name VARCHAR(100) NOT NULL COMMENT 零件名称, material VARCHAR(50) COMMENT 材料牌号如HT200, spec VARCHAR(100) COMMENT 规格尺寸, remark VARCHAR(255) COMMENT 备注 ) ENGINEInnoDB COMMENT零件表; CREATE TABLE t_equipment ( equip_id INT PRIMARY KEY AUTO_INCREMENT, equip_code VARCHAR(30) NOT NULL UNIQUE COMMENT 设备编号, equip_name VARCHAR(50) NOT NULL COMMENT 设备名称, model VARCHAR(50) COMMENT 设备型号, status VARCHAR(10) DEFAULT 可用 COMMENT 状态可用/维修/停用 ) ENGINEInnoDB COMMENT设备表; CREATE TABLE t_process_card ( card_id INT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(24) NOT NULL UNIQUE COMMENT 工艺卡片编号如GY-2024-0001, part_id INT NOT NULL COMMENT 关联零件ID, version_no VARCHAR(10) DEFAULT V1.0 COMMENT 版本号, process_type VARCHAR(30) DEFAULT 机加工 COMMENT 工艺类型, editor VARCHAR(50) COMMENT 编制人, auditor VARCHAR(50) COMMENT 审核人, create_date DATE COMMENT 编制日期, status VARCHAR(10) DEFAULT 草稿 COMMENT 状态草稿/审核中/已发布/作废, CONSTRAINT fk_card_part FOREIGN KEY (part_id) REFERENCES t_part(part_id) ) ENGINEInnoDB COMMENT工艺卡片主表; CREATE TABLE t_operation ( op_id INT PRIMARY KEY AUTO_INCREMENT, card_id INT NOT NULL COMMENT 所属工艺卡片ID, op_seq INT NOT NULL COMMENT 工序号建议10的倍数, op_name VARCHAR(50) NOT NULL COMMENT 工序名称, op_content VARCHAR(255) COMMENT 工序内容/工艺要求, equip_id INT COMMENT 使用设备ID可空, work_hours DECIMAL(6,2) COMMENT 定额工时单位小时, fixture VARCHAR(50) COMMENT 夹具或工装, CONSTRAINT fk_op_card FOREIGN KEY (card_id) REFERENCES t_process_card(card_id) ON DELETE CASCADE, CONSTRAINT fk_op_equip FOREIGN KEY (equip_id) REFERENCES t_equipment(equip_id), UNIQUE KEY uk_card_op (card_id, op_seq) ) ENGINEInnoDB COMMENT工序明细表;逻辑说明工序明细表的外键写在card_id上并带上ON DELETE CASCADE这样主表删除时明细自动清理避免出现“卡片没了、工序还在”的孤儿数据。另一处关键是联合唯一索引uk_card_op它的作用是一张卡片里不允许出现两个同号工序比如10号和10号不能并存这是业务上的硬约束。参数说明card_no设置为24字符是因为要容纳“GY-2024-0001”这类格式前缀加上年份加上四位数序号整体不会超过20个字符留一点余量避免后续扩展时改表。work_hours用DECIMAL(6,2)最大可以存9999.99小时比用float更可控不会出现浮点误差。equip_id允许为NULL因为检验工序经常不需要设备外键不设NOT NULL就是为此留的口子。2.4 插入演示数据让系统第一次跑起来就有内容可看建完表之后马上灌入几条基础数据后面写查询、做界面都用得上。这里要注意插入有外键关系的表时必须先插入被引用的表。INSERT INTO t_user (username, real_name, password, role) VALUES (admin, 系统管理员, MD5(123456), admin), (user01, 工程师A, MD5(123456), user); INSERT INTO t_part (part_code, part_name, material, spec) VALUES (P001, 拨叉, HT200, 200x120x80), (P002, 法兰盘, Q235, DN150x30), (P003, 传动轴, 45#钢, φ40x500); INSERT INTO t_equipment (equip_code, equip_name, model) VALUES (EQ01, 普通车床, CA6140), (EQ02, 立式铣床, X5032), (EQ03, 摇臂钻床, Z3050);这段数据的工艺内容要和零件材料对应上比如45号钢的传动轴走车削HT200的拨叉走铣削。老师抽查工序内容时如果发现零件和工序完全不匹配会显得数据是随便造的。密码字段用MD5函数存Java里比对时把输入密码也做MD5再比较这样可以直接跟老师说“密码不落明文”。3. 工艺卡片的增删改查与存储过程让核心业务跑通并输出亮点3.1 新增一张工艺卡片主表和明细表必须在一个事务里写入工艺卡片的核心操作是新增卡片及工序这一步如果只在界面上插入主表工序明细丢失演示时打开卡片就是空壳。常见做法是用事务把主表插入和明细表批量插入包在一起中间任何一步失败都整体回滚。数据库里直接执行下面的SQL就能体会事务的效果START TRANSACTION; INSERT INTO t_process_card (card_no, part_id, version_no, process_type, editor, auditor, create_date, status) VALUES (GY-2024-0001, 1, V1.0, 机加工, 工程师A, 工程师B, CURDATE(), 草稿); SET new_card_id LAST_INSERT_ID(); INSERT INTO t_operation (card_id, op_seq, op_name, op_content, equip_id, work_hours, fixture) VALUES (new_card_id, 10, 下料, 按毛坯尺寸锯切留3mm余量, 1, 0.50, 平口钳), (new_card_id, 20, 车削, 粗车外圆留0.4mm精车余量, 2, 1.20, 四爪卡盘), (new_card_id, 30, 铣削, 铣削底面至尺寸要求, 2, 1.00, 压板), (new_card_id, 40, 检验, 按图纸检验关键尺寸并记录, NULL, 0.20, 游标卡尺); COMMIT;参数说明LAST_INSERT_ID()是MySQL提供的会话级函数它返回当前会话最后一次自增主键的值需要紧跟INSERT语句之后调用中途不能执行别的INSERT否则拿到的是别人的ID。op_seq用10的倍数这是工艺行业的习惯写法目的是在两道工序之间插入新工序时不用改动已有编号。这里有个容易踩的坑如果不用事务先插主表再插明细第二次INSERT中途报错主表数据已经提交了界面上就会出现“卡片总数多了工序却没有”的不一致状态。演示时老师如果现场测试最好让他看到的是“保存失败全部回滚”的反馈而不是留下半截数据。3.2 列表查询用一条GROUP BY把工序数带出来工艺卡片列表是界面最核心的查询它要同时展示卡片主信息和每张卡片下的工序数量。初学者最常见的写法是两张表分开查再在Java里循环统计但那时数据库白学了。直接在SQL里用聚合函数一步完成SELECT c.card_id, c.card_no, p.part_name, c.process_type, c.editor, c.auditor, c.status, COUNT(o.op_id) AS op_count FROM t_process_card c JOIN t_part p ON p.part_id c.part_id LEFT JOIN t_operation o ON o.card_id c.card_id GROUP BY c.card_id, c.card_no, p.part_name, c.process_type, c.editor, c.auditor, c.status ORDER BY c.card_id DESC;逻辑说明JOIN零件表是为了把part_name展示到列表里LEFT JOIN工序表再加COUNT是为了统计每张卡片的工序数。LEFT JOIN保证了没有工序的卡片也能显示出来如果改成JOIN那些空卡片的行就消失了。参数说明MySQL默认开启ONLY_FULL_GROUP_BY模式SELECT里出现的非聚合列必须全部出现在GROUP BY中所以我把主表的所有列都写进去了。要想偷懒也可以只按c.card_id分组然后把其他字段改用ANY_VALUE函数包起来但那样写在课设里显脏不如老老实实列全。3.3 用视图把多表联查固定下来Java端只查视图视图的价值在于把复杂的JOIN逻辑封装在数据库层Java端只需要一句“SELECT * FROM 视图名 WHERE 条件”。下面建一个工艺卡片明细视图把主表、零件、工序、设备信息全部铺平CREATE OR REPLACE VIEW view_card_detail AS SELECT c.card_id, c.card_no, p.part_code, p.part_name, c.version_no, c.process_type, o.op_seq, o.op_name, o.op_content, o.work_hours, e.equip_name, c.editor, c.auditor, c.create_date, c.status FROM t_process_card c JOIN t_part p ON c.part_id p.part_id LEFT JOIN t_operation o ON o.card_id c.card_id LEFT JOIN t_equipment e ON e.equip_id o.equip_id;查询时这样用SELECT * FROM view_card_detail WHERE card_no GY-2024-0001 ORDER BY op_seq;说明视图本身是一个逻辑表不物理存储数据每次查询时动态执行定义里的SELECT。把它作为“只读接口”暴露给上层后台改表结构时只要重定义视图Java代码不用动。课设答辩时被问“为什么引入视图”可以用“屏蔽底层表变化、简化客户端查询”来回答。3.4 存储过程自动生成卡片编号一个能加分的并发细节手工输入编号容易重复而且日期写错了也不容易发现。把编号生成逻辑写进存储过程调用方只传前缀返回生成好的编号。这里处理了并发重复和空表两种边界情况DELIMITER $$ CREATE PROCEDURE sp_gen_card_no( IN p_prefix VARCHAR(10), OUT p_card_no VARCHAR(24) ) BEGIN DECLARE v_year CHAR(4) DEFAULT DATE_FORMAT(CURDATE(), %Y); DECLARE v_seq INT; SELECT IFNULL(MAX(CAST(SUBSTRING_INDEX(card_no, -, -1) AS UNSIGNED)), 0) 1 INTO v_seq FROM t_process_card WHERE card_no LIKE CONCAT(p_prefix, -, v_year, -%) FOR UPDATE; SET p_card_no CONCAT(p_prefix, -, v_year, -, LPAD(v_seq, 4, 0)); END$$ DELIMITER ;参数说明p_prefix是编号前缀这里传‘GY’如果要为检验卡片单独生成编号可以再传一个前缀。SUBSTRING_INDEX(card_no, -, -1)取的是从左往右最后一个分隔符后面的片段也就是四位序号再CAST成整数计算最大值加一比如已有0001和0002时MAX返回2加1变成3。LPAD让数字补成四位数保证0003、0010这样的格式整齐。FOR UPDATE是这过程的点睛之笔它会在事务内锁住扫描到的行防止两个用户同时打开页面生成同一个编号。课设阶段未必真会遇到并发但把锁写出来老师会认为你理解了并发控制。需要注意的是调用过程前要开启事务提交后才能把锁释放否则连续调用会卡住这里建议在Java代码里把“生成编号”和“插入卡片”放在同一个事务里。4. Java与MySQL对接把工艺卡片系统从SQL变成能点击的桌面程序4.1 选型说明为什么用Java Swing而不是网页端数据库课设的验收场景通常是教室里的台式机网络不一定通浏览器访问还要额外启动Tomcat一旦端口被占用就是麻烦。Java Swing是JDK自带的界面框架不需要联网下载双击就能跑配合MySQL驱动稳定程度高。这里说的Java不是框架不需要Spring、不需要Maven只要JDK 8以上加一个mysql-connector-java驱动包就够了。常见做法是先建一个普通Java工程把驱动JAR放入lib目录再在IDE里把该目录加入构建路径。工艺卡片系统的核心是数据库端的设计界面只是呈现层Swing的表格组件能直接映射ResultSet结果演示效果足够。如果之前没接触过Swing也不用怕核心代码不超过200行剩下的就是摆放按钮和文本框。重点精力放在JDBC那两层方法上。4.2 数据库连接工具类URL参数一个都不能少连接是JDBC的第一道关驱动类名、URL参数、用户名密码有一项不对界面根本起不来。下面是最常用的工具类写法import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/craft_card_db ?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/Shanghai useSSLfalseallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws SQLException { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new SQLException(未找到MySQL驱动请检查JAR是否加入构建路径, e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }逻辑说明URL里的useUnicodetrue和characterEncodingutf8必须成对出现它告诉驱动用UTF-8往数据库写中文缺少后控制台和界面上全是问号。serverTimezoneAsia/Shanghai解决MySQL 8.x高版本时区识别问题不写上驱动会抛SQLException。与MySQL 8.0同时出现的还有allowPublicKeyRetrievaltrue参数它允许客户端在连接时从服务器获取公钥解决caching_sha2_password认证方式下的连接报错。参数说明用户名和密码按照你本机的MySQL账号改我这里用的是root和123456。这里还有一个小陷阱MySQL 5.x的驱动类名是com.mysql.jdbc.DriverMySQL 8移动到了com.mysql.cj.jdbc.Driver新旧驱动类名不能混着写否则ClassNotFoundException会直接摆在你面前。4.3 新增工艺卡片的DAO方法事务和批量PreparedStatement界面上“保存卡片”这个按钮背后做的事情是同时插入主表和多条工序。这个DAO方法把事务控制、自增主键回填、工序批量插入都放在一起public int addCard(String cardNo, int partId, ListOperation ops) throws SQLException { String sqlCard INSERT INTO t_process_card(card_no, part_id, version_no, process_type, editor, auditor, create_date, status) VALUES(?,?,?,?,?,?,CURDATE(),草稿); String sqlOp INSERT INTO t_operation(card_id, op_seq, op_name, op_content, equip_id, work_hours) VALUES(?,?,?,?,?,?); Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); int cardId; try (PreparedStatement ps conn.prepareStatement(sqlCard, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, cardNo); ps.setInt(2, partId); ps.setString(3, V1.0); ps.setString(4, 机加工); ps.setString(5, 工程师A); ps.setString(6, 工程师B); ps.executeUpdate(); try (ResultSet rs ps.getGeneratedKeys()) { rs.next(); cardId rs.getInt(1); } } try (PreparedStatement psOp conn.prepareStatement(sqlOp)) { for (Operation op : ops) { psOp.setInt(1, cardId); psOp.setInt(2, op.getOpSeq()); psOp.setString(3, op.getOpName()); psOp.setString(4, op.getOpContent()); if (op.getEquipId() 0) { psOp.setNull(5, java.sql.Types.INTEGER); } else { psOp.setInt(5, op.getEquipId()); } psOp.setBigDecimal(6, op.getWorkHours()); psOp.addBatch(); } psOp.executeBatch(); } conn.commit(); return cardId; } catch (SQLException e) { conn.rollback(); throw new SQLException(保存工艺卡片失败全部回滚, e); } finally { conn.setAutoCommit(true); conn.close(); } }逻辑说明setAutoCommit(false)关闭自动提交之后的INSERT都进入事务直到commit才生效。用RETURN_GENERATED_KEYS配合getGeneratedKeys()把主表自增ID取出来这个ID又作为工序的card_id完整对齐了表关系。工序部分用了addBatch和executeBatch批量提交而不是一条条执行既满足事务要求又减少与数据库的交互次数。参数说明Operation是工序的POJO字段可以是opSeq、opName、opContent、equipId、workHours。这里写了一个细节处理当equipId为0时表示界面未选设备用setNull写入NULL而不是存0进外键因为外键0可能匹配不到任何设备。commit放在finally之外只有不抛异常才提交。4.4 列表查询方法把聚合结果映射成界面数据查询“卡片列表并显示工序数”的方法如下它供Swing的JTable刷新数据时调用public ListCardVO listCards() throws SQLException { String sql SELECT c.card_id, c.card_no, p.part_name, c.process_type, c.editor, c.status, COUNT(o.op_id) AS op_count FROM t_process_card c JOIN t_part p ON p.part_id c.part_id LEFT JOIN t_operation o ON o.card_id c.card_id GROUP BY c.card_id, c.card_no, p.part_name, c.process_type, c.editor, c.status ORDER BY c.card_id DESC; ListCardVO list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { CardVO vo new CardVO(); vo.setCardId(rs.getInt(card_id)); vo.setCardNo(rs.getString(card_no)); vo.setPartName(rs.getString(part_name)); vo.setProcessType(rs.getString(process_type)); vo.setEditor(rs.getString(editor)); vo.setStatus(rs.getString(status)); vo.setOpCount(rs.getInt(op_count)); list.add(vo); } } return list; }逻辑说明这个方法和第3章的GROUP BY查询一一对应只是把结果集逐行转成CardVO对象。CardVO就是界面表格的行载体字段定义得和JTable的列一一对应刷新时可以省去再转换的功夫。这里全部使用JDK 7的try-with-resources写法连接、语句、结果集自动关闭不会出现连接泄漏。界面那侧的做法是新建一个DefaultTableModel把CardVO的字段填充进二维数组再setDataVector替换表格数据。按钮“刷新列表”的监听器里调用listCards()完成后重新填充模型整个过程不需要重启程序。如果你已经用过其它界面库套路完全一样只是表格API的细节不同。5. 工艺卡片系统里最容易翻车的五个地方现场踩坑记录与排查清单5.1 中文全部变成问号字符设置没有形成闭环现象界面插入“下料”MySQL里看到的是“???”更诡异的是部分机器上中文正常另一台机器乱码。原因字符集问题从来不是单点问题数据库默认字符集、JDBC连接参数、客户端工具三个环节里只要有一处是latin1中文就坏。解决第一步确认数据库默认字符集执行SHOW VARIABLES LIKE character_set_database;如果是latin1就要改库的字符集第二步检查JDBC URL保证带useUnicodetruecharacterEncodingutf8第三步如果通过命令行插入乱码把命令行的编码切到UTF-8。最稳妥的解决办法是用数据库建库语句时就固定utf8mb4后续不再改这是后悔药也是预防针。5.2 删除工艺卡片时外键约束报错现象DELETE FROM t_process_card WHERE card_id 1; 报错Cannot delete or update a parent row。原因t_operation表的外键card_id默认使用RESTRICT策略主表有子表引用时禁止删除这其实是数据库在保护数据完整性。解决建表时在工序外键上写ON DELETE CASCADE这样删除主表时MySQL自动把对应工序一并清理。还有一种做法是在Java代码里先执行“DELETE FROM t_operation WHERE card_id?”再删主表两种方案都可以课设里我推荐级联删除因为代码路径更短而且在ER图上标注了级联语义。5.3 工艺卡片编号重复字符串排序和数值排序的差异现象编号生成过程第一次调用得到GY-2024-0001第二次却还是GY-2024-0001插入时主键冲突。原因直接对文本格式的编号取MAX比如库里已有GY-2024-0001、GY-2024-0002、GY-2024-0010按字符串比较MAX反而取到0002甚至0001而不是0010。解决生成过程里必须用SUBSTRING_INDEX取出最后一段序号并CAST成整数再按数值求最大加一。这也解释了为什么card_no字段要统一格式前缀、年份、序号的分隔符一旦混用解析代码就会出边界。5.4 Java连接MySQL 8报ClassNotFoundException或公钥错误现象程序启动就报找不到驱动类或者报Public Key Retrieval is not allowed。原因驱动包版本和数据库版本不匹配最常见的是用MySQL 5.x的驱动去连MySQL 8.x驱动类名不同还有MySQL 8默认的caching_sha2_password认证要求客户端额外获取公钥。解决驱动包换成mysql-connector-java 8.0.x版本驱动类名写com.mysql.cj.jdbc.Driver同时在URL里追加allowPublicKeyRetrievaltrueuseSSLfalse。如果还是报认证问题检查MySQL用户表的plugin字段是否是caching_sha2_password课设环境里可以直接改成mysql_native_password便于稳定演示。5.5 演示时数据量太少空列表让功能看起来没做完现象进入界面只有一两行数据老师问“你们的列表查询是不是有问题”场面非常尴尬。原因不是查询没写好而是演示数据太少聚合结果没有视觉冲击力。解决提前用脚本批量生成20张工艺卡片每张卡3到6道工序让列表至少占满一屏。建议造数据时保持业务一致拨叉配铣削工序法兰盘配车削和钻孔设备引用正确status字段均匀分布在草稿、审核中、已发布三种状态。这样演示“按状态筛选”功能时切换条件能看到行数变化比一律“草稿”有意料之外的说服力。6. 让答辩现场顺畅到位的习惯把演示剧本做成项目的一部分真正把项目做厚靠的不是最后一晚加班而是把一个“演示剧本”当成交付物来准备。我会在项目根目录放一个README.md里面从环境配置写起JDK版本、MySQL版本、驱动包位置、建库脚本路径每一步都给出命令确保换一台机器也能跑起来。然后在README里固定演示路径第一步打开列表看聚合查询第二步新增一张卡片展示事务写入和编号自动生成第三步展开卡片查看明细工序第四步按状态筛选第五步删除卡片验证级联清理。每步写清楚“点哪个按钮”“预期看到什么”答辩时照着走状态稳定。我通常还会把walkthrough用到的数据记录成一张表和SQL脚本比如演示用纸条写清卡片编号GY-2024-0016对应的零件和工序这样现场无论老师随机点哪个页面都能立刻切换应对。这个习惯帮我绕过不少次翻车也让我意识到课设里真正值分的是“文档里写清楚、界面能复现、换机器能跑”。希望这份从建表到演示的运行路径能帮到你把工艺卡片系统做出超出预期的完成度。本文还有配套的精品资源点击获取