拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

数据库课程设计实战:工艺卡片系统设计与MySQL实现指南

数据库课程设计实战:工艺卡片系统设计与MySQL实现指南 简介这是一份数据库课程设计“工艺卡片系统”的完整项目资源面向需要完成数据库课设的在校学生帮助其系统掌握数据库设计、实现与应用的全过程。项目采用网页应用模式包含用户登录、管理员与普通员工权限区分、工艺卡片增删改查等功能覆盖需求分析、概念模型、逻辑模型到物理实现等环节并附有页面文件、后台代码及数据库备份。压缩包共二十七个文件大小约四兆主要包含网页页面、后台逻辑、数据库文件、日志文件、样式表及若干界面截图可直观查看运行效果。目前已有二百四十五人学习下载。完整源码和数据库文件同步提供读者能参考表结构设计与权限控制逻辑直接修改后即可用于课设验收。1. 工艺卡片系统课设值不值得做为什么它是数据库老师眼里的“安全牌”“绝对可以让你通过老师的检查”这种话说得太满了。数据库课设没有保过模板尤其是老师现场抽问你的事务边界、主键设计和 ER 图关系时背稿子没有用。但如果你还在选题阶段工艺卡片系统确实值得认真考虑它不是那种花哨的题目却把数据库课设该考的增删改查、主外键约束、事务、连接池、多表联查全部装进去了复杂度又刚好控制在一个人两三周能做完的范围。适合正在找数据库课程设计选题、想避开“图书管理”“商城系统”这类撞题率极高方向的在校生。老师看到工艺卡片这种带制造业业务背景的选题往往会觉得你认真调研过因为里面涉及主从表、版本号、工时统计这些真实业务细节。2. 工艺卡片系统核心表结构从业务表单到 ER 模型与建表 SQL工艺卡片也叫工艺过程卡是机械加工企业里指导生产的一张表单记录一个零件从下料、粗车、精车到检验的完整加工路径。数据库课设做这个系统本质工作就是把这张纸上的信息搬进 MySQL做成可维护、可查询、可统计的软件。2.1 先读懂工艺卡片表单字段就是你的实体清单一张真实的工艺卡片从上到下通常分四段。头部是产品名称、零件图号、零件名称、材料牌号、毛坯种类、卡片编号、版次、编制人和编制日期。主体是工序明细工序号、工序名称、工艺内容、设备名称、工装夹具、工时定额一行一道工序一个零件往往有十几道。尾部是审核批准审核人、批准人、日期。另外还有修改记录修改标记、修改内容、修改人。这几段信息对应到数据库里实体清单就出来了产品表 product、零件表 part、工艺卡片主表 process_card、工序明细表 process_step、设备表 equipment、工艺员表 staff再加一个 sys_user 做登录。关系也很清晰一个产品包含多个零件一个零件可能有多版工艺卡片一张卡片有多道工序。这天然就是主从表加一对多的结构非常契合数据库课设“ER 图画得出来、关系讲得清楚”的评分要求。顺带对比一下选题图书管理系统的实体关系太浅商城系统的订单主从表和工艺卡片类似但业务细节复杂。工艺卡片系统最难得的地方是它有“版本”概念——同一零件因为设计改版会有多张卡片这在图书和商城里都不存在却在答辩时能引出一段关于数据冗余和范式设计的讨论这正是老师想听的。2.2 建表 SQL主外键、唯一约束与范式一次定好我一般会建六张表核心是 process_card 和 process_step 这对主从表。先看建表脚本。-- 产品表 CREATE TABLE product ( product_id INT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(64) NOT NULL COMMENT 产品名称 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 零件表 CREATE TABLE part ( part_id INT PRIMARY KEY AUTO_INCREMENT, part_no VARCHAR(32) NOT NULL UNIQUE COMMENT 零件图号, part_name VARCHAR(64) NOT NULL COMMENT 零件名称, material VARCHAR(32) NOT NULL COMMENT 材料牌号, product_id INT NOT NULL COMMENT 所属产品, FOREIGN KEY (product_id) REFERENCES product(product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 工艺员表同时兼任系统登录账号 CREATE TABLE staff ( staff_id INT PRIMARY KEY AUTO_INCREMENT, staff_no VARCHAR(16) NOT NULL UNIQUE COMMENT 工号, staff_name VARCHAR(32) NOT NULL COMMENT 姓名, role TINYINT NOT NULL COMMENT 1-工艺员 2-审核员 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 设备表 CREATE TABLE equipment ( equipment_id INT PRIMARY KEY AUTO_INCREMENT, equipment_name VARCHAR(32) NOT NULL COMMENT 设备名称, equipment_type VARCHAR(32) NOT NULL COMMENT 车床/铣床/磨床 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 工艺卡片主表 CREATE TABLE process_card ( card_id INT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(32) NOT NULL UNIQUE COMMENT 工艺卡片编号, part_id INT NOT NULL, version_no INT NOT NULL DEFAULT 1 COMMENT 版本号, create_date DATE NOT NULL, maker_id INT NOT NULL COMMENT 编制人, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1已审核, FOREIGN KEY (part_id) REFERENCES part(part_id), FOREIGN KEY (maker_id) REFERENCES staff(staff_id), UNIQUE KEY uk_part_version (part_id, version_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 工序明细表 CREATE TABLE process_step ( step_id INT PRIMARY KEY AUTO_INCREMENT, card_id INT NOT NULL, step_no INT NOT NULL COMMENT 工序号, step_name VARCHAR(32) NOT NULL COMMENT 工序名称, content VARCHAR(500) NOT NULL COMMENT 工艺内容, equipment_id INT NULL COMMENT 检验工序没有设备, time_quota DECIMAL(6,2) NOT NULL DEFAULT 0 COMMENT 工时定额 小时, FOREIGN KEY (card_id) REFERENCES process_card(card_id), FOREIGN KEY (equipment_id) REFERENCES equipment(equipment_id), UNIQUE KEY uk_card_step (card_id, step_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本把课设的几个关键考点都埋进去了。card_no 用 UNIQUE 保证业务编号不重复uk_part_version 防止同一个零件重复创建两个同版本卡片process_step 的联合唯一键 (card_id, step_no) 防止同一张卡片里出现两个 10 号工序。status 字段用来演示“草稿到审核”的状态流转。time_quota 用 DECIMAL 而不是 FLOAT是因为浮点累计工时会出现 0.30000000000000004 这类显示问题答辩时被老师看到会扣印象分。这里有个设计取舍要说明为什么主键用自增的 card_id而不是直接用 card_no因为工序表要引用卡片如果外键引用一个 32 位变长字符串索引占用大、关联查询慢。业务编号和数据库主键分开是课设答辩第二容易被追问的点把理由写在设计文档里。2.3 字符集与排序规则写进建库语句的隐藏参数很多课设把 DEFAULT CHARSETutf8mb4 写进每张表但建库时不指定导致库的默认字符集是 latin1表又是 utf8mb4。这样跨表联查时偶尔会出现 charset mismatch 报错中文数据在部分工具里也显示乱码。最稳的做法是建库时就一次定死。CREATE DATABASE craft_card DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;排序规则里utf8mb4_general_ci 和 utf8mb4_unicode_ci 对课设没有本质差别选 general_ci 就好比较性能略好。真正要统一的是三处建库语句、每张表的 DEFAULT CHARSET、JDBC 连接串的 characterEncodingUTF-8。三处一致中文乱码的概率基本为零。字符集之外还有个字段类型细节工时定额要精确到 0.01 小时DECIMAL(6,2) 的范围是 9999.99 小时对单道工序绰绰有余。如果做“按产品汇总工时”功能SUM 一下直接出结果。而 create_date 用 DATE 不用 DATETIME因为工艺卡片只精确到天答辩时解释“业务上不需要时分秒”也算一个合理设计判断。3. 用 JDBC 数据库连接池跑通增删改查事务边界与多表联查这一章进入代码。工艺卡片系统的界面通常包括卡片列表、工序明细表、登录窗口、设备管理数据库访问这部分我推荐用 HikariCP 连接池做分层封装。3.1 三层结构与连接池为什么别把 SQL 写在界面事件里界面里直接写 JDBC 的课设我见过不少演示能过但老师一问“改需求怎么办”就卡壳。工艺卡片系统的查询条件有卡片编号、零件名、材料牌号多种组合如果 SQL 和界面代码裹在一起改一个查询条件要牵连所有窗口。常见做法是拆三层UI 包只放界面页面Service 包处理业务判断DAO 包只负责 SQL。对课设来说不用引入 Spring手动分层足够。数据库访问这块用 HikariCP 而不是每个页面新建 Connection理由有两层。对课设答辩来说连接池是高频考点“如果 50 个学生同时打开系统数据库连接会不会耗尽”。对稳定性来说Swing 界面频繁开关窗体每次增删改查都新建连接演示到一半 MySQL 会报 Too many connections。HikariCP 配置起来很短。// DbPool.java —— 基于 HikariCP 的最小连接池封装 import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class DbPool { private static HikariDataSource ds; static { HikariConfig cfg new HikariConfig(); cfg.setJdbcUrl(jdbc:mysql://localhost:3306/craft_card ?useSSLfalsecharacterEncodingUTF-8serverTimezoneAsia/Shanghai); cfg.setUsername(root); cfg.setPassword(123456); cfg.setMaximumPoolSize(10); // 池里最多10个连接 cfg.setMinimumIdle(2); // 空闲时保持2个 cfg.setConnectionTimeout(3000); // 3秒拿不到连接就失败 ds new HikariDataSource(cfg); } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }参数说明useSSLfalse 是因为本机演示不需要证书serverTimezoneAsia/Shanghai 解决 MySQL 8.x 与 JDBC 时区差 8 小时的问题。maximumPoolSize 对桌面单机程序 10 足够不需要再大连接池太大反而浪费内存。如果老师让你去掉第三方依赖只用原生 DriverManager也能答但把连接池放进来是立意更高的做法说明你考虑了并发场景。3.2 主从表新增一个事务保证卡片和工序同时成功工艺卡片系统中新增一张卡片必然要同时插入若干道工序。如果先插卡片成功、插工序时失败数据库里就会出现一张没有工序的“空卡片”这是典型的数据不一致。正确写法是把主表和明细表的插入放在同一个事务里要么全部成功要么全部回滚。// CardService.java —— 新增工艺卡片及明细工序 public boolean addCardWithSteps(ProcessCard card, ListProcessStep steps) { Connection conn null; PreparedStatement psCard null; PreparedStatement psStep null; try { conn DbPool.getConnection(); conn.setAutoCommit(false); // 关闭自动提交事务从这里开始 // 1. 插入主表并取回自增主键 String sqlCard INSERT INTO process_card (card_no, part_id, version_no, create_date, maker_id, status) VALUES(?,?,?,?,?,?); psCard conn.prepareStatement(sqlCard, Statement.RETURN_GENERATED_KEYS); psCard.setString(1, card.getCardNo()); psCard.setInt(2, card.getPartId()); psCard.setInt(3, card.getVersionNo()); psCard.setDate(4, new java.sql.Date(card.getCreateDate().getTime())); psCard.setInt(5, card.getMakerId()); psCard.setInt(6, 0); // 初始状态草稿 psCard.executeUpdate(); ResultSet keySet psCard.getGeneratedKeys(); int cardId 0; if (keySet.next()) { cardId keySet.getInt(1); // 刚插入的 card_id } // 2. 批量插入工序明细 String sqlStep INSERT INTO process_step (card_id, step_no, step_name, content, equipment_id, time_quota) VALUES(?,?,?,?,?,?); psStep conn.prepareStatement(sqlStep); for (ProcessStep s : steps) { psStep.setInt(1, cardId); psStep.setInt(2, s.getStepNo()); psStep.setString(3, s.getStepName()); psStep.setString(4, s.getContent()); psStep.setObject(5, s.getEquipmentId()); // 允许为 NULL psStep.setBigDecimal(6, s.getTimeQuota()); psStep.addBatch(); } psStep.executeBatch(); 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 (psStep ! null) psStep.close(); if (psCard ! null) psCard.close(); if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }逻辑说明先插入主表再用 RETURN_GENERATED_KEYS 拿到自增的 card_id然后所有工序都带这个外键批量插入。executeBatch 把多道工序的插入在一次网络往返中发给 MySQL对课设数据量性能无所谓但代码整洁。最关键的是 setAutoCommit(false) 到 commit 之间的区域中间任何一步抛异常都会触发 rollback数据库里不会留下半截数据。3.3 卡片列表查询多表联查怎么写才不被老师挑毛病查询列表通常要做三件事按卡片编号或零件名模糊搜索、显示卡片所属零件、显示每张卡片总工时。直接 SELECT * FROM process_card 然后去界面里循环查零件名会带来 N1 查询问题代码也低效。-- 工艺卡片列表联查 SELECT c.card_id, c.card_no, c.version_no, c.create_date, c.status, p.part_no, p.part_name, p.material, (SELECT SUM(s.time_quota) FROM process_step s WHERE s.card_id c.card_id) AS total_quota FROM process_card c JOIN part p ON c.part_id p.part_id WHERE c.card_no LIKE CONCAT(%, ?, %) OR p.part_name LIKE CONCAT(%, ?, %) ORDER BY c.create_date DESC LIMIT ?, 20;参数说明LIMIT ?, 20 配合界面分页第一个参数是偏移量。total_quota 用相关子查询而不用 GROUP BY是因为 GROUP BY 会把卡片行合并万一某张卡片只有一条工序联查时如果没有对 c 做聚合函数SQL 又在 ONLY_FULL_GROUP_BY 模式下直接报错。相关子查询对每张卡片单独算合计行数保持在“一张卡片一行”对界面分页最友好。这个查询在答辩时有一个必讲点索引。WHERE 里的 c.card_no LIKE 用了前后双百分号会导致索引失效。我一般会在设计文档里写清楚卡片编号支持精确匹配时走 uk_card_no 索引模糊搜索时全表扫描在数据量低于一万行时影响不大。老师如果问到可以现场用 EXPLAIN 展示扫描行数这比背概念更有说服力。4. 老师检查课设时重点看什么演示节奏、数据准备与检查点数据库课设的验收老师手上一般有三样东西你交的 ER 图、SQL 脚本或数据库导出文件、能跑起来的程序。最容易被扣分的不是功能缺失而是三样对不上。4.1 ER 图、数据库脚本、运行结果三样对齐常见的翻车现场是ER 图里画了六个实体数据库里只有五张表脚本建出来的字段和程序里 insert 的字段名不一致文档里写“支持版本管理”界面却没有版本入口。老师一眼就能看出哪些是后补的材料。我的做法是先把 ER 图画完图上每一个实体标注对应表名每一条关系标注外键字段然后按 ER 图回头检查建表 SQL最后在程序里加一个“数据库自检”按钮用一条 SQL 查询 information_schema 里的表清单显示在日志页。-- 数据库自检列出当前库所有表用于核对ER图 SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema craft_card ORDER BY table_name;这样验收时老师让你对照 ER 图找表你可以一秒指出来印象分会好很多。脚本文件里还要注意导出顺序先导库结构再导数据触发器、存储过程放最后不然在新机器上导入时外键先执行会报找不到父表。4.2 演示数据要有业务含义增删改查覆盖完整流程工艺卡片系统里的数据不能是 asdf、111、测试1 这类占位符。老师会随手点开一张卡片如果看到工序名是“步骤1”基本能判断你没接触过真实工艺。我一般会在初始数据里放三个产品、八个零件、每张卡片十到十五道工序材料牌号用 45钢、40Cr、Q235A工序名用下料、粗车、精车、磨削、检验。INSERT INTO product(product_name) VALUES (减速器), (齿轮泵); INSERT INTO part(part_no, part_name, material, product_id) VALUES (GB-301, 输入轴, 40Cr, 1), (GB-302, 从动齿轮, 45钢, 1); INSERT INTO process_card(card_no, part_id, version_no, create_date, maker_id, status) VALUES (PC-2024-018, 1, 1, 2024-05-20, 1, 1);演示顺序也要排一下先登录再按卡片编号查一张卡片展示工序明细然后新增一张卡片并带五道工序再修改一个工序的工时最后删除一张没有明细的卡片。这个流程覆盖增删改查全链路而且每个动作在界面上都能看到结果。4.3 准备一份演示脚本每个动作对应一个可观察的结果我在答辩前会自己走一遍脚本并把每个动作写在备注里。不是为了背稿是防止现场紧张后乱点点了某个按钮发现没反应就卡在那里。演示动作界面操作可观察结果背后知识点登录输入工号密码进入主界面用户表查询卡片查询输入 PC-2024-018列表显示一张卡片多表联查 模糊匹配查看明细双击卡片下方表格显示 12 道工序主从表外键查询新增卡片填写产品零件 5 道工序列表新增一条总工时自动算事务 自增主键修改工时把 0.5 改成 0.75明细表数字变化UPDATE 重新汇总删除空卡片选择无工序卡片点删除记录消失且无报错外键约束设计统计报表点按产品统计每个产品总工时清单存储过程 GROUP BY这张表比任何 PPT 都实用。最后一行统计报表建议用存储过程跑出来下面第 6 章会写怎么做。演示前还有三件环境准备确认 MySQL 服务已启动、确认账号密码能连上、把数据库删掉重新导入一次脚本确保目标机器上没有历史残留数据。5. 工艺卡片课设避坑指南5 个让演示当场翻车的真实坑这一章是我认为整个课设里最值钱的部分。以下五个坑我见过不同小组分别踩过有的甚至直接导致答辩中断。5.1 表中文字段全部乱码建库、建表、连接串三处字符集不一致现象界面和数据库工具里中文全是问号或乱码英文数字正常。 原因字符集在链路中不一致。建库时默认 latin1表单独设为 utf8mb4JDBC 连接串又没有指定 characterEncoding导致 MySQL 端给客户端返回 latin1 编码的字节Java 按 UTF-8 解码就成了乱码。 解决按 2.3 节把建库语句、表定义、连接串三处统一为 utf8mb4 / UTF-8。另外界面代码里不要用 new String(bytes, GBK) 这种强转写法。要彻底解决只能删库重来因为已经乱码的数据即使改了连接串也恢复不了。5.2 删除卡片报外键约束失败先删主表还是先删明细现象演示时想删除一张工艺卡片程序直接抛 SQLIntegrityConstraintViolationException界面弹红字。 原因process_step 表的外键引用 process_card 的 card_id直接删主表记录时 MySQL 拒绝执行这是 InnoDB 外键的默认行为。 解决在程序代码里用事务先删明细再删主表。conn.setAutoCommit(false); String delSteps DELETE FROM process_step WHERE card_id ?; String delCard DELETE FROM process_card WHERE card_id ?; // 先删明细再删主表最后 commit提示删除顺序不能反过来。如果想省事可以在建表时给外键加 ON DELETE CASCADE但一般不建议课设里用老师问起来反而多一个要解释的点。手动事务更能体现你理解主从表关系。5.3 MySQL 8.0 连接失败Public Key Retrieval is not allowed现象程序启动时连接池报 java.sql.SQLNonTransientConnectionException提示 Public Key Retrieval is not allowed。 原因MySQL 8.0 默认认证插件是 caching_sha2_password客户端第一次连接需要向服务器请求 RSA 公钥旧版 JDBC 驱动默认不允许这个行为。 解决连接串加 allowPublicKeyRetrievaltrue。如果用的还是 mysql-connector-java 5.x 驱动建议直接换 8.x因为 5.x 对 MySQL 8.0 的认证兼容性更差。加参数后仍连不上再检查 root 用户 host 是否为 localhost、密码是否真的正确。5.4 删除数据后磁盘占用没变小ibd 文件的物理空间复用现象界面里删了一批卡片MySQL 数据目录里 craft_card 对应的 ibd 文件大小没有减小老师看到后问“数据库是不是没删干净”。 原因InnoDB 删除数据只是把页标记为可复用不会自动收缩物理文件。这是 InnoDB 的常规行为不是 bug。 解决文档里别写“删除实时释放空间”这类错误结论。如果需要演示空间释放可以在命令行执行 OPTIMIZE TABLE process_card;但要注意该操作在数据量大时可能锁表课设数据量小不受影响。答辩时如实说明“InnoDB 的物理空间复用机制”反而显得扎实。5.5 事务“串味”setAutoCommit(false) 没复位第二次操作卡死现象第一次新增卡片成功第二次新增时明明没点提交数据库里却多了一条记录。更诡异的是一个窗口删了数据另一个窗口的查询也带上未提交的数据。 原因HikariCP 的连接是复用的。代码里 setAutoCommit(false) 提交完事务后连接归还池子时如果 autoCommit 还是 false下一次拿到这个连接的人执行 update 后不会自动提交但某些查询却能读到上一个事务的残留状态这就是连接状态泄漏。 解决在 finally 块里把 autoCommit 设回 true 再 close。另一个相关坑是catch 到异常后没有 rollback 就直接 return导致未提交事务一直占着行锁后面所有对同一行的 UPDATE 都卡住界面像死锁一样。所以 catch 里必须 rollbackfinally 里必须复位。这五条按“现象、原因、解决”写进你的调试日志答辩时能回答“开发中遇到过什么坑”这个问题而且是有细节的坑不是背概念。6. 答辩加分技巧存储过程做工时统计并发锁演示隔离级别如果前面都跑通了想在答辩环节拿点加分项最划算的两件事是写一个按产品汇总工时的存储过程再演示一下并发更新同一行时的锁现象。这两件事业务价值讲得通代码量也不大。-- 统计指定产品所有工艺卡片的总工时 CREATE PROCEDURE sp_total_quota_by_product(IN prod_id INT) BEGIN SELECT p.product_id, p.product_name, COUNT(DISTINCT c.card_id) AS card_count, ROUND(SUM(s.time_quota), 2) AS total_hours FROM product p JOIN part pt ON p.product_id pt.product_id JOIN process_card c ON pt.part_id c.part_id JOIN process_step s ON c.card_id s.card_id WHERE p.product_id prod_id GROUP BY p.product_id, p.product_name; END;调用方式 CALL sp_total_quota_by_product(1);。ROUND 的意义在于即使字段是 DECIMAL对多行 SUM 后仍可能出现长尾小数报表显示 283.75 比 283.75000001 专业。老师如果问“存储过程和普通 Java 循环求和有什么区别”回答要点是存储过程在数据库实例内执行不需要把明细数据传到客户端对几十万行级别的数据性能优势明显。并发锁的演示不需要写代码开 Navicat 两个查询窗口就行。第一个窗口执行 BEGIN; UPDATE process_step SET time_quota 2.5 WHERE step_id 100;第二个窗口对同一行再执行一次 UPDATE会一直等待。然后第一个窗口执行 COMMIT;第二个窗口立刻返回成功。整个过程展示了行级锁会一直持有到事务结束比任何文字描述都有说服力。注意别演示成死锁——死锁需要两个事务互相等对方持有的行现场难度大且容易吓到老师不推荐。实验做完记得回滚第一个窗口别把演示数据改脏。索引方面可以再加一个小验证对工艺卡片列表查询里的 part_name 建索引后执行 EXPLAIN看 type 从 ALL 变成 ref。课设里不用真做性能优化但说清楚“索引减少扫描行数”就达到答辩要求。最后一个建议来自我自己做课设时的教训所有功能最后都过一遍删除流程再封版尤其是带外键关系的删除。我有一次新增、修改、查询都调通了觉得稳了第二天演示时删一张卡片被外键卡住好在有准备调换了删除顺序才没丢人。后来养成了习惯提交前把演示动作从头到尾走两遍第二遍故意走错看系统报错是否可理解。工作以后做生产系统这个习惯也一直管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表