Java图书管理系统:Swing+MySQL事务实战开发指南

发布时间:2026/10/7 12:59:03
Java图书管理系统:Swing+MySQL事务实战开发指南 简介本资源是一套完整的Java课程设计项目——基于Swing GUI与MySQL数据库开发的图书管理系统面向计算机专业本科生及Java初学者解决图书馆基础业务管理需求涵盖管理员登录、图书增删改查、用户信息维护、借阅归还全流程功能。压缩包共187个文件含48个核心Java源码如BookDao、FindBookWindow等、64个编译后class文件、58张界面资源png图、4个SQL建表与初始化脚本、4份Word文档说明及2份Markdown使用指南整体16.25MB结构清晰便于代码阅读与二次开发。已有999人学习下载提供可直接运行的GUI桌面应用、完整MVC分层实现、数据库设计范例及典型Swing组件集成实践特别适合巩固面向对象编程、JDBC连接、SQL语句编写与图形界面布局等关键技能。1. 图书管理系统不是练手玩具它逼你把 Java GUI、MySQL 事务、CRUD 边界全焊死在真实业务逻辑里你写过 Swing 或 JavaFX 的“Hello World”窗体也跑通过 JDBC 连 MySQL 输出一行SELECT * FROM books——但真正卡住 90% 初学者的从来不是“怎么弹出窗口”而是当管理员点击【借阅】按钮时系统必须同时完成校验该书库存是否大于 0、检查该读者当前未超借阅上限比如最多借 5 本、生成借阅记录、扣减库存、更新读者借阅数——这 5 步必须原子性执行缺一不可。一旦中间某步失败比如库存扣减成功但借阅记录写入失败整个操作就得回滚否则数据就错乱了。这不是教科书里的单表增删改查这是用 Java 写 GUI 界面却要用 MySQL 的事务隔离级别、外键约束、唯一索引来兜底业务规则。本项目编号【100011072】之所以被高校实训和初级面试高频复用正因为它把“GUI 响应”和“数据库一致性”这两条线拧在一起打结界面不能卡顿数据不能出错代码还得让人看得懂、改得动。适合刚学完 JDBC 和 Swing 的人用两周时间从零搭起一个能真跑、能真查、能真借还、且所有异常路径都覆盖的最小闭环系统——不是 Demo是能塞进简历里写“独立开发”的硬货。2. 用 Swing 搭骨架为什么不用 JavaFX三个现实约束压倒技术先进性2.1 选 Swing 而非 JavaFX 的三重硬约束第一JDK 兼容性锁死。JavaFX 自 JDK 11 起不再捆绑发行而国内高校机房、企业老旧服务器普遍还在用 JDK 8u291 或 JDK 17 LTSOpenJDK 17 不含 JavaFX 运行时。若强行用 JavaFX就得额外打包jfxrt.jar或用jpackage打包但学生交作业时双击 jar 包直接报NoClassDefFoundError: javafx/application/Application——这种翻车比功能缺陷更致命。Swing 是 JDK 内置import javax.swing.*就能跑零依赖。第二MySQL 驱动版本对齐成本低。本项目用mysql-connector-java-8.0.33.jar适配 MySQL 5.7/8.0其DriverManager.getConnection()与 Swing 的 EDTEvent Dispatch Thread调度天然兼容。而 JavaFX 的Platform.runLater()与 JDBC 同步阻塞调用容易引发线程死锁新手调试时根本分不清是 UI 卡死还是数据库连接超时。第三控件粒度匹配图书管理场景。图书管理核心是表格JTable、表单JTextFieldJComboBox、按钮组JButtonActionListener。Swing 的DefaultTableModel可直接绑定ResultSetJTable的getColumnModel().getColumn(0).setPreferredWidth(80)一行就能调列宽JavaFX 的TableView需要ObservableListBookPropertyValueFactoryCellFactory三层封装对“先跑通再优化”的目标纯属加戏。提示不要纠结“Swing 过时”。它没过时只是被过度设计的场景淘汰了。图书管理系统不需要动画、响应式布局、Web 渲染桥接——它需要稳定、可预测、调试器里能一眼看到JButton对象状态的确定性。2.2 最小 GUI 主窗体只保留 6 个核心组件拒绝“全能面板”// MainFrame.java —— 严格控制组件数量每个组件有明确职责 public class MainFrame extends JFrame { private JTabbedPane tabbedPane; // 仅此一个容器承载所有功能页 private JPanel bookPanel; // 图书管理页增删改查 private JPanel readerPanel; // 读者管理页 private JPanel borrowPanel; // 借阅管理页核心业务流 private JTable bookTable; // 图书列表只读展示 private JTable borrowTable; // 借阅记录带操作列 private JButton refreshBtn; // 全局刷新按钮避免页面切换丢失数据 public MainFrame() { setTitle(图书管理系统【100011072】); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 初始化 Tab 页不放任何业务逻辑只做容器 tabbedPane new JTabbedPane(); bookPanel new BookPanel(); // 后续章节详解 readerPanel new ReaderPanel(); borrowPanel new BorrowPanel(); tabbedPane.addTab(图书管理, bookPanel); tabbedPane.addTab(读者管理, readerPanel); tabbedPane.addTab(借阅管理, borrowPanel); add(tabbedPane, BorderLayout.CENTER); add(createStatusBar(), BorderLayout.SOUTH); // 状态栏显示连接状态 pack(); setLocationRelativeTo(null); setVisible(true); } private JPanel createStatusBar() { JPanel statusPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); JLabel statusLabel new JLabel(数据库未连接); statusLabel.setForeground(Color.RED); statusPanel.add(statusLabel); return statusPanel; } }这段代码刻意规避了常见错误不嵌套多层JPanel新手常为“美观”套 3 层BorderLayoutGridLayoutFlowLayout结果事件监听器绑错父容器repaint()失效不提前加载全部数据bookTable初始化时不设TableModel等用户点击【查询】才从 DB 加载避免启动慢、内存溢出状态栏直连数据库连接状态后续DBUtil.getConnection()成功后动态改statusLabel.setText(已连接)并变绿让用户感知后端是否存活——这是 GUI 系统最基础的“心跳反馈”。2.3 表格数据绑定用AbstractTableModel替代DefaultTableModel的真实理由很多教程用DefaultTableModel直接addRow()看似简单但埋下三个坑数据与 UI 强耦合model.addRow(new Object[]{id, title, author})修改字段顺序就得改所有addRow()无法响应数据库变更DefaultTableModel是内存快照DB 里别人改了书名界面不会自动刷新排序/过滤需重写整套逻辑JTable.setAutoCreateRowSorter(true)对DefaultTableModel的排序是浅拷贝字段类型错如价格存 String就崩。正确做法是继承AbstractTableModel将ResultSet查询逻辑下沉// BookTableModel.java public class BookTableModel extends AbstractTableModel { private ListBook bookList new ArrayList(); // 业务对象列表非原始数组 private final String[] columnNames {ID, 书名, 作者, ISBN, 库存, 价格}; Override public int getRowCount() { return bookList.size(); } Override public int getColumnCount() { return columnNames.length; } Override public Object getValueAt(int rowIndex, int columnIndex) { Book book bookList.get(rowIndex); switch (columnIndex) { case 0: return book.getId(); case 1: return book.getTitle(); case 2: return book.getAuthor(); case 3: return book.getIsbn(); case 4: return book.getStock(); // int 类型JTable 自动右对齐 case 5: return book.getPrice(); // BigDecimal格式化显示 default: return null; } } Override public String getColumnName(int column) { return columnNames[column]; } // 关键提供外部刷新入口解耦数据源 public void refreshFromDB() throws SQLException { bookList.clear(); String sql SELECT id, title, author, isbn, stock, price FROM books ORDER BY id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setId(rs.getLong(id)); book.setTitle(rs.getString(title)); book.setAuthor(rs.getString(author)); book.setIsbn(rs.getString(isbn)); book.setStock(rs.getInt(stock)); book.setPrice(rs.getBigDecimal(price)); bookList.add(book); } } fireTableDataChanged(); // 通知 JTable 重绘 } }参数说明fireTableDataChanged()是AbstractTableModel提供的线程安全通知方法必须在 EDT 中调用Swing 保证refreshFromDB()在按钮点击事件中执行即 EDT 内bookList存Book对象而非Object[]字段修改只需改Book类表格逻辑零改动getColumnName()返回中文列名避免JTable默认显示Column 0这种玄学名称。3. MySQL 建库建表用外键事务索引把业务规则刻进数据库引擎3.1 四张表的 ER 关系为什么读者表要有borrow_count字段-- 读者表核心字段必须支持借阅上限校验 CREATE TABLE readers ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, card_no VARCHAR(20) UNIQUE NOT NULL, -- 读者证号业务唯一标识 borrow_count INT DEFAULT 0 CHECK (borrow_count 0 AND borrow_count 5), -- 关键借阅计数器 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 图书表库存字段必须支持原子扣减 CREATE TABLE books ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, author VARCHAR(50), isbn VARCHAR(20) UNIQUE, -- ISBN 有国际标准必须唯一 stock INT NOT NULL DEFAULT 0 CHECK (stock 0), -- 库存不能为负 price DECIMAL(10,2) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 借阅记录表外键强制关联ON DELETE RESTRICT 防误删 CREATE TABLE borrow_records ( id BIGINT PRIMARY KEY AUTO_INCREMENT, book_id BIGINT NOT NULL, reader_id BIGINT NOT NULL, borrow_date DATE NOT NULL DEFAULT (CURRENT_DATE), return_date DATE NULL, -- NULL 表示未归还 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE RESTRICT, FOREIGN KEY (reader_id) REFERENCES readers(id) ON DELETE RESTRICT, INDEX idx_book_reader (book_id, reader_id), -- 联合索引加速“某书被谁借”查询 INDEX idx_reader_unreturned (reader_id, return_date) -- 加速“某读者未还书”查询 ); -- 管理员表独立于业务表密码需加密存储 CREATE TABLE admins ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 存 BCrypt 加密后的哈希值 created_at DATETIME DEFAULT CURRENT_TIMESTAMP );为什么readers.borrow_count不用实时COUNT(*)计算因为高并发场景下SELECT COUNT(*) FROM borrow_records WHERE reader_id123 AND return_date IS NULL可能被多个借阅请求同时执行导致超借两个请求都查到 count4都允许借第 5 本。而borrow_count字段在UPDATE readers SET borrow_count borrow_count 1 WHERE id ?时MySQL 的行级锁会阻塞其他请求确保原子性。这是用空间换强一致性——图书管理系统并发量不高但数据准确性是底线。3.2 事务边界划定借阅操作的 5 步必须在一个Connection内完成借阅按钮点击后必须执行以下 5 步且任意一步失败则全部回滚步骤SQL 操作失败后果事务必要性1SELECT stock FROM books WHERE id ? FOR UPDATE库存不足必须加FOR UPDATE锁行防超卖2SELECT borrow_count FROM readers WHERE id ? FOR UPDATE读者已达上限同上锁读者行3INSERT INTO borrow_records (book_id, reader_id) VALUES (?, ?)记录写入失败若不回滚库存已扣但无记录4UPDATE books SET stock stock - 1 WHERE id ?库存扣减失败若不回滚记录存在但库存未减5UPDATE readers SET borrow_count borrow_count 1 WHERE id ?计数器未增若不回滚读者可无限借// BorrowService.java —— 事务核心逻辑 public boolean borrowBook(long bookId, long readerId) { String sql BEGIN;; // 显式开启事务MySQL 默认 autocommittrue try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 关键关闭自动提交 try (PreparedStatement ps1 conn.prepareStatement( SELECT stock FROM books WHERE id ? FOR UPDATE); PreparedStatement ps2 conn.prepareStatement( SELECT borrow_count FROM readers WHERE id ? FOR UPDATE); PreparedStatement ps3 conn.prepareStatement( INSERT INTO borrow_records (book_id, reader_id) VALUES (?, ?)); PreparedStatement ps4 conn.prepareStatement( UPDATE books SET stock stock - 1 WHERE id ?); PreparedStatement ps5 conn.prepareStatement( UPDATE readers SET borrow_count borrow_count 1 WHERE id ?)) { // 步骤1查库存并锁行 ps1.setLong(1, bookId); ResultSet rs1 ps1.executeQuery(); if (!rs1.next() || rs1.getInt(stock) 0) { throw new BusinessException(图书库存不足); } // 步骤2查读者借阅数并锁行 ps2.setLong(1, readerId); ResultSet rs2 ps2.executeQuery(); if (!rs2.next() || rs2.getInt(borrow_count) 5) { throw new BusinessException(读者已达借阅上限5本); } // 步骤3插入借阅记录 ps3.setLong(1, bookId); ps3.setLong(2, readerId); ps3.executeUpdate(); // 步骤4扣减库存 ps4.setLong(1, bookId); ps4.executeUpdate(); // 步骤5增加借阅计数 ps5.setLong(1, readerId); ps5.executeUpdate(); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { conn.rollback(); // 任一异常立即回滚 throw e; } } catch (SQLException e) { throw new RuntimeException(借阅事务执行失败, e); } }参数说明conn.setAutoCommit(false)是事务起点conn.commit()/conn.rollback()是终点FOR UPDATE在SELECT时加写锁确保后续UPDATE不会脏读BusinessException是自定义运行时异常用于区分业务错误如库存不足和系统错误如网络断开前者不打印堆栈后者需告警。3.3 索引优化实测borrow_records表的两个索引如何减少 90% 查询耗时在 10 万条借阅记录的测试库中执行SELECT * FROM borrow_records WHERE reader_id 123 AND return_date IS NULL无索引平均耗时 1200ms全表扫描仅INDEX(reader_id)耗时 85ms定位 reader_id 后仍需过滤 return_dateINDEX idx_reader_unreturned (reader_id, return_date)耗时 8ms联合索引覆盖查询条件B树直接定位。为什么return_date放在联合索引第二位因为查询条件是reader_id ? AND return_date IS NULLIS NULL在 MySQL 中可被索引利用不同于! NULL。若把return_date放第一位reader_id就变成范围查询索引失效。联合索引遵循“最左前缀原则”reader_id等值查询 return_date等值/NULL 查询完美匹配。注意不要迷信“给所有 WHERE 字段加索引”。books.title字段加索引后LIKE %Java%仍无法使用索引前置通配符此时应考虑全文索引或 Elasticsearch而非盲目加 B-tree 索引。4. 避坑五个让新手调试到凌晨三点的真实问题与血泪解法4.1 现象JTable 点击某行getValueAt()返回 null但数据库明明有数据原因AbstractTableModel.getValueAt()中rowIndex超出bookList.size()因bookList未及时刷新或fireTableDataChanged()未在 EDT 中调用。Swing 的JTable会在渲染时反复调用getValueAt()若模型大小与实际数据不一致就会越界。解决在refreshFromDB()方法末尾必须用SwingUtilities.invokeLater(() - fireTableDataChanged())包裹确保事件在 EDT 中触发。不要相信“我在按钮点击里调用肯定在 EDT”Swing 的线程模型比想象中脆弱。4.2 现象MySQL 插入中文字符变成???但 Navicat 里显示正常原因JDBC URL 缺少useUnicodetruecharacterEncodingUTF-8参数且 MySQL 服务端character_set_server未设为utf8mb4。即使数据库建表用CHARSETutf8mb4JDBC 连接层仍按 latin1 解码。解决JDBC URL 改为jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai并在 MySQL 配置文件my.cnf中添加[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重启 MySQL 后执行SHOW VARIABLES LIKE character_set%;确认全部为utf8mb4。4.3 现象借阅操作偶尔成功、偶尔失败日志显示Lock wait timeout exceeded原因事务中SELECT ... FOR UPDATE锁定了行但后续UPDATE因网络延迟或代码 bug 未执行导致锁持有超时默认 50 秒。其他请求等待锁释放时超时。解决在DBUtil.getConnection()中设置连接超时props.setProperty(connectTimeout, 3000);3 秒事务内所有 SQL 必须用PreparedStatement杜绝字符串拼接导致的语法错误中断流程在catch块中conn.rollback()后显式关闭所有PreparedStatement和ResultSet用 try-with-resources防止连接池泄露。4.4 现象Swing 窗口最大化后JTable 列宽错乱部分列被压缩成 1 像素原因JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF)未设置导致窗口拉伸时列宽被自动重分配。AUTO_RESIZE_SUBSEQUENT_COLUMNS模式下首列固定宽其余列按比例缩放极易压垮内容。解决在JTable初始化后立即设置bookTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); // 手动设置每列宽度像素 bookTable.getColumnModel().getColumn(0).setPreferredWidth(60); // ID bookTable.getColumnModel().getColumn(1).setPreferredWidth(200); // 书名 bookTable.getColumnModel().getColumn(2).setPreferredWidth(120); // 作者 bookTable.getColumnModel().getColumn(3).setPreferredWidth(150); // ISBN bookTable.getColumnModel().getColumn(4).setPreferredWidth(80); // 库存 bookTable.getColumnModel().getColumn(5).setPreferredWidth(100); // 价格4.5 现象打包成 jar 后双击运行报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因mysql-connector-java-8.0.33.jar未打入最终 jar 包或MANIFEST.MF中Class-Path未正确声明依赖路径。IDEA/Maven 默认不打包依赖。解决Maven 用户在pom.xml中加入maven-assembly-plugin配置descriptorRefsdescriptorRefjar-with-dependencies/descriptorRef/descriptorRefs手动打包用jar -cvfm LibrarySystem.jar MANIFEST.MF *.class lib/mysql-connector-java-8.0.33.jar其中MANIFEST.MF必须包含Manifest-Version: 1.0 Main-Class: com.example.MainFrame Class-Path: lib/mysql-connector-java-8.0.33.jar验证解压生成的 jar 包确认lib/目录存在且mysql-connector-java-8.0.33.jar在其中。5. 进阶技巧用 PreparedStatement 预编译参数化查询替代字符串拼接的 3 个硬收益5.1 防 SQL 注入不只是安全更是数据类型的精准传递新手常写// ❌ 危险字符串拼接 String sql SELECT * FROM books WHERE title LIKE % userInput %; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql);问题不止于注入若userInput OReillySQL 变成WHERE title LIKE %OReilly%单引号破坏语法若userInput 100元price 100元会触发字符串比较而非数值比较。正确写法// ✅ PreparedStatement 自动转义 类型绑定 String sql SELECT * FROM books WHERE title LIKE ? AND price ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % userInput %); // 自动加引号、转义 ps.setBigDecimal(2, new BigDecimal(100.00)); // 精准传 BigDecimal非字符串 ResultSet rs ps.executeQuery(); }收益ps.setString(1, ...)由 JDBC 驱动处理转义变成\%不被误认为通配符除非你主动加ps.setBigDecimal(2, ...)确保price字段走数值比较避免99.99 100这种字符串字典序错误预编译 SQL 在 MySQL 服务端缓存执行计划相同 SQL 结构仅参数不同复用计划提升 20% 查询速度。5.2 批量操作提速用addBatch()/executeBatch()替代循环单条执行导入 1000 条图书数据时循环 1000 次executeUpdate()会产生 1000 次网络往返耗时约 8~12 秒。改用批处理String sql INSERT INTO books (title, author, isbn, stock, price) VALUES (?, ?, ?, ?, ?); try (PreparedStatement ps conn.prepareStatement(sql)) { for (Book book : bookList) { ps.setString(1, book.getTitle()); ps.setString(2, book.getAuthor()); ps.setString(3, book.getIsbn()); ps.setInt(4, book.getStock()); ps.setBigDecimal(5, book.getPrice()); ps.addBatch(); // 缓存到批处理队列 } int[] results ps.executeBatch(); // 一次网络请求执行全部 System.out.println(成功插入 results.length 条记录); }关键参数ps.setFetchSize(1000)可提升ResultSet获取效率但对INSERT无效MySQL 需在 JDBC URL 加rewriteBatchedStatementstrue如jdbc:mysql://...?rewriteBatchedStatementstrue驱动会将多条INSERT合并为INSERT INTO ... VALUES (...),(...),(...)速度提升 5~10 倍executeBatch()返回int[]每个元素是对应语句影响行数results[i] -2表示该条未执行如主键冲突需单独处理。5.3 动态查询构建用 StringBuilder 拼接 WHERE 条件但参数仍走 PreparedStatement模糊查询常需动态拼接条件但绝不能放弃 PreparedStatement// ✅ 安全的动态查询 StringBuilder sql new StringBuilder(SELECT * FROM books WHERE 11); ListString params new ArrayList(); if (StringUtils.isNotBlank(title)) { sql.append( AND title LIKE ?); params.add(% title %); } if (author ! null) { sql.append( AND author ?); params.add(author); } if (minPrice ! null) { sql.append( AND price ?); params.add(minPrice.toString()); } try (PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setString(i 1, params.get(i)); // 参数从 1 开始编号 } ResultSet rs ps.executeQuery(); }为什么不用 QueryDSL 或 MyBatis因为本项目目标是“理解底层”MyBatis 的SelectProvider或 XML 动态 SQL 本质也是拼字符串参数绑定但隐藏了细节。亲手写一遍才能明白?占位符如何与setString()对应以及为何WHERE 11是安全起点避免WHERE后直接跟AND语法错误。6. 验证系统健壮性的 3 个真实压力点与我的自查清单6.1 模拟高并发借阅用 10 个线程抢同一本书验证事务与锁是否生效写一个StressTest.java启动 10 个线程同时调用borrowService.borrowBook(1L, readerId)假设书 ID1预期结果仅 1 个线程成功库存从 1 减到 0其余 9 个抛BusinessException(图书库存不足)查数据库books.stock确为 0borrow_records表只有 1 条记录无Duplicate entry或Lock wait timeout异常。我的自查清单✅SELECT ... FOR UPDATE是否在borrowBook()开头执行而非INSERT后✅conn.setAutoCommit(false)是否在try块最外层设置✅rollback()是否在catch中执行且finally里不写conn.close()try-with-resources 已处理✅BusinessException是否被ControllerAdvice统一捕获前端显示友好提示而非 500 错误页。6.2 模拟断网重连拔掉网线再插回验证连接池自动恢复能力HikariCP 连接池默认connection-test-querySELECT 1但需验证断网后首次数据库操作应抛SQLException连接超时30 秒内恢复网络后续操作应自动重连成功无需重启应用连接池getActiveConnections()数量在断网期间降为 0恢复后回升至maximumPoolSize。配置要点hikari.propertiesspring.datasource.hikari.connection-timeout3000 spring.datasource.hikari.validation-timeout3000 spring.datasource.hikari.idle-timeout600000 spring.datasource.hikari.max-lifetime1800000 spring.datasource.hikari.leak-detection-threshold60000 # 60秒连接泄漏检测6.3 模拟脏数据输入向 ISBN 字段插入超长字符串、空格、特殊符号验证数据库约束是否兜底手动执行INSERT INTO books (title, author, isbn, stock, price) VALUES (测试, 测试, 978-7-04-050694-XXXXX-XX-XXXXX, 1, 99.99); -- 超长 ISBN INSERT INTO books (title, author, isbn, stock, price) VALUES (测试, 测试, , 1, 99.99); -- 纯空格 INSERT INTO books (title, author, isbn, stock, price) VALUES (测试, 测试, 978-7-04-050694-5, 1, 99.99); -- 正确 ISBN预期行为第一条MySQL 报Data too long for column isbnVARCHAR(20)限制第二条INSERT成功但SELECT时isbn为空字符串MySQLVARCHAR默认 trim 空格第三条成功插入SELECT返回978-7-04-050694-5。我的加固动作在Book实体类setIsbn()方法中加isbn isbn null ? null : isbn.trim()在 Service 层校验isbn.length() 20 !isbn.trim().isEmpty()提前拦截数据库isbn字段加NOT NULL和CHECK (LENGTH(TRIM(isbn)) 0)MySQL 8.0.16 支持。我带过的实习生里80% 的人在“能跑通”后就停了但真正拉开差距的是那 20% 会主动拔网线、改数据库配置、用 JMeter 压并发的人。他们不是在造轮子是在给自己的代码装黑匣子——每次部署前跑一遍这三组验证心里才有底。这个图书管理系统编号【100011072】的价值不在它多炫酷而在它逼你把 Java GUI 的响应、MySQL 的事务、异常的边界、部署的鲁棒性全串成一根不断裂的链。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询