资讯动态

基于Java和MySQL的图书馆管理系统:从建表到事务实战

发布时间:2026/10/4 17:39:18 来源:尧图企业网站定制
简介基于JAVA和MySQL的图书馆管理信息系统PDF文档面向软件开发学习者、数据库设计初学者以及需要完成课程设计的高校学生旨在帮助读者掌握图书借阅、归还、查询及读者信息管理系统的建设方法。文档系统梳理了从需求分析、ER图与关系模型转换到数据库物理设计中的索引、视图、安全机制再到Java Swing前端、JDBC数据库连接及测试部署的完整流程并以课程设计报告形式组织章节层次分明便于对照查阅。包内仅1个PDF文件整体644KB内容紧凑便于阅读和打印。目前已有99人学习适合作为参考模板或实战蓝本。读者可从中获得图书、读者、借阅登记等核心表结构设计思路以及系统模块划分、权限控制与异常处理等实现要点有助于快速理解基于Java与MySQL的桌面信息系统开发方法。1. 图书馆管理信息系统基于JAVA和MySQL先把最小闭环跑通再说“图书馆管理信息系统基于JAVA和MySQL”这个标题在课程设计和毕设里流传很广。但照着论文里的结构去复现时很多人卡住的不是SQL写不出来而是借书还书这件事落地时到处是边界书刚被借走还显示在架、逾期天数算错一天、两个人同时借同一本书。这类系统的核心是把图书与读者维护、借书、还书、逾期罚款、统计查询这个最小闭环跑完整。数据落在MySQLJava通过JDBC访问业务规则在Java层实现。技术栈不新却是理解“Java应用如何配合MySQL”的最小完整样本能在一台电脑上跑通并讲清每个环节。适合正在做课设或毕设的同学也适合想补Java基础与MySQL配合细节的初级开发。照着把表、连接层和借还书事务搭起来你对数据库应用系统的体感会扎实很多。2. 先拆模块与数据表图书馆管理系统的状态流转与建库SQL动手写代码之前先把模块和表结构定下来。这类课设翻车大多翻在没想清楚状态流转导致Java代码后来写得像打补丁。表结构定好了业务逻辑其实很固定。2.1 功能模块划分借书、还书、逾期罚款的最小闭环最简系统至少包含四块后台功能图书管理、读者管理、借阅管理、统计查询外加一个管理员登录。每块都不是孤立的图书管理要维护馆藏总数与在架状态读者管理要维护借书证状态借阅管理是核心一次借书会同时修改图书、读者和借阅记录三处数据统计查询则依赖前面积累的数据。我一般先把状态流转画出来再写建表语句。图书有“在馆 / 借出 / 下架”三种状态读者有“正常 / 挂失 / 注销”借阅记录有“借出 / 已还 / 逾期未还”。这三个状态组之间互相约束比如读者挂失时不能借书图书下架时不能借出。另一个课设里很常见的错误是只建一张“借阅记录表”然后用“最新一条记录是否存在”去推断图书是否在架。这个思路在没有并发时能用一旦出现归还、续借、预约查询就会变得极其别扭。正确做法是把“当前是否可借”冗余到图书表里借出时扣减还书时加回借阅记录表只负责流水账。罚款字段我建议直接放在借阅记录表里而不是单独拆一张罚款表。逾期未还、还书时结算都是一条记录上的事报表统计也方便。只有当你要按年度汇总财务、跟其他费用一起管理时再去拆独立罚款表。2.2 建库建表图书、读者、借阅、管理员四张核心表的CREATE语句CREATE DATABASE IF NOT EXISTS library_system DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE library_system; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL COMMENT 密文不要存明文, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT管理员表; CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL COMMENT ISBN用字符型别用INT, title VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), category VARCHAR(50), total_count INT NOT NULL DEFAULT 1 COMMENT 馆藏总数, available_count INT NOT NULL DEFAULT 1 COMMENT 当前可借数量, status TINYINT NOT NULL DEFAULT 0 COMMENT 0在馆 1借出 2下架, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_title (title), KEY idx_category (category) ) ENGINEInnoDB COMMENT图书表; CREATE TABLE readers ( id INT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(20) NOT NULL UNIQUE COMMENT 借书证号, name VARCHAR(50) NOT NULL, phone VARCHAR(20), status TINYINT NOT NULL DEFAULT 0 COMMENT 0正常 1挂失 2注销, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT读者表; CREATE TABLE borrows ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL COMMENT 应还日期 借出日期 借期, return_date DATE NULL, fine DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 逾期罚款, status TINYINT NOT NULL DEFAULT 0 COMMENT 0借出 1已还 2逾期未还, CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES books(id), CONSTRAINT fk_borrow_reader FOREIGN KEY (reader_id) REFERENCES readers(id), KEY idx_status (status), KEY idx_due_date (due_date) ) ENGINEInnoDB COMMENT借阅表;这段建库建表有几个地方值得说明。字符集用utf8mb4而不是utf8因为utf8在MySQL里实际是utf8mb3存生僻字和部分符号会报错或乱码utf8mb4才是完整的Unicode字符集。ISBN字段必须用VARCHAR不能用INT。ISBN有13位数字还有可能带连字符超出INT范围会直接报错即使存得下前导零也会丢失。图书总量和可借数量都定义成INT罚款金额用DECIMAL(10,2)不能用FLOAT或DOUBLE浮点数在金额累计时会累积误差。索引的选择上books.title和books.category用于模糊查询和分类筛选borrows.status和borrows.due_date用于统计逾期和排序。外键在课设阶段建议保留能让数据完整性多一层保障。但要注意生产环境里很多团队会去掉外键改用应用层保证这是后话。2.3 设计取舍外键、冗余字段与索引选型的三个决定外键在课设和面试里是加分项但在高并发业务里经常被故意去掉。原因有两点一是外键会让每次写操作都去检查关联表锁的范围变大二是分库分表后外键在物理上根本不成立。这个系统只有几万条数据保留外键完全没问题。available_count是个典型的冗余字段。它让“查一本书能不能借”变成一次主键查询不用去borrows表里统计未归还记录。代价是每次借书、还书都得多维护这个字段而且并发下要小心扣成负数这个坑第5章会专门讲。关于排序和搜索统计报表里的ORDER BY尽量走due_date、created_at这类有索引的字段别在Java代码里把全表捞出来再用内存排序。模糊查询里LIKE %关键字%用不上普通B树索引数据量小无所谓量大了要换全文索引或者搜索引擎这是后话但不该在课设阶段养成全表扫描的习惯。3. JDBC连接与数据访问层让Java程序真正连上MySQL表建好之后第一个要解决的是连接问题。“mysql安装教程”和“java环境变量配置”是这条路上最先出现的两个热搜词但真正卡人的往往是驱动版本和URL参数。3.1 驱动选择与JDBC URL参数时区、SSL、字符集怎么配// MySQL 5.x 时代的驱动入口是 com.mysql.jdbc.Driver // MySQL 8.x 必须换成 com.mysql.cj.jdbc.Driver Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/library_system ?useUnicodetruecharacterEncodingutf8 useSSLfalse serverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; Connection conn DriverManager.getConnection(url, root, 你的密码);Class.forName在JDBC 4.0之后其实可以省略因为驱动jar包里的META-INF/services会自动注册但保留它能让新手看清楚驱动加载的过程也不影响运行。URL里的参数才是真正的坑。很多人照着老教程写jdbc:mysql://localhost:3306/library_system在MySQL 8上直接报SSL connection error或者时区错误。这里的useSSLfalse表示本地开发不做SSL握手自签名证书在客户端不受信任时不会卡住连接serverTimezoneAsia/Shanghai解决MySQL 8把时区默认成UTC导致的时间差8小时问题characterEncodingutf8配合建库时的utf8mb4才能保证中文不乱码。参数名取值作用不配会怎样useUnicodetrue启用Unicode字符集转换中文乱码characterEncodingutf8指定客户端编码中文乱码useSSLfalse关闭SSL握手报 SSL connection errorserverTimezoneAsia/Shanghai指定服务器时区时间差8小时allowPublicKeyRetrievaltrue允许客户端获取公钥报 Public Key Retrieval is not allowed最后那个allowPublicKeyRetrievaltrue是MySQL 8的caching_sha2_password认证插件引入的。旧版驱动连接8.0会直接失败很多人卡在这一步很久实际上换一个8.x版本的驱动jar就能解决。下载驱动时要注意jar包版本和MySQL服务器版本最好对齐5.7的库配8.x驱动能跑通反过来大概率翻车。3.2 封装一个DBUtil连接、关闭、查询与更新的通用方法import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/library_system ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 你的密码; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 关闭顺序ResultSet - Statement - Connection顺序反了会浪费连接 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) {} } } }这个DBUtil把每一次获取连接和关闭资源的样板代码收拢到一个类里。关闭顺序必须是从里到外先ResultSet再Statement最后Connection。顺序反了虽然不一定立刻报错但长时间运行会积累大量未释放的资源尤其是连接池场景下连接泄漏是服务端“越跑越慢”的一个隐蔽原因。如果项目里没有引入任何依赖管理工具驱动jar要手动放到项目的lib目录并加入构建路径。不要图省事把jar路径写进系统的CLASSPATH环境变量换一台机器、换一个IDE就会找不到类。用IDE的项目依赖或者Maven管理mysql-connector-j才是正路。3.3 连接池与事务边界什么时候用什么时候别硬上这个桌面端课设系统单机单用户直接用DriverManager完全够了不必上连接池。但如果你把系统做成了Java Web应用或者用Spring Boot包了一层HTTP接口那每次请求都用DriverManager创建连接就非常浪费。MySQL建立连接要经过TCP握手、认证、权限校验耗时通常在几十毫秒到上百毫秒高并发下会成为瓶颈。常见做法是换HikariCP连接池它能复用连接并且帮你处理连接失效后的重连。连接池的配置核心是三个参数maximumPoolSize控制池子里最多放多少连接一般设为CPU核心数 * 2 1minimumIdle控制空闲时保留多少连接connectionTimeout控制获取连接的最大等待时间。课设或者内部小系统这几个参数用默认值就行不用反复调整。事务边界这块是重头戏。借书要同时改图书和插入借阅记录必须保证原子性。很多人把DBUtil.getConnection()当成普通查询在用每条SQL各拿一个连接结果事务根本不存在——因为每个连接是独立的各自会自动提交。正确的做法是在业务方法里获取一个连接关掉自动提交全部SQL执行完统一commit任何一个失败都rollbackConnection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交事务从这里开始 // 执行多条写操作扣库存、插借阅记录…… conn.commit(); // 全部成功提交 } catch (SQLException e) { if (conn ! null) { conn.rollback(); // 任何一步失败回滚全部 } throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } }setAutoCommit(false)之后所有SQL都在同一个事务里执行直到commit才真正落盘。这里最忌讳的是把getConnection()封装成一个静态方法每条SQL都调用一次然后还指望它是同一个事务。事务必须绑定同一个Connection没有例外。4. 借书还书的核心业务事务、状态流转与逾期计算模块和连接层就绪后核心业务就集中到两个方法上借书和还书。这两个方法覆盖了事务、状态流转、日期计算和并发控制是这个项目里最值得反复写的部分。4.1 借书流程先查后扣两条写操作必须放进同一个事务public boolean borrowBook(int bookId, int readerId) throws SQLException { Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 第一步校验图书和读者状态 String checkSql SELECT available_count, status FROM books WHERE id ? FOR UPDATE; PreparedStatement checkStmt conn.prepareStatement(checkSql); checkStmt.setInt(1, bookId); ResultSet rs checkStmt.executeQuery(); if (!rs.next()) { throw new SQLException(图书不存在); } int available rs.getInt(available_count); int bookStatus rs.getInt(status); if (bookStatus 2 || available 0) { throw new SQLException(图书不在馆或不可借); } String readerSql SELECT status FROM readers WHERE id ?; PreparedStatement readerStmt conn.prepareStatement(readerSql); readerStmt.setInt(1, readerId); ResultSet readerRs readerStmt.executeQuery(); if (!readerRs.next() || readerRs.getInt(status) ! 0) { throw new SQLException(读者状态异常无法借书); } // 第二步扣减可借数量同时把图书状态置为借出 String updateBook UPDATE books SET available_count available_count - 1, status 1 WHERE id ? AND available_count 0; PreparedStatement updateStmt conn.prepareStatement(updateBook); updateStmt.setInt(1, bookId); if (updateStmt.executeUpdate() 0) { throw new SQLException(图书已被借出); } // 第三步插入借阅记录借期默认30天 String insertBorrow INSERT INTO borrows (book_id, reader_id, borrow_date, due_date, status) VALUES (?, ?, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY), 0); PreparedStatement insertStmt conn.prepareStatement(insertBorrow); insertStmt.setInt(1, bookId); insertStmt.setInt(2, readerId); insertStmt.executeUpdate(); // 在同一个事务里清理资源时不要关闭 conn conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); } }这段代码的核心在SELECT ... FOR UPDATE。它会对books表里id ?这一行加排他锁锁一直保持到事务提交或回滚。两个管理员同时借同一本书时第二个事务会阻塞在SELECT上直到第一个事务提交后才能读到最新数据从而避免把库存扣成负数。UPDATE books SET available_count available_count - 1 ... WHERE id ? AND available_count 0是第二道防线。即使前面的行锁没有命中这个条件也会保证只有库存大于0时才会执行更新executeUpdate()返回0就说明没抢到。due_date用DATE_ADD(CURDATE(), INTERVAL 30 DAY)在SQL端计算借期30天写死在SQL里。如果想把借期做成参数就把30换成?再通过setInt传入。这种写法比在Java端算好日期再传字符串更可靠日期格式不会出错。事务里抛出的异常会触发rollback()三条SQL要么全成功要么全不生效不会出现图书状态改了、借阅记录没插进去的中间状态。4.2 还书与逾期罚款用LocalDate算清每一天public boolean returnBook(int borrowId) throws SQLException { Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); String query SELECT book_id, due_date, return_date, status FROM borrows WHERE id ? FOR UPDATE; PreparedStatement qStmt conn.prepareStatement(query); qStmt.setInt(1, borrowId); ResultSet rs qStmt.executeQuery(); if (!rs.next() || rs.getInt(status) 1) { throw new SQLException(借阅记录不存在或已归还); } int bookId rs.getInt(book_id); java.sql.Date dueDate rs.getDate(due_date); // 用 java.time 计算逾期天数避免 Date 的过期 API 出错 java.time.LocalDate dueLocal dueDate.toLocalDate(); java.time.LocalDate today java.time.LocalDate.now(); long overdueDays java.time.temporal.ChronoUnit.DAYS.between(dueLocal, today); if (overdueDays 0) { overdueDays 0; } // 罚金规则每天0.5元封顶50元 double fine Math.min(overdueDays * 0.5, 50.0); String updateBorrow UPDATE borrows SET return_date CURDATE(), fine ?, status 1 WHERE id ?; PreparedStatement uStmt conn.prepareStatement(updateBorrow); uStmt.setDouble(1, fine); uStmt.setInt(2, borrowId); uStmt.executeUpdate(); // 归还后把库存加回来如果这是最后一本状态改回在馆 String updateBook UPDATE books SET available_count available_count 1, status 0 WHERE id ?; PreparedStatement bStmt conn.prepareStatement(updateBook); bStmt.setInt(1, bookId); bStmt.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); } }日期计算是这里最容易出错的地方。java.util.Date的老API在计算“相隔几天”时容易踩时区、毫秒的坑我一般直接用java.time.LocalDate。ChronoUnit.DAYS.between返回的两个日期相隔天数负数表示还没到期直接归零。还书时同样要对借阅记录加FOR UPDATE防止同一本书被两次归还。归还的最终状态统一写成status 1不管它之前是“借出”还是“逾期未还”都在归还动作里一笔勾销。图书的available_count加回1status恢复成0。逾期罚金规则是典型的业务参数每天0.5元、封顶50元写死在代码里是为了课设演示。如果想让规则可配置可以抽到常量类或者建一张config表放罚金单价和封顶金额业务方法里读配置。这样答辩时改需求不用改代码加分项。4.3 统计查询与分页排序走索引检索用参数化-- 逾期未还图书列表按逾期天数倒序 SELECT r.card_no, r.name AS reader_name, b.title AS book_title, br.borrow_date, br.due_date, DATEDIFF(CURDATE(), br.due_date) AS overdue_days FROM borrows br JOIN readers r ON br.reader_id r.id JOIN books b ON br.book_id b.id WHERE br.status IN (0, 2) AND br.due_date CURDATE() ORDER BY overdue_days DESC LIMIT 0, 20; -- 借阅次数最多的10本图书 SELECT b.title, COUNT(*) AS borrow_times FROM borrows br JOIN books b ON br.book_id b.id GROUP BY br.book_id, b.title ORDER BY borrow_times DESC LIMIT 10;ORDER BY overdue_days DESC里那个overdue_days是别名MySQL允许在ORDER BY里使用别名但在WHERE里必须用原始字段比较因为别名在WHERE阶段还不存在。排序字段如果想走索引直接排due_date就行别在Java层做内存排序。Java侧执行查询时凡是带用户输入的条件一律用PreparedStatement的?占位不能直接拼接字符串。这既是防SQL注入也是让MySQL能复用执行计划。分页用LIMIT offset, size页数变大后offset很大时效率会下降课设数据量小不用纠结。统计查询不需要事务连接用完立即关闭即可。把报表SQL封装成独立的查询方法放ReportDAO跟借还书的写操作分开代码结构会清晰很多答辩时也容易讲。5. 避坑与排查JAVAMySQL项目最常见的5个翻车现场这5个坑我基本都踩过一遍写下来省得你反复试错。有些问题看起来像“玄学”其实都能从驱动版本、URL参数、事务边界、字符集这几条线找到根因。5.1 环境层翻车驱动丢失、SSL连接错误、认证插件不匹配现象运行时报ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因绝大多数是驱动jar没进classpath。很多人照着老教程把jar路径写进系统环境变量CLASSPATH结果换目录、换IDE、换机器就找不到类还有人是Maven项目但没加mysql-connector-j依赖。解决用IDE的“Add to Build Path”或者Maven/Gradle管理依赖别动系统CLASSPATH。确认jar版本和MySQL服务器版本在同一个大版本MySQL 8配8.x驱动MySQL 5.7配5.1.x以上驱动也能用。现象能加载驱动但连接时报Communications link failure或SSL connection error。原因连接URL少了serverTimezone或没关SSL。MySQL 8默认开启SSL要求本地开发没有合法的服务器证书握手阶段就失败serverTimezone缺失会让连接时区设置失败同样报错。解决URL加上?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue。这三件套能解决绝大多数MySQL 8连接问题。mysql ssl连接错误这个关键词搜出来的一大半答案都是这三件套。现象连接时提示Public Key Retrieval is not allowed或者Access denied for user。原因MySQL 8默认认证插件是caching_sha2_password旧版驱动5.1.x不认这个插件或者不允许自动获取公钥做加密传输。解决把驱动升级到8.x并在URL加allowPublicKeyRetrievaltrue。如果你用的是5.7版本的数据库创建用户时也可以指定mysql_native_password但那是为了兼容老客户端生产环境不建议绕开新认证机制。5.2 数据层翻车中文乱码、并发借同一本书现象Java往MySQL里写入中文查出来全是??。原因这是一条链路的问题可能坏在三个位置建库时字符集不是utf8mb4连接URL没带characterEncodingutf8MySQL服务端character_set_server配置不对。只改一个位置不一定见效。解决按“建库、连接、服务端”三层排查。建库语句加DEFAULT CHARACTER SET utf8mb4URL加useUnicodetruecharacterEncodingutf8最后确认MySQL配置文件[mysqld]段有character_set_serverutf8mb4。三层都对齐中文就稳定了。现象两个管理员同时借同一本书库存被扣成负数或者出现两条相同的借阅记录。原因先SELECT判断可借再UPDATE扣减这两步之间有间隙。两个事务都读到同一本可借然后各自执行UPDATE后执行的把前一个覆盖成负数。MySQL默认的锁分类里InnoDB在可重复读隔离级别下普通UPDATE会对命中的行加排他锁但前提是你的事务和索引都正确而且SELECT不加锁时两个连接确实能同时读到旧的库存。解决在借书的第一步就执行SELECT ... FOR UPDATE对这条记录加排他锁让第二个事务阻塞到第一个事务提交同时UPDATE里带上AND available_count 0作为最后一道校验executeUpdate()返回0就说明没抢到。这两层都做了并发借书的问题才真正堵死。关于MySQL事务处理和锁分类的知识背景这里有个简单认知InnoDB的锁是索引锁WHERE id ?能命中主键索引所以锁的是这一行如果条件字段没有索引InnoDB会锁更多行甚至全表这也是为什么前面强调索引要建在查询和更新条件的字段上。6. 进阶从单机到Web化离生产还差哪几步6.1 用JDK定时任务做逾期提醒不急着上框架课设里“逾期未还”通常靠管理员手动查询但很多真实需求是系统每天自动扫描一次把逾期读者列出来。说到定时任务很多人一上来就想用Quartz、XXL-Job这些框架。对于这个小系统JDK自带的ScheduledExecutorService完全够用import java.time.LocalDate; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class OverdueTask { public static void start() { ScheduledExecutorService scheduler Executors.newScheduledThreadPool(1); // 每天凌晨2点执行一次初始延迟按需设置 scheduler.scheduleAtFixedRate(() - { String sql SELECT COUNT(*) AS cnt FROM borrows WHERE status 0 AND due_date ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setDate(1, java.sql.Date.valueOf(LocalDate.now())); ResultSet rs ps.executeQuery(); if (rs.next() rs.getInt(cnt) 0) { // 这里只输出数量实际项目会发站内信或通知 System.out.println(当前逾期未还记录数 rs.getInt(cnt)); } } catch (Exception e) { // 定时任务要记录异常不能吞掉 e.printStackTrace(); } }, 0, 24, TimeUnit.HOURS); } }scheduledAtFixedRate保证按固定频率执行凌晨2点跑一次统计。真实项目里这里会发短信或站内信但通信接口是另一个话题。小系统用JDK自带定时器足够了等量大了再换xxl-job或者Quartz不迟。6.2 并发借书的验证方法与Web化第一步改完事务代码后怎么证明并发控制真的生效别用脑补开两个终端连同一个数据库同时执行借书事务。第一个事务提交前第二个会阻塞在SELECT ... FOR UPDATE上第一个提交后第二个读到的是新库存如果库存已被借光UPDATE返回0并回滚。观察这个现象比看十遍理论都管用。Web化的第一步不是急着引入Spring Boot而是把借书、还书、查询这些业务方法从界面代码里抽出来改成无状态Service类输入参数就是bookId、readerId不再依赖Swing或控制台。然后把DBUtil换成HikariCP连接池最后才是加Controller层。这三步做完后用JMeter模拟20个并发借同一个ID看事务是否仍然正确。我做这类项目时吃过最大的亏是还没把表结构想清楚就急着写界面后期几乎每个功能都在为表结构打补丁。现在不管题目的PDF写得怎么样我都会先花半天把状态流转和事务边界画清楚再动手。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑