2026/10/31 4:04:13

学生宿舍管理系统课程设计:SQL建表与调宿事务实战

学生宿舍管理系统课程设计:SQL建表与调宿事务实战 简介面向高校数据库课程设计场景的学生宿舍管理系统完整实践资料包适合正在完成数据库课程设计、需要参考建表思路与前后端功能实现的学生使用。资源围绕寝室长与宿管员两类角色展开涵盖登录身份判断、寝室人员信息查看、报修提交与处理、学生住宿信息的按学号检索与增删、学生账号管理以及密码修改等模块并给出寝室长、宿管员、报修、学生信息四张核心表单的字段设计可作为需求分析与数据库建模的对照样本。压缩包共3个文件包含1个rar源代码包、1个sql建库建表脚本和1个docx文档模板整体约2.96MBsql文件可直接导入数据库验证表结构与初始数据Word模板可支撑课程设计报告的撰写。目前已有5057人学习下载便于读者快速搭建可运行系统、理解权限划分逻辑并补齐设计文档减少从零摸索的时间。1. 答辩前一晚老师只问了一句学生退宿后床位记录为什么还在去年这个时候某高校的一位同学抱着笔记本来找我系统能跑增删改查都正常界面也做得挺像回事。老师翻了两页报告问了一句“学生退宿之后床位记录为什么还留着”他当场卡住。这类学生宿舍管理系统课程设计代码量其实不大真正拉开分差的全在数据库那一层——表之间怎么约束、调宿怎么保证不会出现一张床住两个人、删学生时关联记录怎么处理、报修工单状态怎么流转。这份资源包给的是三样东西一份能直接执行的 sql 建表脚本、一套可运行的 Java Swing MySQL 源代码、一份 Word 课程设计报告模板。功能覆盖宿舍楼、房间、床位、学生、入住记录、报修、访客、管理员八块。适合正在做数据库课程设计、想要一份能跑又能讲明白的底稿的同学也适合代码写完但报告一个字憋不出来的人。下面按我实际拆这套代码的顺序讲从目录结构一路讲到怎么改。2. 骨架拆解从 sql 脚本到三层代码的目录与依赖拿到一份课程设计源码我一般不看界面先看目录和建表脚本。界面好看是加分项表设计错了整个系统就是沙上建塔改一处塌一片。这一章先把资源包的物理结构、数据库设计和代码分层讲透后面两章才有东西可谈。2.1 资源包目录结构与技术栈解压之后目录大致是这样我按真实项目习惯补了注释DormSystem/ ├── sql/ │ ├── 01_schema.sql -- 建库、建表、外键、索引先执行这个 │ └── 02_data.sql -- 初始数据管理员账号、几栋楼、若干房间床位 ├── src/ │ └── com/demo/dorm/ │ ├── Main.java -- 程序入口启动登录窗体 │ ├── entity/ -- 实体类和表一一对应 │ ├── dao/ -- 数据访问层只写 SQL 和结果集映射 │ ├── service/ -- 业务层事务、校验、状态流转都在这 │ ├── ui/ -- Swing 窗体只管展示和收参数 │ └── util/ │ ├── DBUtil.java -- 连接池/连接获取全项目唯一入口 │ └── ExcelUtil.java -- 统计结果导出 ├── lib/ │ └── mysql-connector-j-8.0.33.jar ├── doc/ │ └── 课程设计报告模板.docx └── README.md -- 环境版本、执行顺序、默认账号技术栈不复杂JDK 8 或以上、MySQL 8.0、JDBC 直连、Swing 做界面没上 Spring 那一套。有人会问为什么不用框架——课程设计的评分点是数据库设计能力和 SQL 编写能力套上 ORM 之后 SQL 全被框架藏起来答辩时老师让你现场写一个多表连接查询反而写不出来。我一般也建议这类作业保持原生 JDBC把PreparedStatement、事务、连接释放这几件事自己写一遍比背注解有用。需要引的外部依赖只有 MySQL 驱动一个 jar。如果你用 IDEA把lib目录右键 Add as Library 就行用 Eclipse 就加到 Build Path 里。别去 Maven 中央仓库拉最新版驱动8.0.33 这个版本和 MySQL 8.0 服务端匹配得比较稳新手换版本最容易在第一晚就翻车。2.2 建表脚本八张表怎么划出来建表脚本是整个资源包里最值钱的部分报告里的 E-R 图、表结构说明、数据字典全靠它。八张表的关系链是宿舍楼包含房间房间包含床位学生通过入住记录占用床位报修和访客分别挂在学生和房间上。先看楼、房、床这三张-- 宿舍楼最顶层先有楼才有房间 CREATE TABLE building ( building_id INT AUTO_INCREMENT PRIMARY KEY, building_name VARCHAR(32) NOT NULL, -- 如1号楼不写真实校名 floor_count TINYINT NOT NULL DEFAULT 6, manager VARCHAR(32), -- 楼管姓名 remark VARCHAR(128) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 房间挂在楼下一栋楼多个房间 CREATE TABLE room ( room_id INT AUTO_INCREMENT PRIMARY KEY, building_id INT NOT NULL, room_no VARCHAR(16) NOT NULL, -- 如 301 capacity TINYINT NOT NULL DEFAULT 4, -- 可住人数 gender_limit TINYINT NOT NULL DEFAULT 0, -- 0 不限 1 男 2 女 CONSTRAINT fk_room_building FOREIGN KEY (building_id) REFERENCES building(building_id), UNIQUE KEY uk_building_room (building_id, room_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 床位真正被占用的对象调宿的本质是换床位 CREATE TABLE bed ( bed_id INT AUTO_INCREMENT PRIMARY KEY, room_id INT NOT NULL, bed_no VARCHAR(8) NOT NULL, -- 房间内编号 1/2/3/4 status TINYINT NOT NULL DEFAULT 0, -- 0 空闲 1 已住 2 维修 CONSTRAINT fk_bed_room FOREIGN KEY (room_id) REFERENCES room(room_id), UNIQUE KEY uk_room_bed (room_id, bed_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这三张表的关键设计点在bed上。很多同学的作业把“住几个人”直接写在room表里用一个current_num字段记看着简单一调宿就出错——两个人同时退宿current_num减两次变成负数。把床位单独抽成一张表每一行代表一张实实在在的床谁住哪张床一目了然统计入住率也只是count(status1) / count(*)报告里画 E-R 图时层次也更清楚。再看学生和入住记录这两张它们是整个系统的业务核心-- 学生只存基本信息不存房间号 CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY, -- 学号做主键业务上唯一 name VARCHAR(32) NOT NULL, gender TINYINT NOT NULL, -- 1 男 2 女 college VARCHAR(64), -- 学院报告里用通用名称 phone VARCHAR(20), status TINYINT NOT NULL DEFAULT 1 -- 1 在校 0 已离校 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 入住记录学生和床位的历史关系表退宿不删记录只写结束时间 CREATE TABLE checkin ( checkin_id INT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20) NOT NULL, bed_id INT NOT NULL, start_date DATE NOT NULL, end_date DATE DEFAULT NULL, -- 为空表示当前仍在住 operator VARCHAR(32), -- 办理人审计用 CONSTRAINT fk_checkin_student FOREIGN KEY (student_id) REFERENCES student(student_id), CONSTRAINT fk_checkin_bed FOREIGN KEY (bed_id) REFERENCES bed(bed_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;checkin表用end_date是否为空来区分“在住”和“已退宿”这个设计直接决定了第 1 章那个问题的答案退宿不是删记录是把end_date填上、把床位status改回 0。历史记录保留下来以后查“这个学生大二住哪间”还能查得到报告里写“数据不做物理删除保留历史轨迹”这句话老师通常会点头。提示student_id用学号做主键而不是自增 ID答辩时容易被追问。理由要讲清楚——学号在业务上天然唯一且稳定用它做主键能让checkin表少一次关联查询如果学校存在学号复用的情况那就老老实实加自增主键、把学号设为唯一索引。剩下的报修、访客、管理员三张表结构类似repair表要带status状态字段和create_time、finish_time两个时间戳visitor表要带visit_time和leave_timeadmin表存账号密码和角色。建表脚本末尾记得加索引-- 高频查询走索引按学生查入住历史、按状态查待处理报修 CREATE INDEX idx_checkin_student ON checkin(student_id); CREATE INDEX idx_repair_status ON repair(status); CREATE INDEX idx_bed_status ON bed(status);索引不用多这三个是实际查询里一定会用到的。写报告的数据字典章节时把每张表的字段类型、是否为空、默认值、说明列成表评分表里“表结构设计”那一项基本就稳了。2.3 DAO/Service/UI 三层与登录会话代码分层的意义在课程设计里经常被忽略很多人一个MainFrame.java写到两千行所有 SQL 都塞在按钮事件里。这套代码分了三层虽然简单但边界清晰ui只负责收参数和弹窗service负责校验和事务dao只负责执行 SQL。DBUtil是全项目唯一的连接入口我一般会把它写得稍微讲究一点public class DBUtil { // 连接串三个参数都不能省后面避坑章节会讲为什么 private static final String URL jdbc:mysql://127.0.0.1:3306/dorm_db ?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PWD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); // 8.x 驱动的类名别写成 5.x 的 } catch (ClassNotFoundException e) { throw new RuntimeException(驱动没加载上检查 lib 目录, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // 关闭顺序必须和打开相反否则连接池会泄漏 public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (SQLException ignored) {} try { if (s ! null) s.close(); } catch (SQLException ignored) {} try { if (c ! null) c.close(); } catch (SQLException ignored) {} } }参数说明characterEncodingutf8解决中文写入变问号serverTimezoneAsia/Shanghai解决 8.0 驱动启动即报时区异常useSSLfalse是本地开发省掉证书校验。这三项少一个第一天就跑不起来我见过不止一个同学在这卡三小时。DAO 层统一用PreparedStatement一是防注入二是带参数时不用自己拼引号public ListBed listFreeBeds(Connection conn, int buildingId) throws SQLException { String sql SELECT b.bed_id, b.bed_no, r.room_no, bl.building_name FROM bed b JOIN room r ON b.room_id r.room_id JOIN building bl ON r.building_id bl.building_id WHERE b.status 0 AND r.building_id ? ORDER BY r.room_no, b.bed_no; ListBed list new ArrayList(); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, buildingId); // 参数下标从 1 开始不是 0 try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Bed bed new Bed(); bed.setBedId(rs.getInt(bed_id)); bed.setBedNo(rs.getString(bed_no)); bed.setRoomNo(rs.getString(room_no)); bed.setBuildingName(rs.getString(building_name)); list.add(bed); } } } return list; }这段查询是三表连接报告里“多表查询”章节可以直接引用。注意try-with-resources里传进去的conn不会被关闭它由调用方service层统一管理生命周期这是分层之后必须守住的约定——DAO 不许关别人传进来的连接否则外层事务直接就断了。登录这块用一个Session静态类存当前用户登录成功后写入adminId和role主窗体根据role决定菜单项是否可见。简单但够用比把用户名塞进System.setProperty之类的野路子干净得多。3. 核心模块实战调宿事务、报修流转与统计查询界面能点通不算本事课程设计真正会被追问的是三个地方调宿会不会把数据搞乱、报修状态能不能乱跳、统计数字对不对。这一章按业务链路拆三段代码每一段都能直接抄进你的作业里也能直接对应到报告的功能实现章节。3.1 宿舍调换一个必须走事务的操作调宿看起来只是“换个房间”实际上要动三张表旧入住记录写结束日期、新入住记录插入、两个床位的状态互转。这四步任何一步失败都会留下脏数据所以必须放在一个事务里。更麻烦的是并发——两个管理员同时把同一个学生往同一张空床上调不锁的话两边都判断“床位空闲”最后一张床两条在住记录。public String changeBed(String studentId, int toBedId, String operator) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关自动提交后面几步要么全成要么全败 // 1. 锁目标床位FOR UPDATE 让并发请求在这里排队 Bed target bedDao.selectForUpdate(conn, toBedId); if (target null) { conn.rollback(); return 目标床位不存在; } if (target.getStatus() ! 0) { conn.rollback(); return 该床位已被占用或处于维修状态; } // 2. 找当前在住记录没有说明这学生还没入住 Checkin current checkinDao.findActiveByStudent(conn, studentId); if (current null) { conn.rollback(); return 该学生当前没有在住记录请先办理入住; } if (current.getBedId() toBedId) { conn.rollback(); return 目标床位就是当前床位; } // 3. 旧记录写结束日期学生表状态不变 checkinDao.closeCheckin(conn, current.getCheckinId(), new Date()); // 4. 插入新入住记录 checkinDao.insert(conn, studentId, toBedId, new Date(), operator); // 5. 两个床位状态互换这一步最容易漏 bedDao.updateStatus(conn, current.getBedId(), 0); bedDao.updateStatus(conn, toBedId, 1); conn.commit(); return OK; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} return 调宿失败 e.getMessage(); } finally { DBUtil.close(conn, null, null); } }逐段说明第 1 步的selectForUpdate对应 SQL 是SELECT ... FROM bed WHERE bed_id ? FOR UPDATE它在 InnoDB 里加的是一把行锁事务提交前别的连接查同一行会阻塞这正是我们要的效果。第 3 步只填end_date不删记录历史可追溯。第 5 步把旧床位改回 0、新床位改成 1很多人只做了前三步界面上看着调过去了一统计入住率就发现总数对不上这就是第 1 章那个问题的翻版。参数方面operator传当前登录管理员的名字写进checkin表做审计toBedId必须是用户从空闲床位列表里选出来的不能手输否则会出现往一个不存在的床位上调。返回值用字符串而不是布尔是为了让界面能弹出具体原因答辩演示时体验差别很大。有个细节值得在报告里单独写一段为什么不用数据库触发器自动维护床位状态。答案是触发器里拿不到“操作人”而且调试困难出问题只能看日志。业务逻辑放service层SQL 只做数据搬运这是我这几年越来越坚持的划分方式。3.2 报修工单状态机的四步流转报修模块的价值在于状态流转不是简单的增删改查。工单有四个状态0 待受理、1 处理中、2 已完成、3 已取消。合法的跳转只有 0→1、1→2、0→3、1→3 四种随便改状态就是业务漏洞。// 合法的状态迁移表用二维数组比一长串 if-else 好维护 private static final boolean[][] ALLOWED { // 到: 0 1 2 3 /* 0 */ {false, true, false, true}, /* 1 */ {false, false, true, true}, /* 2 */ {false, false, false, false}, /* 3 */ {false, false, false, false} }; public String updateStatus(int repairId, int from, int to, String handler) { if (from 0 || from 3 || to 0 || to 3 || !ALLOWED[from][to]) { return 不允许从状态 from 直接变为 to; } String sql UPDATE repair SET status ?, handler ?, finish_time CASE WHEN ? 2 THEN NOW() ELSE finish_time END WHERE repair_id ? AND status ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, to); ps.setString(2, handler); ps.setInt(3, to); ps.setInt(4, repairId); ps.setInt(5, from); // 带上原状态做乐观锁 int rows ps.executeUpdate(); return rows 1 ? OK : 工单状态已被他人修改请刷新后重试; } catch (SQLException e) { return 更新失败 e.getMessage(); } }关键在WHERE repair_id ? AND status ?这个条件。它做的是乐观锁如果两个人同时打开同一条待受理工单一个点了“受理”另一个点了“取消”第二个人的UPDATE因为status已经变了而影响 0 行返回提示让他刷新。这种写法不用加锁也不用事务比FOR UPDATE轻适合这种冲突概率低的场景。finish_time用CASE WHEN在 SQL 里判断省一次额外更新。报告里可以把状态迁移图画成有向图Word 里用形状画别用文字描述配一句“非法跳转在业务层拦截不依赖前端按钮置灰”这一句写在测试章节前面能加分。界面上报修列表默认按status升序、create_time倒序排待受理的永远在最上面。这个小细节在演示时很讨巧老师点进去第一眼看到的就是需要处理的工单。3.3 统计查询与 Excel 导出统计是课程设计里最容易出彩也最容易翻车的部分。楼栋入住率、男女生分布、报修处理及时率这三个指标够用了。-- 每栋楼的床位使用情况一条 SQL 出结果 SELECT bl.building_name, COUNT(b.bed_id) AS total_beds, SUM(CASE WHEN b.status 1 THEN 1 ELSE 0 END) AS used_beds, ROUND(SUM(CASE WHEN b.status 1 THEN 1 ELSE 0 END) / COUNT(b.bed_id) * 100, 2) AS usage_rate FROM building bl JOIN room r ON r.building_id bl.building_id JOIN bed b ON b.room_id r.room_id GROUP BY bl.building_id, bl.building_name ORDER BY usage_rate DESC;这条 SQL 用了CASE WHEN做条件计数比写两个子查询快也是数据库课里“聚合函数 分组”的标准考点。ROUND(..., 2)保留两位小数避免界面上出现一长串浮点数。注意GROUP BY里带了building_id因为building_name理论上可能重名按主键分组更稳。导出走ExcelUtil用 POI 把ResultSet逐行写进Workbook。这里有个坑ResultSet不能直接转成表格得先定义一个ListMapString, Object中间结构DAO 查出来、Service 组装、Util 只负责写文件三层各干各的。报告里写“导出模块与查询模块解耦查询结果可复用于界面表格和 Excel 两种输出”这句话能体现设计意识。统计结果我一般还会在界面上加一句口径说明比如“入住率 已住床位 / 总床位维修中床位计入总数”答辩时老师问数字怎么来的指着这行字就行比口头解释靠谱。4. 运行与避坑排查从驱动 jar 到中文乱码代码看懂是一回事跑起来是另一回事。这一章按真实环境走一遍然后把我见过的、自己踩过的五个坑摆出来每个都按“现象—原因—解决”写对着排就行。4.1 三步启动建库、导数据、跑主类第一步装 MySQL 8.0装的时候字符集选utf8mb4排序规则选utf8mb4_general_ci。装完用命令行连一下确认SELECT VERSION();能出结果。第二步执行脚本。用 Navicat 或者命令行都行顺序不能反# 先建库建表再灌初始数据 mysql -uroot -p123456 sql/01_schema.sql mysql -uroot -p123456 sql/02_data.sql # 确认数据进去了应该有 1 个管理员、3 栋楼、若干房间床位 mysql -uroot -p123456 -e USE dorm_db; SELECT COUNT(*) FROM bed;第三步IDEA 里把lib/mysql-connector-j-8.0.33.jar加进项目依赖改DBUtil里的账号密码运行Main.java。默认管理员账号在doc或者README里写着登录进去先点“宿舍管理”看楼栋列表有没有数据有数据说明连接和脚本都通了。提示01_schema.sql开头一般有DROP DATABASE IF EXISTS dorm_db;和CREATE DATABASE dorm_db DEFAULT CHARSET utf8mb4;重复执行不会报错。但02_data.sql里的INSERT如果重复跑会撞主键改数据之前先确认一下。4.2 五条踩坑记录每条都能让你少熬一晚坑一java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver现象是程序一启动就抛异常控制台第一行就是这个。原因是驱动 jar 没加进 classpath或者加了但版本是 5.x类名应该是com.mysql.jdbc.Driver。解决办法确认lib目录下有mysql-connector-j-8.0.33.jarIDEA 里 File → Project Structure → Modules → Dependencies 加上Eclipse 右键项目 → Build Path → Add JARs。别把 jar 只复制到目录里就以为加上了这一步很多人翻车。坑二写入的中文变成问号???现象是界面上输入“张三”存进数据库查出来是三个问号。原因有两层连接串少了characterEncodingutf8或者建库时用了latin1。解决连接串补上useUnicodetruecharacterEncodingutf8然后SHOW CREATE DATABASE dorm_db;看字符集不是utf8mb4就重建库把01_schema.sql里的建库语句改成DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci后重跑。已经写坏的数据改不回来只能重灌。坑三启动报The server time zone value ... is unrecognized现象是第一次拿连接就失败异常信息里带时区。原因是 MySQL 8.0 驱动要求显式指定时区服务端又是系统默认时区。解决连接串加serverTimezoneAsia/Shanghai或者serverTimezoneGMT%2B8。这是一行常量的事但不加就是跑不起来。坑四调宿之后入住率对不上总数少一张床现象是调宿功能点了没报错但统计页面显示已住床位比学生数少一个。原因基本是只改了入住记录、没同步更新bed.status旧床位还停在 1新床位也是 1两边都算“已住”但实际只有一个人。解决办法把换状态那两行补进同一个事务里然后写一条校对 SQL 定期跑-- 找出状态是已住但没有任何在住记录的床位正常应为空 SELECT b.bed_id, b.room_id, b.bed_no FROM bed b LEFT JOIN checkin c ON c.bed_id b.bed_id AND c.end_date IS NULL WHERE b.status 1 AND c.checkin_id IS NULL;这条校对 SQL 我建议直接写进报告的“数据一致性测试”小节比干说“系统保证一致性”有说服力得多。坑五删学生时报外键约束错误现象是Cannot delete or update a parent row: a foreign key constraint fails。原因是checkin表还引用着这个学生。解决思路有两个一是软删除把student.status改成 0 表示离校不做物理删除二是先删关联记录再删学生但这会丢历史。课程设计里选第一种并在报告里写明“学生数据不做物理删除通过状态字段标记离校保留入住历史”。老师问起来这就是标准答案。5. 二次开发加一个字段要走完的四步和报告怎么写才不虚这套代码最常见的二次开发需求是“加个空调字段”“加个辅导员联系方式”“按学院筛选”。加字段不是改完数据库就完事得按顺序走四步漏一步界面就不显示。以给room表加has_ac是否有空调为例-- 第一步改表结构给默认值避免存量数据为 NULL ALTER TABLE room ADD COLUMN has_ac TINYINT NOT NULL DEFAULT 0 COMMENT 0 无空调 1 有空调 AFTER gender_limit;第二步改Room.java实体类加private Integer hasAc;和 getter/setter第三步改RoomDao的insert、update、list三个方法SQL 里补上字段和占位符ResultSet里补rs.getInt(has_ac)第四步在RoomPanel的表格模型里加一列编辑弹窗里加一个复选框。四步走完列表、新增、修改三处同时生效。改完之后一定要重新跑一遍那条校对 SQL再点一遍调宿流程。我自己的习惯是每加一个字段就把“新增房间 → 分配学生 → 调宿 → 退宿 → 统计”这条链路手动走一遍五分钟的事能挡掉八成回归问题。血泪经验是只测新增不测修改改完字段名对不上ResultSet抛Column not found现场演示直接黑匣子。报告模板这部分我说几个实际写起来容易写空的地方。需求分析别抄百度把系统里真实存在的八个功能模块列成表每个模块写一句“输入什么、处理什么、输出什么”。E-R 图用 Word 自带的形状画实体用矩形、属性用椭圆、关系用菱形标上 1:N 和 M:N别用截图糊弄。表结构章节直接把建表脚本里的COMMENT抄进去那是现成的数据字典。测试章节别写“测试通过”写具体用例输入不存在的学号调宿期望提示“该学生当前没有在住记录”实际也是这句这才叫用例。最后说个答辩技巧。老师最爱问的三个问题是为什么床位要单独建表、调宿为什么要用事务、非法状态跳转怎么拦。这三个问题的答案分别在 2.2、3.1、3.2把这三段代码记住现场指着屏幕讲比背 PPT 强。我当年第一次做这类课设图省事把房间和床位合成一张表调宿调了三天没调明白从那以后我每次设计表结构都先把“谁被占用、谁被引用”这两个问题写清楚再动手建表。希望帮到你。本文还有配套的精品资源点击获取