Java Swing + MySQL 图书管理系统:从三张表建模到借还书事务实践

发布时间:2026/10/9 6:05:33
Java Swing + MySQL 图书管理系统:从三张表建模到借还书事务实践 简介一份基于Java Swing与MySQL的图书管理系统完整项目适用于高校计算机课程设计及Java GUI开发练习。项目实现学生、管理员双角色登录管理员具备读者管理、书籍入库、书籍管理、借阅管理等功能学生可完成借书、还书操作业务逻辑覆盖图书借阅主要环节数据库包含6张表与9张视图代码附有注释。开发工具为Eclipse技术栈以Swing和AWT为主。资源包共184个文件包含35个Java源文件、101个class文件、3个SQL数据库脚本、3个JAR运行库以及多张PNG/JPG/GIF界面截图便于对照界面跟踪代码另附docx课程报告和说明文档可直接作为设计报告的素材。压缩包大小24.47MB已有601人学习下载。项目内容结构清晰同时提供源码、数据库脚本和配套报告适合用于课程答辩、实验参考或快速上手Java图书管理系统的二次开发。1. Java Swing 写桌面图书系统一个老组合为什么还能交付图书管理系统大概是 Java 语言里被练手次数最多的项目你去搜“javaweb项目完整案例 mysql”十个里有八个是网页端改版但真实的小型图书室、资料室和内部借阅场景往往不需要浏览器和 Web 服务器。固定一台电脑、双击启动、断网也能登记借还这是 Swing MySQL 这类 C/S 组合至今还能交付的理由。本文不会给你一份“复制粘贴就能跑”的完整源码而是把背后真正关键的部分拆开讲三张表怎么建才不乱、借书还书为什么必须走事务、Swing 窗口怎么组织才不卡死以及新手最容易翻车的几个点。适合正在做课设、毕设的在校生也适合想用一个小项目把 Java 基础、JDBC 和 MySQL 串起来应对面试的求职者。2. 选型与交付边界为什么是 Swing MySQL而不是 Spring Boot Vue2.1 Swing 还在被使用的真实场景很多人一看到 Swing 就觉得是“过时技术”但“过时”和“不适用”是两回事。Spring Boot 那套 B/S 方案意味着你要维护接口层、前端工程、跨域、部署环境甚至还要考虑域名和端口而 Swing 应用只需要一个能装 JDK 的 Windows 或者 Linux 桌面把打包好的 jar 复制过去就能跑。图书管理这种操作频率不高、并发极低的内部场景Swing 的劣势根本暴露不出来优势反而很明显离线可用、无浏览器兼容问题、界面状态本地化。我一般会这样判断如果系统是图书管理员在固定工作台上录入、盘点和处理借还桌面端完全够用如果读者需要在手机上查书目、在线预约那才应该考虑做成 Web 服务。同一个需求在不同交付形态下工作量能差一个数量级。标题里既然写的是 Swing说明它面向的是前者这也是这类课设和内部工具最常见的真实需求。2.2 数据访问层JDBC 直连还是 MyBatis数据访问层是这个项目里最容易被过度设计的地方。常见做法是引入 MyBatis 甚至 MyBatis-Plus有人还会用 MyBatis-Plus 根据 Java 实体类自动生成建表 SQL但这套东西在 Swing 桌面项目里收益很低。图书管理系统的表关系只有三个实体最大的查询也就是两表 JOIN手写 JDBC 反而能让你把 SQL、事务、连接管理都摸清楚。面试时被问到“你做过什么 MySQL 项目实战”你能讲出 PreparedStatement 参数绑定和事务回滚的具体场景比说“我用 ORM 自动生成了 SQL”可信得多。JDBC 也不是完全裸写。我通常会先抽一个 DBUtil 负责连接再把 DAO 拆成 UserDao、BookDao、BorrowDao窗口事件里只调用 DAO 方法这样排查问题时能顺着“界面层 → 数据访问层 → 数据库”一条线查下去。真正需要换 MyBatis 的时机是表超过十几张、关联查询复杂到 SQL 难维护时这个小项目远没到那个量级。2.3 环境准备JDK、MySQL 版本与第一个 JDBC 连接动手之前先把环境对齐这一步做好了后面少踩一半坑。JDK 建议 8 或 11Swing 在这两个版本下最稳定MySQL 选 5.7 或 8.x 都行但要注意驱动的差异。如果你参照网上 mysql 安装教程装的是 8.x连接驱动类名要写 com.mysql.cj.jdbc.Driver5.7 时代常用的 com.mysql.jdbc.Driver 在 8.x 驱动里只是兼容遗留类。MySQL 8.x 的 LTS 分支还要求连接参数里显式给出 serverTimezone否则驱动会直接报时区错误。组件版本建议说明JDK8 或 11Swing 内置无需额外 GUI 库MySQL5.7.x 或 8.x本地开发用 InnoDB 引擎mysql-connector-java与 MySQL 大版本匹配8.x 驱动对应 8.x 数据库IDEIDEA 或 Eclipse用于导出可执行 jar环境就绪后的第一件事不是画界面而是先验证 Java 能连上 MySQL。下面这个 DBUtil 是整套系统的连接基础我会把它写在最前面public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/library ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }这段代码里有几个参数值得解释。characterEncodingutf8 负责让中文在 Java 和 MySQL 之间正常往返省掉一堆乱码问题useSSLfalse 是因为本地开发没有证书开启 SSL 反而会握手失败allowPublicKeyRetrievaltrue 是给 MySQL 8.x 默认的 caching_sha2_password 认证用的否则首次连接会卡在公钥获取。static 块里的 Class.forName 是驱动加载8.x 驱动类名必须写全。close 方法里按 ResultSet → Statement → Connection 的反向顺序关闭这个习惯能避免数据库连接资源被耗尽。提示如果你的 MySQL 是用 Docker 容器跑起来的本地连接时还要确认端口映射和容器内字符集比直接裸装多一层排查成本。课设阶段建议本机直接装 MySQL省时间。3. 先建模再写界面图书管理系统的三张表与借还书事务3.1 功能拆解与三张表的关系图书管理系统不管界面长什么样核心功能都能拆成四块用户登录、图书信息维护、借书、还书。围绕这四块数据库只需要三张表sys_user 存登录账号book 存图书信息和库存borrow 存每一次借还记录。sys_user 和 book 之间是多对多关系borrow 表就是它们的关系表同时带上了借出时间、应还时间和状态。这里有个容易忽略的设计点borrow 表里存的是 user_id不是用户名。借书时如果只在表里冗余一个用户名等哪天管理员改了姓名历史借阅记录就会跟着错。存 id 之后查询阶段再用 JOIN 把用户名带出来数据不会漂移。图书表里还要区分 total 和 stock 两个字段total 是馆藏总量stock 是当前可借数量还书时加回的是 stock而不是 total。3.2 建表 SQL 与几个约束细节建表语句是整套系统的地基我习惯在建库时就定好字符集避免后面对着一堆问号发愁。下面是三张表的完整 DDL可以直接在 MySQL 命令行或 Navicat 里执行CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE library; CREATE TABLE sys_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 明文仅用于演示生产环境必须存哈希, role VARCHAR(16) NOT NULL DEFAULT USER ) ENGINEInnoDB; CREATE TABLE book ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(128) NOT NULL COMMENT 书名, author VARCHAR(64) NOT NULL, isbn VARCHAR(32) UNIQUE, category VARCHAR(32) DEFAULT 未分类, total INT NOT NULL DEFAULT 0, stock INT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE borrow ( id INT AUTO_INCREMENT PRIMARY KEY, book_id INT NOT NULL, user_id INT NOT NULL, borrow_time DATETIME NOT NULL, return_time DATETIME NULL, status VARCHAR(16) NOT NULL DEFAULT BORROWED, FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (user_id) REFERENCES sys_user(id) ) ENGINEInnoDB;三个地方需要特别说明。第一字符集用 utf8mb4 而不是 utf8因为 utf8mb4 才能存下生僻字和部分特殊符号第二所有表指定 ENGINEInnoDB这是 MySQL 事务生效的前提换成 MyISAM 的话后面的借还事务全部白搭第三borrow 表的外键指向 book 和 sys_user这样不会出现借书记录指向一张不存在的图书的脏数据。MySQL 8.0.16 之前 CHECK 约束只是解析不校验所以库存是否充足这种业务规则要在 Java 代码里判断不能指望数据库兜底。3.3 列表查询与排序把 MySQL 的排序规则用对图书列表是主界面用得最多的功能通常支持按书名、作者或 ISBN 模糊搜索。SQL 写起来不复杂但有两个细节值得注意一是 LIKE 匹配时搜索词里的 % 和 _ 是通配符如果用户真的输入了一个百分号查询结果会变得不可控需要显式转义二是 ORDER BY 的稳定性同一秒入库的图书如果没有次级排序翻页或刷新时顺序可能跳来跳去。SELECT id, title, author, isbn, category, total, stock FROM book WHERE title LIKE ? OR author LIKE ? OR isbn LIKE ? ORDER BY create_time DESC, id ASC;参数里的三个问号用 PreparedStatement 的 setString 填充关键字本身和 SQL 语句分离既避免了注入也天然解决了通配符问题。ORDER BY 先按 create_time 倒序再按 id 升序意思是入库时间相同的记录用自增主键兜底保证排序结果确定。MySQL 排序默认是字典序中文按 Unicode 编码排这对图书列表完全够用。3.4 借书还书的原子性MySQL 事务不是可选项借书这个动作表面上是“点一下按钮”背后其实是三个数据库操作检查库存是否大于 0、把 book 表的 stock 减一、向 borrow 表插入一条借阅记录。这三个操作必须同时成功或者同时失败否则就会出现库存减了但借阅记录没写上或者记录写上了但库存没减的脏状态。保证这种一致性的机制就是 MySQL 事务。下面这个方法是借书的核心逻辑我把事务边界放在 Service 层而不是 DAO 层因为一次借书要操作两张表跨 DAO 的原子性必须由同一个连接来保证public void borrow(Connection conn, int userId, int bookId) throws SQLException { String deduct UPDATE book SET stock stock - 1 WHERE id ? AND stock 0; String insert INSERT INTO borrow(book_id, user_id, borrow_time, status) VALUES (?, ?, NOW(), BORROWED); conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(deduct); PreparedStatement ps2 conn.prepareStatement(insert)) { ps1.setInt(1, bookId); int rows ps1.executeUpdate(); if (rows 0) { throw new SQLException(库存不足无法借出); } ps2.setInt(1, bookId); ps2.setInt(2, userId); ps2.executeUpdate(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } }这段代码里的关键点是那条 UPDATE 语句带了 AND stock 0 条件这一步在数据库层面拦截了并发下的超借问题。executeUpdate 返回的是受影响行数如果库存已经为 0这个条件不成立更新 0 行代码就抛异常并进入 rollbackborrow 插入不会执行。两个操作被包在同一个事务里任何一步失败都整体回滚这是 Av 系统里最值得记住的写法。还书操作是借书的镜像先更新 borrow 表的 status 为 RETURNED 并写入 return_time再对 book 表执行 stock stock 1。同样需要事务同样要在同一个 Connection 里完成。MySQL 默认的事务隔离级别是 REPEATABLE READ对这个场景已经够用不需要改也别为了“更安全”升级成 SERIALIZABLE那会让并发借还互相排队。4. 界面落地从登录窗口到图书列表Swing JDBC 的最小实现4.1 先分三层再写第一行界面Swing 项目最容易写成一坨按钮事件里直接拼 SQL、建连接、执行查询、往表格里塞数据。这样的小程序跑起来没问题但一旦要加功能就会变成黑匣子改一行代码连带几个地方报错。我通常把代码分成三层窗口层只负责收集输入和展示结果事件层把用户动作转成业务调用DAO 层只写 SQL。窗口层不知道 SQL 长什么样DAO 层不知道界面长什么样各层的修改互不牵连。这个项目里窗口层有 LoginFrame 和 MainFrameMainFrame 里用 JTabbedPane 放图书管理、借还服务、超期提醒几个页签。DAO 层有 UserDao、BookDao、BorrowDaoDBUtil 统一管理连接。事件监听器写成一个内部类或者 lambda调用 Service 方法后刷新界面。分层之后后面加一个“导出借阅记录”的功能只需要在 DAO 里加一个方法再在窗口里加一个按钮代码改动范围是收敛的。4.2 登录窗口校验下放到 DAO界面只做收集和提示登录窗口是系统的入口也是最容易留下安全漏洞的地方。我的做法是窗口层只负责拿输入框的内容密码校验全部下放到 UserDao 里。登录失败统一弹 JOptionPane 提示不告诉用户是“用户名不存在”还是“密码错误”避免被用来探测账号。下面是一个简化但完整的登录窗口骨架public class LoginFrame extends JFrame { private JTextField usernameField new JTextField(16); private JPasswordField passwordField new JPasswordField(16); private JButton loginButton new JButton(登录); private UserDao userDao new UserDao(); public LoginFrame() { setTitle(图书管理系统登录); setSize(320, 180); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); setLayout(new BorderLayout(6, 6)); JPanel form new JPanel(new GridLayout(2, 2, 6, 6)); form.add(new JLabel(用户名)); form.add(usernameField); form.add(new JLabel(密码)); form.add(passwordField); add(form, BorderLayout.CENTER); JPanel buttons new JPanel(); buttons.add(loginButton); add(buttons, BorderLayout.SOUTH); loginButton.addActionListener(e - doLogin()); } private void doLogin() { String username usernameField.getText().trim(); String password new String(passwordField.getPassword()); User user userDao.findUser(username, password); if (user null) { JOptionPane.showMessageDialog(this, 用户名或密码错误, 登录失败, JOptionPane.ERROR_MESSAGE); return; } new MainFrame(user).setVisible(true); dispose(); } }这里有个细节新手很容易忽略JPasswordField.getPassword() 返回的是 char[] 而不是 String。Swing 官方建议用字符数组保存密码输入是因为 String 一旦创建就留在内存里无法主动清空这里 new String 转出来只是为了传给 DAO 比较用完即弃。UserDao.findUser 内部用 PreparedStatement 参数化查询用户名和密码作为占位符参数传入而不是拼进 SQL 字符串这是登录功能最基本的安全底线。4.3 图书列表JTable 的模型绑定与刷新图书列表是主界面的核心控件Swing 里 JTable 本身只负责展示数据放在 TableModel 里。最常见的错误是把 TableModel 存在成员变量里反复改结果刷新时界面不更新或者列错位。我每次刷新都直接 new 一个新的 DefaultTableModel 并调用 setModel让 JTable 自己感知模型变化逻辑最干净public class BookTablePanel extends JPanel { private JTable table new JTable(new DefaultTableModel()); private JTextField keywordField new JTextField(16); private JButton searchButton new JButton(查询); private BookDao bookDao new BookDao(); public BookTablePanel() { setLayout(new BorderLayout(6, 6)); table.getTableHeader().setReorderingAllowed(false); JPanel bar new JPanel(); bar.add(new JLabel(关键字)); bar.add(keywordField); bar.add(searchButton); add(bar, BorderLayout.NORTH); add(new JScrollPane(table), BorderLayout.CENTER); searchButton.addActionListener(e - reloadData()); reloadData(); } private void reloadData() { String keyword keywordField.getText().trim(); ListBook books bookDao.findBooksByKeyword(keyword); String[] columns {ID, 书名, 作者, ISBN, 馆藏, 可借}; Object[][] rows new Object[books.size()][columns.length]; for (int i 0; i books.size(); i) { Book b books.get(i); rows[i] new Object[]{b.getId(), b.getTitle(), b.getAuthor(), b.getIsbn(), b.getTotal(), b.getStock()}; } table.setModel(new DefaultTableModel(rows, columns) { Override public boolean isCellEditable(int row, int column) { return false; } }); } }这段代码解决了两个常见问题。一个是只读表格JTable 默认允许双击单元格编辑所以重写 isCellEditable 返回 false避免管理员误改表格里的展示数据另一个是刷新时机每次查询都重建 TableModel列结构和数据一起更新不会出现“旧列还在、数据变空”的怪现象。图书列表固定放在 JScrollPane 里是为了让表头和滚动条正常工作直接 add(table) 到顶层容器是常见的错误用法。4.4 借书操作取选中行、走事务、刷新列表借书按钮的事件处理比登录复杂一些因为要先从 JTable 拿到用户选中的是哪一本。这里有一个索引陷阱如果 JTable 开启了排序用户看到的行号和 TableModel 里的行号不是一回事。正确做法是先用 getSelectedRow 拿到视图行号再用 convertRowIndexToModel 转换成模型行号再取值int viewRow table.getSelectedRow(); if (viewRow 0) { JOptionPane.showMessageDialog(this, 请先选择要借的图书); return; } int modelRow table.convertRowIndexToModel(viewRow); int bookId (int) table.getModel().getValueAt(modelRow, 0); Connection conn null; try { conn DBUtil.getConnection(); borrowService.borrow(conn, currentUserId, bookId); JOptionPane.showMessageDialog(this, 借书成功); reloadData(); } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 借书失败 ex.getMessage()); } finally { DBUtil.close(conn, null, null); }getValueAt(modelRow, 0) 拿的是表格第一列即 ID 字段因为 BookTablePanel 在构建列结构时把 ID 放在第 0 列所以这里类型转换是安全的。事务真正执行在 BorrowService.borrow 里窗口层的职责只是拿到 bookId、捕获异常、刷新列表。借书成功后调一次 reloadData让库存数字立刻变化否则用户看到的是旧数据会误以为操作没生效。还书按钮的写法几乎一样差别只在于调用的 Service 方法不同。5. 避坑指南Swing 界面卡死、中文乱码与数据错乱的 5 个排查记录5.1 点击按钮后窗口白屏几秒Swing 的 EDT 被堵住了现象点击“查询”按钮后整个窗口变白鼠标变成等待状态好几秒后才恢复。原因按钮的 actionPerformed 是在 Swing 的事件分发线程EDT上执行的这个线程同时负责重绘界面。如果你在事件里同步执行了数据库查询EDT 就被查询阻塞界面重绘被迫排队看起来就是卡死。解决耗时查询放进 SwingWorker 的 doInBackground 里跑查询结果通过 done 方法回到 EDT 更新界面。常见做法是给查询按钮单独封装一个 SwingWorker搜索时先禁用按钮防重复点击再用状态标签提示加载中。这个小改动能让界面体验从“像死机”变成“有响应”是桌面应用和 Web 应用差异最大的地方。5.2 驱动类找不到ClassNotFoundException 与 jar 依赖缺失现象程序一启动就抛 java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或者运行到第一次查库时才报。原因mysql-connector-java 的 jar 没有打进 classpath或者驱动类名写错。新版 8.x 驱动里旧类名 com.mysql.jdbc.Driver 虽然还兼容但排查问题时优先切成新类名试一次。解决确认 IDEA 的 Project Structure 里 Libraries 包含了驱动 jar如果打包成可执行 jar还要把驱动 jar 一并打进 lib 目录光把 classes 打进去是不够的。这个坑的特点是 IDE 里跑得好好的换到命令行或双击 jar 就出错本质是打包时漏了依赖。5.3 中文乱码连接参数和数据库字符集的“双重锅”现象数据库里存进去的书名显示成问号或者 Swing 界面上文字变成方块。原因有两层JDBC 连接参数没带 characterEncodingJava 和 MySQL 之间默认用了不同编码或者建库时没指定 utf8mb4数据库用了默认的 latin1。解决连接 URL 里加 useUnicodetruecharacterEncodingutf8同时回头检查库和表的字符集用 SHOW VARIABLES LIKE character_set%; 确认结果不是 latin1。界面字体问题单独处理Windows 下给 JLabel 和 JTable 设置 Font(微软雅黑, Font.PLAIN, 14)生僻字就不会变成方块。乱码问题经常是玄学排查时先拿一个已知中文串直接在 MySQL 命令行插入命令行正常而程序不正常就锁定是连接参数的问题。5.4 库存越借越负事务没提交或提交时机不对现象连续借出同一本书明明只有 3 本库存借到第 4 次还提示成功列表里 stock 变成负数。原因有两个借书时没有用 UPDATE ... WHERE stock 0 做条件拦截或者每个 DAO 方法都自己拿了连接各自提交减库存和插记录没在同一个事务里。解决回看第 3.4 节的写法把减库存和插借阅记录放进同一个 Connection 的同一个事务提交前任何一步失败都 rollback。这里会牵扯到 MySQL 锁的分类InnoDB 的 UPDATE 默认加的是行锁只会锁住这一本书的记录不会锁全表所以你放心用事务不会拖慢其他书的借还。5.5 表格刷新后数据错位排序状态把行号带偏了现象点击表格表头按书名排序后再选中某一行点击“借书”弹出的提示却是另一本书。原因JTable 的 getSelectedRow 返回的是排序后的视图行号TableModel 里的行号是物理行号两者在排序后不一致。解决取值前先调用 table.convertRowIndexToModel(viewRow)把视图行号转换成模型行号再用转换后的索引去 getValueAt。这是 JTable 最典型的血泪经验没碰过排序功能的人不会踩到一旦踩到又很难想明白为什么取到的数据不对。建议所有从 JTable 取行数据的代码都统一走这个转换不管当前有没有开排序习惯养成了就不会翻车。6. 进阶与验收超期提醒、打包发布与一段验收脚本6.1 超期提醒用一个 SQL 算出 overdue 列表借书功能跑通之后下一步通常是为管理员加一个超期提醒。借阅规则一般是 30 天所以查询条件就是 status 为 BORROWED、并且借出时间加上 30 天已经早于当前时间。SQL 如下SELECT u.username, b.title, br.borrow_time, DATE_ADD(br.borrow_time, INTERVAL 30 DAY) AS due_time FROM borrow br JOIN sys_user u ON br.user_id u.id JOIN book b ON br.book_id b.id WHERE br.status BORROWED AND DATE_ADD(br.borrow_time, INTERVAL 30 DAY) NOW() ORDER BY due_time ASC;这段 SQL 用 DATE_ADD 在查询时计算应还时间而不是在 Java 里算好再拼进 SQL保证时区逻辑统一由数据库处理。ORDER BY due_time 让最紧急的超期记录排在表格最上面管理员一眼看到该催谁。6.2 打包发布与验收脚本让系统能脱离 IDE 运行Swing 项目的交付物通常是一个可执行 jar。打包时要注意三件事MANIFEST 里指定 Main-Class把 mysql-connector-java 打进依赖目录目标机器装同版本或更高版本的 JRE。用 IDEA 的 Artifact 导出是最省事的方式导出后在一台没有装 MySQL 客户端的机器上先测连接确认驱动 jar 被正确打进去。发布前我会跑一段 MySQL 命令行验收脚本模拟从登录到借还的完整链路mysql -uroot -p library SQL -- 断言管理员账号存在 SELECT COUNT(*) FROM sys_user WHERE usernameadmin; -- 准备把测试书库存设为 3 UPDATE book SET stock 3 WHERE id 1001; -- 模拟借书成功库存从 3 减到 2 START TRANSACTION; UPDATE book SET stock stock - 1 WHERE id 1001 AND stock 0; INSERT INTO borrow(book_id, user_id, borrow_time, status) VALUES (1001, 1, NOW(), BORROWED); COMMIT; -- 断言库存必须为 2 SELECT stock FROM book WHERE id 1001; SQL这段脚本把数据库层的核心行为做成了可重复执行的断言比在界面里手动点十次按钮高效得多。我在做完这个项目后养成的习惯是先写数据库脚本再写 DAO最后才画窗口。这个顺序让每个环节都能独立验证窗口层出问题时不至于怀疑是 SQL 写错了。如果你是从“先拖界面再写数据”开始做的也来得及回头把三层拆开拆完之后你会发现改功能不再牵一发动全身。希望这份笔记能帮你把这个经典课设做成一个真正拿得出手的项目。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询