多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Java+MySQL+Swing实现会议预约管理系统:数据库课设完整指南

Java+MySQL+Swing实现会议预约管理系统:数据库课设完整指南 简介这是一套面向数据库课程设计的会议预约管理系统项目开发语言为Java数据库采用MySQL界面使用Swing构建适合计算机相关专业学生完成课程设计、毕业设计或日常Java与数据库整合练习。系统围绕会议预约管理核心业务实现了用户登录、会议申请、审批处理、会议信息展示、按时间查询等功能并采用分层结构将界面、业务逻辑与数据访问分离便于理解和二次开发。资源包共33个文件包括22个Java源文件、3个JAR依赖库、1个SQL脚本、课程设计报告及工程配置文件整体仅5.02MB。SQL脚本可直接导入MySQL完成库表创建JAR包已集成MySQL驱动、JUnit和Lombok省去环境搭建烦恼Java源码按包组织覆盖实体类、DAO层和Swing界面配合readme说明可快速运行。目前已有860人学习下载尤其适合需要完整项目参考、快速完成报告和演示答辩的同学。1. 数据库课程设计里的经典选题JavaMySQLSwing 的会议预约管理系统到底在做什么数据库课程设计里用 JavaMySQLSwing 实现会议预约管理系统是出现频率最高的选题之一因为它刚好覆盖课设最看重的三件事数据表设计、Java 操作 MySQL、能演示的图形界面。它看着容易无非是登录、会议室列表、提交预约几个窗口但真做起来从表结构到并发冲突每一层都有坑。我见过不少同学把系统做成“能登录、能录入、能查询”的演示壳子答辩时被问“同一时间两个人约同一间会议室怎么办”就卡壳。下面按可提交的粒度来讲数据模型怎么建、JDBC 与 Swing 怎么连、事务与并发怎么处理最后是避坑清单和答辩加分项。中间给出的 SQL 和 Java 代码都可以直接抄进自己的工程里改改就能跑但更重要的是搞清楚每段代码为什么这么写——答辩老师追问的往往是“为什么”而不是“是什么”。适合正在做这个题目的同学也适合想让预约逻辑经得起追问的人。2. 数据模型先行从需求拆解到三张核心表的建表 SQL2.1 需求倒推表结构用户、会议室、预约单需要哪些字段会议预约管理系统的核心需求可以收敛成三句话谁能进系统用户、有什么可约会议室、约了什么时段预约单。大部分课设的复杂度都集中在这三张表上先别急着写代码拿张纸把字段列出来比你边写边改高效得多。用户表建议字段id、username、password、role、real_name、phone、created_at。role 用 TINYINT 区分管理员和普通用户比字符串省空间也比硬编码在代码里好维护。password 在课设里可以用 MD5 存但心里要清楚这只是为了演示登录流程真实系统不会这么存。会议室表建议字段id、room_name、capacity、location、equipment、status、remark。capacity 后面要用来做预约时的容量校验equipment 用逗号分隔的字符串存“投影仪,白板”这类信息课设粒度足够不需要为设备单独建一张表除非你想在答辩时讲第三范式拆分的理由。预约单表是最容易设计过头的表。我的建议是只放必要字段把“谁约的、约了哪个房间、什么主题、什么时间段、几个人、什么状态”六件事表达清楚即可。status 用 0/1/2 表示待确认、已确认、已取消如果课设没有审核流程要求直接用已确认一种状态也能交差不必为了凑功能硬加审核表。2.2 外键与联合索引为什么预约表不能只靠直觉设计三张表里真正决定系统质量的是预约单的外键和索引。user_id 和 room_id 必须建外键理由不只是“老师要求”而是外键能在你误删被引用数据时给出明确的数据库报错避免业务数据变成孤儿记录。另一个常见做法是在 (room_id, start_time) 上建联合唯一索引从数据库层面挡住“同一会议室同一开始时间被约两次”的重复插入。但要意识到这个索引的边界它挡不住时间段交叉的冲突。比如已有一条 14:00-15:00 的记录别人再提交 14:30-15:30 依然能插进去因为 start_time 不同。交叉判断必须在应用层完成第四章会专门展开写。索引本身也有代价联合索引的字段顺序要按“等值条件在前、范围条件在后”的习惯对预约查询来说更合适的索引是 (room_id, start_time, end_time)而不是把 end_time 放最前面否则很多查询用不上这个索引。2.3 初始化脚本直接在 MySQL 里执行的建库建表语句下面这份脚本在模拟项目X 里用过MySQL 8.0 可以直接执行。字符集统一用 utf8mb4别用 MySQL 默认的 latin1否则中文乱码问题会从建表一路追到你答辩前夜。CREATE DATABASE IF NOT EXISTS meeting_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE meeting_system; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 0-管理员 1-普通用户, real_name VARCHAR(32), phone VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE meeting_room ( id INT PRIMARY KEY AUTO_INCREMENT, room_name VARCHAR(64) NOT NULL, capacity INT NOT NULL DEFAULT 0, location VARCHAR(128), equipment VARCHAR(255) COMMENT 设备清单逗号分隔, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-可用 0-停用, remark VARCHAR(255) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE reservation ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, room_id INT NOT NULL, subject VARCHAR(128) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, attendee_count INT NOT NULL DEFAULT 1, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-待确认 1-已确认 2-已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_resv_user FOREIGN KEY (user_id) REFERENCES sys_user(id), CONSTRAINT fk_resv_room FOREIGN KEY (room_id) REFERENCES meeting_room(id), KEY idx_room_time (room_id, start_time, end_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段类型选择上时间字段用 DATETIME 而不是 TIMESTAMP因为 DATETIME 不依赖数据库时区课设环境和你演示的环境时区不一致时不容易出偏差。AUTO_INCREMENT 主键对这三张表都够用不需要 UUID。外键约束用 CONSTRAINT 显式命名后面报错信息里能直接看到是哪个约束被违反排查速度快很多。注意如果你的 MySQL 服务端字符集本来就是 latin1建库前先执行 SHOW VARIABLES LIKE character_set_server; 确认服务端默认字符集否则建库语句可能被覆盖。建完表再插两条演示数据方便后面联调界面INSERT INTO sys_user (username, password, role, real_name) VALUES (admin, MD5(123456), 0, 系统管理员), (alice, MD5(123456), 1, A同学); INSERT INTO meeting_room (room_name, capacity, location, equipment) VALUES (A301, 20, 3号教学楼301, 投影仪,白板), (B502, 50, 5号教学楼502, 投影仪,音响,视频会议);用 MD5 只是为了课设演示登录流程生产环境至少要换成带盐的哈希算法。如果你在答辩里主动说出这一点通常比藏着不说效果更好老师会觉得你对密码存储的边界是清楚的。3. Java 与 MySQL 连接层JDBC 驱动配置与 Swing 登录界面联调3.1 JDBC 驱动版本与连接参数最常见的启动即翻车点Java 连 MySQL 的第一步是引入驱动依赖。你需要确认驱动 jar 包和本机 MySQL 版本能对上MySQL 8.x 对应驱动类名是 com.mysql.cj.jdbc.Driver连接协议是 jdbc:mysql://如果本机装的是 MySQL 5.x旧驱动类名是 com.mysql.jdbc.Driver。很多压缩包里附带的驱动版本比较旧拿到手先确认版本再决定类名写哪个。我一般会用独立的 DBUtil 类把连接细节藏起来界面代码不直接碰 DriverManagerimport java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // url 里必须带 serverTimezone否则 8.x 驱动会报时区错误 private static final String URL jdbc:mysql://localhost:3306/meeting_system ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4; private static final String USER root; private static final String PASSWORD 改成你的密码; public static Connection getConnection() throws SQLException { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new SQLException(JDBC 驱动未找到检查 jar 包是否在构建路径里, e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }Class.forName 在 8.x 驱动里其实可以省略驱动会自动注册但保留这行有一个实际好处当 jar 包缺失时你能立刻看到 ClassNotFoundException而不是一个莫名其妙的连接超时。useSSLfalse 是关掉 SSL 握手本地课设环境没必要开开着反而可能因为证书问题连不上。serverTimezone 参数是应对 8.x 驱动对时区严格检查的写法漏掉它最常见的报错是一段与时区有关的异常信息里带乱码看起来很像玄学其实只是缺参数。提示8.x 驱动连 5.x 数据库一般没问题反过来 5.x 驱动连 8.x 数据库常常因为认证插件不兼容而握手失败。排查连接问题先确认驱动和数据库版本方向是否一致。3.2 登录模块从 JFrame 事件到 PreparedStatement 参数绑定Swing 的登录窗口逻辑很简单用户点登录按钮程序从文本框取值调数据库校验成功进主界面失败弹提示。但这里有一个所有课设都会被问到的安全点拼接 SQL 字符串是绝对不能做的。用 PreparedStatement 的 ? 占位符既避免了 SQL 注入也让代码在参数变多时更好读。public class LoginService { private int currentUserId; private int currentRole; public boolean login(String username, String password) { String sql SELECT id, role FROM sys_user WHERE username ? AND password MD5(?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { currentUserId rs.getInt(id); currentRole rs.getInt(role); return true; } } } catch (SQLException e) { e.printStackTrace(); } return false; } }这里用 MD5(?) 让密码在 SQL 层做哈希和建表时的 MD5(123456) 是配套的。注意 try-with-resources 的写法Connection、PreparedStatement、ResultSet 都自动关闭避免连接泄漏。很多同学登录模块能跑通之后就不再改这个结构后面每个功能都复制一份如果你想少踩坑建议把“取连接、预编译、查结果”的三段式固定成肌肉记忆。按钮事件绑定是纯 Swing 内容JButton loginBtn new JButton(登录); loginBtn.addActionListener(e - { String name usernameField.getText().trim(); String pwd new String(passwordField.getPassword()); if (name.isEmpty() || pwd.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } LoginService service new LoginService(); if (service.login(name, pwd)) { new MainFrame(service.getCurrentUserId(), service.getCurrentRole()).setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } });密码框要用 getPassword() 拿 char[] 再转成 String这是 Swing 组件本身的约束直接用 getText() 在部分 Java 版本上也能跑但属于不规范的写法。登录成功之后把 userId 和 role 带进主界面后续提交预约时直接取当前用户 id不用再查一次。trim() 用来去掉用户名首尾空格避免用户手滑多打一个空格导致登录失败这种很难发现的低级错误。3.3 查询结果到表格ResultSet 转 TableModel 的封装写法主界面的会议室列表和预约记录列表通常都用 JTable 展示。JTable 的数据来源要继承 AbstractTableModel最常见的死法是直接在模型里放一个 ResultSet然后界面刷新时发现游标已经关了。正确做法是把 ResultSet 的数据复制到内存里的 List 结构等数据库资源释放后表格依然能正常显示。public class RoomTableModel extends AbstractTableModel { private final String[] columns {ID, 会议室, 容量, 位置, 设备}; private final ListObject[] rows new ArrayList(); public void loadRooms() { rows.clear(); String sql SELECT id, room_name, capacity, location, equipment FROM meeting_room WHERE status 1 ORDER BY id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { rows.add(new Object[]{ rs.getInt(id), rs.getString(room_name), rs.getInt(capacity), rs.getString(location), rs.getString(equipment) }); } } catch (SQLException e) { e.printStackTrace(); } fireTableDataChanged(); // 通知 JTable 数据已更新触发重绘 } Override public int getRowCount() { return rows.size(); } Override public int getColumnCount() { return columns.length; } Override public Object getValueAt(int rowIndex, int columnIndex) { return rows.get(rowIndex)[columnIndex]; } Override public String getColumnName(int column) { return columns[column]; } }fireTableDataChanged 是表格刷新的关键只改 List 不会触发界面重绘必须调它通知监听器。这个小细节是很多新手第一次看到“数据明明变了但表格不更新”现象的根源。逻辑上这段代码做了三件事先清空旧数据再从 ResultSet 逐行复制到内存 List最后触发界面刷新。把查询结果从 ResultSet 复制到 List 还有一个附带好处表格排序、筛选可以纯内存操作不用每次点表头都回数据库。4. 事务与并发控制让会议预约在多人同时操作时不翻车4.1 并发冲突场景为什么“先查再插”会在两个客户端上同时成功假设预约流程没有任何防护用户点提交时程序先查会议室 A301 在 14:00-15:00 是否空闲空闲就插入一条预约。如果 A 同学和 B 同学同时打开两个窗口都查到“空闲”然后先后执行插入数据库里就会出现两条重叠记录。问题出在“查”和“插”之间有时间差这个时间差里双方看到的数据都没有变化检查形同虚设。这不是理论问题课程设计演示时只要开两个客户端就能复现。很多同学的预约系统在单人演示时一切正常一到答辩现场老师让两个同学同时操作就穿帮。解决办法是把检查和插入放进同一个数据库事务并且选对隔离级别让“同时发生”变成“一个成功、一个失败”。4.2 用事务把“查空闲-插入-提交”串起来Java 里的完整写法我一般会在 Service 层提供一个方法一个预约接口对应一个事务边界。注意 conn.setAutoCommit(false) 必须在第一条 SQL 之前调用提交成功后由 try-with-resources 统一关闭连接。public boolean reserve(ReservationDTO dto) { String checkSql SELECT COUNT(*) FROM reservation WHERE room_id ? AND status 1 AND ? end_time AND ? start_time; String insertSql INSERT INTO reservation (user_id, room_id, subject, start_time, end_time, attendee_count, status) VALUES (?, ?, ?, ?, ?, ?, 1); try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 关闭自动提交开启事务 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); try (PreparedStatement ps conn.prepareStatement(checkSql)) { ps.setInt(1, dto.getRoomId()); ps.setTimestamp(2, dto.getEndTime()); ps.setTimestamp(3, dto.getStartTime()); try (ResultSet rs ps.executeQuery()) { rs.next(); if (rs.getInt(1) 0) { conn.rollback(); return false; // 时间冲突预约失败 } } } try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, dto.getUserId()); ps.setInt(2, dto.getRoomId()); ps.setString(3, dto.getSubject()); ps.setTimestamp(4, dto.getStartTime()); ps.setTimestamp(5, dto.getEndTime()); ps.setInt(6, dto.getAttendeeCount()); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); return false; } }这个写法的核心是让检查和插入共享同一个事务。READ_COMMITTED 隔离级别下另一个事务提交的新记录在本事务后续语句里是可见的所以“先查后插”的空档被压缩到事务执行期间配合数据库行锁两个并发请求在数据库端是串行执行的。注意 setTimestamp 对应的是 DATETIME 字段要传 java.sql.Timestamp如果传 java.util.Date会在类型转换这一步直接抛 SQLException。这段代码没有用 synchronized因为单机单进程下事务加行锁已经能挡住并发插入如果想在代码层面再上一道保险可以在方法上加 synchronized但要在心里清楚它只在单机单进程内有效多个部署节点时仍然要依赖数据库锁。4.3 时间重叠判断的 SQL 条件开区间与闭区间的边界问题判断两个区间是否重叠标准条件是 newStart oldEnd AND newEnd oldStart。用 和 而不是 和 是为了把“正好衔接”的预约放行。比如已有一条 14:00-15:00 的预约新预约 15:00-16:00 应该允许因为 15:00 这个瞬间不属于任何一场如果写成 15:00 会被误判为冲突用户明明可以无缝衔接却约不上。同理新预约 13:00-14:00 也允许因为 newEnd14:00 和 oldStart14:00 相等是衔接而非交叉。这个边界在答辩时经常被老师拿来追问能把“开区间”三个字讲清楚比背十句概念管用。检查条件里我写的是 ? end_time AND ? start_time注意两个占位符的顺序第一个 ? 是新的结束时间第二个 ? 是新的开始时间千万别填反填反了交集判断就变成了差集判断重叠记录照样能插进去。除了应用层事务数据库端还可以补联合唯一索引作为兜底。不过正如第二章说的(room_id, start_time) 联合唯一只能挡住完全相同的开始时间挡不住交叉区间所以它只能防重复提交不能替代业务层的区间判断。MySQL 8.0 没有原生排他约束如果你换用 PostgreSQL可以直接声明 EXCLUDE USING gist课设里不需要为了这个换数据库明白边界在哪里就够了。5. 避坑排查篇数据库课设里最常遇到的 5 个典型故障这一章列的问题都是我在帮人排查这类系统时反复看到的现象每一条按“现象、原因、解决”来讲你可以直接对号入座。这些不是理论上的潜在风险而是实际跑起来几乎必然踩一遍的血泪经验。5.1 运行即报 ClassNotFoundException: com.mysql.cj.jdbc.Driver现象双击运行或执行 java 命令启动控制台第一行就是 java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver程序直接退出。原因驱动 jar 包没有进到运行类路径。很多同学把 mysql-connector 的 jar 包放在项目目录里但 IDE 没有把它加入构建路径。编译能过是因为代码里没有直接引用驱动类运行时 Class.forName 才暴露问题。解决在 IDE 里把 jar 包加入构建路径或者确认项目属性里能看到它如果工程用依赖管理文件检查里面是否声明了 mysql-connector-j 这个坐标。另一个隐蔽原因是驱动版本和 MySQL 版本不匹配MySQL 8.x 装的是 5.x 驱动时类名要写成 com.mysql.jdbc.Driver但 5.x 驱动连 8.x 数据库常因认证插件不兼容而失败建议直接统一到 8.x 驱动。5.2 中文乱码从建库、连接 URL 到 SQL 文件三个环节都要查现象JTable 里显示的中文是问号或者数据库里存进去的是乱码符号界面输入的中文提交后变样。原因乱码是连锁问题通常是三个环节里某一处字符集不一致。最常见的是建库时用了默认字符集latin1或者 JDBC URL 没带 characterEncodingutf8mb4或者 SQL 脚本文件本身是 GBK 编码被直接导入。解决按顺序排查。建库脚本检查 DEFAULT CHARACTER SET utf8mb4JDBC URL 补上 characterEncodingutf8mb4用命令行导入 SQL 时先执行 SET NAMES utf8mb4。还有一个容易忽略的点连接参数里的 utf8 和 utf8mb4 不是一回事MySQL 的 utf8 实际是 utf8mb3存 emoji 或生僻字会报错或截断课设里直接写 utf8mb4 最省心。5.3 程序跑一段时间后操作报 Communications link failure现象系统刚启动时一切正常放一会儿再点查询或提交控制台报 Communications link failure然后所有数据库操作都失败重启程序又好了。原因连接被服务端或网络层关闭了但 Java 端还在用同一个 Connection 对象。MySQL 有 wait_timeout 参数连接空闲超过阈值会被服务端主动断开而常见误用是写一个静态全局 Connection 供所有界面复用长期不关时间一到就集体失效。解决不要持有全局长连接。每次数据库操作都通过 DBUtil.getConnection() 获取新连接用完即关try-with-resources 保证关闭。这样即使个别连接断掉下一次操作会重新建立连接问题自然消失。如果课设里确实有频繁操作可以引入简单连接池但课程设计阶段没必要上重量级框架。5.4 时间重叠的预约没被拦住现象界面上连续提交两段重叠的预约系统都提示成功数据库里出现冲突记录。原因最常见的是检查 SQL 的时间条件写反了比如写成 newStart end_time AND newEnd start_time把交集写成了差集第二种是检查通过了但插入失败时没回滚第三种是检查与插入不在同一个事务里中间的间隙被另一个请求插入。解决检查条件固定为 newStart end_time AND newEnd start_time并和插入放在同一个事务中。如果第四章的事务代码暂时没吃透至少保证一点检查之后马上插入中间不要做任何耗时的 Java 操作否则空档期一定会被复现。真实验证时开两个客户端同时提交同一时段一个成功一个失败才算真正修好。5.5 点击查询后 Swing 界面卡死现象点击“查询预约”按钮后窗口拖不动、按钮点不了过几秒才恢复像死机一样。原因数据库查询是在事件分发线程EDT里执行的。Swing 是单线程模型所有界面事件都在 EDT 上排队你在这个线程里跑数据库 IO界面就停摆。数据量小时感觉不明显数据量稍微上来一点卡顿立刻可见。解决用 SwingWorker 把耗时操作放到后台线程。下面是一个最小模板new SwingWorkerVoid, Void() { Override protected Void doInBackground() { tableModel.loadRooms(); // 后台线程执行数据库查询 return null; } Override protected void done() { // 回到 EDT这里可以直接刷新界面不会再卡顿 roomTable.updateUI(); } }.execute();doInBackground 里不要碰任何 Swing 组件done 里不要做耗时操作这个分工是 SwingWorker 的纪律。如果你从没遇到过卡死说明你的查询数据量太小课设答辩时如果把演示数据加大到几千条这个问题几乎必然出现提前改掉不亏。6. 从“能跑”到“能答辩”视图、触发器与验收自测清单6.1 用视图把三表联查封装成一张“虚拟表”预约记录列表在界面上要显示用户名、会议室名、主题、时间、状态这些字段分散在三张表里。如果每个界面的查询都写 JOIN代码会重复出错了还要改多处。常见做法是建视图CREATE VIEW v_reservation_detail AS SELECT r.id, u.real_name AS user_name, m.room_name, r.subject, r.start_time, r.end_time, r.attendee_count, r.status FROM reservation r JOIN sys_user u ON r.user_id u.id JOIN meeting_room m ON r.room_id m.id;之后 Java 端只需要 SELECT * FROM v_reservation_detail WHERE room_name ?联表逻辑只维护一份。视图在课设里是明确的加分点因为它体现了“把复杂查询封装起来”的意识而且不需要额外写 Java 代码。6.2 触发器把冲突检查下沉到数据库内核应用层事务能解决单实例并发但把规则写在数据库里更接近“数据完整性由数据库保障”的设计思路。可以在 reservation 表上加一个 BEFORE INSERT 触发器DELIMITER // CREATE TRIGGER trg_reservation_check BEFORE INSERT ON reservation FOR EACH ROW BEGIN IF EXISTS ( SELECT 1 FROM reservation WHERE room_id NEW.room_id AND status 1 AND NEW.start_time end_time AND NEW.end_time start_time ) THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 会议室时间冲突; END IF; END// DELIMITER ;触发器会在应用层判断之外多出一道防线即使有人绕过 Java 程序直接往表里插数据冲突记录也进不来。要注意触发器里的检查同样受事务隔离级别影响所以它不是应用层事务的替代品而是兜底网。答辩时说清“应用层负责业务规则触发器负责最后防线”比只丢一个触发器出来更有说服力。6.3 答辩前自测清单把自己当成最刁钻的评审老师按下面的清单过一遍基本能覆盖常见追问测试项操作预期结果登录防护用户名输入 OR 11密码随意登录失败不产生任何异常记录时间倒置提交开始时间晚于结束时间的预约界面拒绝数据库无新记录边界衔接已约 14:00-15:00再约 15:00-16:00预约成功交叉冲突已约 14:00-15:00再约 14:30-15:30预约失败并发冲突两个客户端同时提交同一时段恰好一个成功容量超限参会人数大于会议室 capacity界面拦截并给出提示外键保护删除仍被预约引用的会议室数据库报外键错误程序捕获后提示最后一条尤其容易被忽略很多人把外键建好就不管了结果删除时程序直接抛异常退出。正确的做法是在删除操作的外层捕获 SQLIntegrityConstraintViolationException转成“该会议室已被预约请先处理相关预约”的友好提示。我给不少同学做过这类系统的代码评审印象最深的一位界面做得很漂亮但预约逻辑是“先查一次、再插一条”没有任何事务保护被追问时自己也不敢演示两个窗口同时提交。后来补上事务和触发器同一个系统在答辩里的评价完全不一样。从那以后我的习惯是写任何表结构之前先问自己三个问题——数据会不会被删、会不会被并发写、会不会查不出来。这三个问题过完课程设计的上限基本就定住了。希望这篇笔记能帮你在做这个选题时少走几个弯。本文还有配套的精品资源点击获取
返回列表