首页 / 资讯中心 / 文章详情

Java+SQL Server图书管理系统课程设计:从CRUD到工程思维

Java+SQL Server图书管理系统课程设计:从CRUD到工程思维 ★ FEATURED ARTICLE
简介本资源为基于JAVA与SQL Server的图书管理系统课程设计完整报告面向计算机相关专业学生及需要完成数据库课程设计的开发者。内容围绕图书馆借阅管理场景解决借阅者查找书目困难、管理人员工作繁重等问题涵盖需求分析、E-R图概念设计、关系模式与约束声明、物理结构设计以及JAVA连接SQL Server的集成实现、功能模块划分、测试调试与报告撰写规范等环节适合作为课程设计参考与答辩准备材料。资源包共1个doc文件约672KB为结构完整的课程设计报告文档包含中英文摘要、进度安排、参考文献与成绩评定表等模块。目前已有4270人学习下载可帮助读者快速理解数据库设计与JAVA前端开发的完整流程掌握从需求分析到编码测试的规范化方法并借鉴报告结构与评分标准提升课程设计完成质量与效率。1. 图书管理系统课程设计为什么“能跑”和“能过”之间隔着一整套工程思维很多同学做图书管理系统课程设计第一反应是打开 IDE 新建一个 Java Web 项目连上 SQL Server把增删改查四个按钮一摆觉得功能齐了就能交差。结果答辩时被问“借书时库存怎么扣减”“两个管理员同时操作同一条借阅记录会怎样”“SQL Server 连接池配了多大”当场卡壳。这个标题背后真正要解决的问题不是“写一个能跑的 CRUD”而是用 Java 和 SQL Server 搭出一套有事务边界、有权限分层、有数据一致性保障的小型信息管理系统。它适合正在做课程设计的学生、刚接触企业级开发的初级工程师以及需要把数据库原理落到代码里的人。我见过太多“功能全但一碰就碎”的版本问题几乎都出在把课程设计当成了界面练习而不是工程练习。2. 技术选型与数据库设计为什么是 Java SQL Server 而不是别的组合2.1 Java 做课程设计的现实优势课程设计的时间窗口通常只有两到四周选型的第一原则是“资料多、坑少、老师认”。Java 在这个场景里几乎是默认答案JDBC 是标准 APISQL Server 有官方驱动IDE 对 Java 的支持最成熟遇到问题搜索到的有效答案也最多。更关键的是Java 的强类型和异常机制会逼着你在编译期就处理掉一批低级错误这对课程设计这种“写完就要演示”的场景非常友好。我一般会建议用 JDK 8 或 JDK 11 这两个长期支持版本不要追新。JDK 17 之后的模块化对新手不友好容易在“找不到类”上浪费半天。构建工具用 Maven依赖管理清晰老师看 pom.xml 也能快速判断你的技术栈。Web 层如果必须做界面Servlet JSP 是最稳的选择虽然老但和 SQL Server 的配合资料最全如果允许桌面端Swing 或 JavaFX 也能交差但数据库部分完全一样。2.2 SQL Server 在课程设计里的定位SQL Server 的优势在于它的管理工具 SSMS 足够直观建表、建索引、写查询都能可视化操作对不熟悉命令行的同学很友好。另一个隐性好处是很多学校的机房已经装好了 SQL Server你不需要额外申请环境。但要注意版本差异Express 版有 10GB 单库上限课程设计的数据量完全够用但如果你要演示“大数据量下的索引优化”就得提前确认版本是否支持。连接 SQL Server 需要引入官方 JDBC 驱动Maven 坐标如下dependency groupIdcom.microsoft.sqlserver/groupId artifactIdmssql-jdbc/artifactId version9.4.1.jre8/version /dependency这里版本号选 jre8 后缀对应 JDK 8如果你用 JDK 11换成 jre11 后缀。驱动版本不要贪新9.x 系列在课程设计场景下最稳和 SQL Server 2016 到 2019 都能兼容。2.3 数据库表结构五个核心表与三个容易漏的字段图书管理系统的表结构看起来简单但课程设计的评分点往往藏在细节里。我一般会设计五张核心表图书表、读者表、借阅记录表、管理员表、分类表。下面给出建表 SQL重点看注释里标出的字段。-- 图书表注意 stock 和 total 要分开 CREATE TABLE books ( book_id INT PRIMARY KEY IDENTITY(1,1), isbn VARCHAR(20) NOT NULL UNIQUE, title NVARCHAR(100) NOT NULL, author NVARCHAR(50), category_id INT, total INT NOT NULL DEFAULT 1, -- 总库存 stock INT NOT NULL DEFAULT 1, -- 可借库存 location VARCHAR(20), -- 馆藏位置答辩常问 created_at DATETIME DEFAULT GETDATE() ); -- 借阅记录表状态字段和归还时间必须显式设计 CREATE TABLE borrow_records ( record_id INT PRIMARY KEY IDENTITY(1,1), book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATETIME NOT NULL DEFAULT GETDATE(), due_date DATETIME NOT NULL, -- 应还日期不是可选项 return_date DATETIME NULL, -- 为空表示未归还 status TINYINT NOT NULL DEFAULT 0, -- 0借出 1已还 2逾期 FOREIGN KEY (book_id) REFERENCES books(book_id), FOREIGN KEY (reader_id) REFERENCES readers(reader_id) );total和stock分开是血泪经验。很多同学只存一个“库存”借书减一、还书加一结果遇到“图书丢失”或“下架”时无法区分“总共几本”和“现在能借几本”。due_date必须显式存储不要用borrow_date 30在代码里算因为不同读者类型的借阅期限可能不同存下来才能支持逾期查询。status字段用 TINYINT 而不是 BIT因为要区分“已还”和“逾期未还”两种非借出状态。2.4 连接 SQL Server 的最小可用配置在 Java 里连 SQL Server最容易翻车的地方是连接字符串。下面是一个经过验证的配置public class DBUtil { // 注意encryptfalse 在课程设计环境可以省去证书配置 private static final String URL jdbc:sqlserver://localhost:1433; databaseNameLibraryDB; encryptfalse; loginTimeout30;; private static final String USER sa; private static final String PASSWORD YourStrongPassword; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }encryptfalse是课程设计环境的常见做法因为默认开启加密需要配置证书新手很容易卡在“SSL 错误”上。loginTimeout30避免网络不通时界面卡死。生产环境当然不能这么写但课程设计的评分点不在安全加固而在功能完整和逻辑正确。3. 核心功能实现借书还书背后的三条事务边界3.1 借书操作一个事务里必须完成的三件事借书看起来是“插入一条借阅记录”实际上至少要完成三个动作检查库存、扣减库存、插入借阅记录。这三步必须在一个事务里否则会出现“记录插入了但库存没扣”的脏数据。下面是我常用的实现public boolean borrowBook(int bookId, int readerId) { Connection conn null; PreparedStatement checkStmt null; PreparedStatement updateStmt null; PreparedStatement insertStmt null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 检查库存加行锁防止并发超借 String checkSql SELECT stock FROM books WITH (UPDLOCK) WHERE book_id ?; checkStmt conn.prepareStatement(checkSql); checkStmt.setInt(1, bookId); rs checkStmt.executeQuery(); if (!rs.next() || rs.getInt(stock) 0) { conn.rollback(); return false; } // 2. 扣减库存 String updateSql UPDATE books SET stock stock - 1 WHERE book_id ? AND stock 0; updateStmt conn.prepareStatement(updateSql); updateStmt.setInt(1, bookId); if (updateStmt.executeUpdate() ! 1) { conn.rollback(); return false; } // 3. 插入借阅记录应还日期默认30天后 String insertSql INSERT INTO borrow_records(book_id, reader_id, due_date, status) VALUES(?, ?, DATEADD(DAY, 30, GETDATE()), 0); insertStmt conn.prepareStatement(insertSql); insertStmt.setInt(1, bookId); insertStmt.setInt(2, readerId); insertStmt.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) { } e.printStackTrace(); return false; } finally { // 关闭资源顺序与打开相反 closeQuietly(rs, checkStmt, updateStmt, insertStmt, conn); } }WITH (UPDLOCK)是 SQL Server 特有的表提示它在读取时加更新锁防止两个事务同时读到 stock1 然后都去扣减。没有这个锁并发测试时必然出现库存变成 -1 的情况。UPDATE ... AND stock 0是第二道保险即使锁没拦住条件更新也能保证不会扣成负数。DATEADD(DAY, 30, GETDATE())把应还日期交给数据库算避免 Java 和 SQL Server 时区不一致带来的偏差。3.2 还书操作逾期判断与库存回补还书比借书多一个逾期判断。我的做法是在还书时计算实际归还日期和应还日期的差值如果超过 0 天就标记为逾期同时更新库存。public boolean returnBook(int recordId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 查询借阅记录确认未归还 String querySql SELECT book_id, due_date FROM borrow_records WHERE record_id ? AND return_date IS NULL; PreparedStatement queryStmt conn.prepareStatement(querySql); queryStmt.setInt(1, recordId); ResultSet rs queryStmt.executeQuery(); if (!rs.next()) { conn.rollback(); return false; } int bookId rs.getInt(book_id); java.sql.Timestamp dueDate rs.getTimestamp(due_date); boolean overdue System.currentTimeMillis() dueDate.getTime(); // 2. 更新借阅记录 String updateRecordSql UPDATE borrow_records SET return_date GETDATE(), status ? WHERE record_id ?; PreparedStatement updateRecordStmt conn.prepareStatement(updateRecordSql); updateRecordStmt.setInt(1, overdue ? 2 : 1); updateRecordStmt.setInt(2, recordId); updateRecordStmt.executeUpdate(); // 3. 回补库存 String updateStockSql UPDATE books SET stock stock 1 WHERE book_id ?; PreparedStatement updateStockStmt conn.prepareStatement(updateStockSql); updateStockStmt.setInt(1, bookId); updateStockStmt.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) { } e.printStackTrace(); return false; } finally { closeQuietly(conn); } }逾期判断放在 Java 层而不是 SQL 层是因为课程设计里经常需要把“逾期天数”显示在界面上Java 里算好可以直接传给前端。status用 2 表示逾期和借出状态 0 区分开查询“所有逾期未还”时直接WHERE status 2即可。3.3 查询功能分页、模糊搜索与索引的配合课程设计的查询功能通常要求“按书名模糊搜索 分页”。SQL Server 2012 之后支持OFFSET FETCH比ROW_NUMBER()写法简洁SELECT book_id, title, author, stock FROM books WHERE title LIKE % ? % ORDER BY book_id OFFSET ? ROWS FETCH NEXT ? ROWS ONLY;参数依次是搜索关键词、偏移量、每页条数。偏移量 (页码 - 1) * 每页条数。这个查询在数据量小的时候没问题但如果老师要求演示“一万条数据下的性能”就需要在title上建索引。不过LIKE %关键词%这种前置通配符用不上普通索引课程设计里可以提一句“实际生产会考虑全文索引”答辩时是个加分项。4. 避坑与排查课程设计里最容易翻车的五个地方4.1 连接 SQL Server 报“TCP/IP 连接失败”现象是启动程序后抛异常提示无法连接到 localhost:1433。原因通常是 SQL Server 的 TCP/IP 协议没启用或者端口不是默认的 1433。解决步骤打开 SQL Server 配置管理器展开“SQL Server 网络配置”启用 TCP/IP然后在 IP 地址页签里确认 IPAll 的 TCP 端口是 1433重启 SQL Server 服务。另一个常见原因是 SQL Server Browser 服务没启动导致命名实例无法解析端口。4.2 中文乱码从数据库到界面全链路排查现象是插入的中文书名在 SSMS 里正常但在 Java 程序里读出来是问号。原因通常是 JDBC URL 没指定字符集或者数据库字段用了 VARCHAR 而不是 NVARCHAR。解决字段类型统一用 NVARCHARJDBC URL 里加sendStringParametersAsUnicodetrue。如果用的是 JSP 页面还要确认pageEncoding和contentType都是 UTF-8。这三个地方任何一个漏了都会乱码排查时按“数据库字段 → 连接字符串 → 页面编码”的顺序逐个确认。4.3 并发借书导致库存变成负数现象是两个人同时借同一本书库存从 1 变成 -1。原因是没有加锁或没有条件更新。解决按 3.1 节的写法在查询库存时加WITH (UPDLOCK)在更新时加AND stock 0。如果不想用表提示也可以把检查库存和扣减合并成一条UPDATE ... WHERE stock 0根据executeUpdate()的返回值判断是否成功。两种方式都能解决但前者更直观适合答辩时解释。4.4 事务忘记回滚导致连接泄漏现象是程序运行一段时间后报“连接池耗尽”或“无法获取连接”。原因是在 catch 块里只打印了异常没有调用conn.rollback()导致事务一直挂起连接无法释放。解决在 catch 块里显式回滚在 finally 块里关闭连接。如果用了连接池还要确认close()是归还连接而不是真正关闭。课程设计里如果不用连接池至少要把conn.close()放在 finally 里。4.5 应还日期算错时区和日期格式的坑现象是明明今天借的书应还日期显示成昨天或明天。原因通常是 Java 的new Date()和 SQL Server 的GETDATE()时区不一致或者用SimpleDateFormat格式化时没考虑时区。解决应还日期统一在 SQL Server 里用DATEADD计算Java 只负责显示。如果必须在 Java 里算用LocalDateTime.now()而不是new Date()并且数据库连接字符串里不要加时区参数让两边都用系统默认时区。5. 从课程设计到可演示作品三个让答辩加分的细节5.1 用存储过程封装借书逻辑课程设计里如果所有 SQL 都写在 Java 里答辩时容易被问“数据库层做了什么”。把借书逻辑写成存储过程既能展示 SQL Server 的能力又能把事务边界收在数据库层CREATE PROCEDURE sp_BorrowBook bookId INT, readerId INT, result INT OUTPUT AS BEGIN SET NOCOUNT ON; BEGIN TRY BEGIN TRANSACTION; IF NOT EXISTS (SELECT 1 FROM books WITH (UPDLOCK) WHERE book_id bookId AND stock 0) BEGIN SET result -1; -- 库存不足 ROLLBACK; RETURN; END UPDATE books SET stock stock - 1 WHERE book_id bookId; INSERT INTO borrow_records(book_id, reader_id, due_date, status) VALUES(bookId, readerId, DATEADD(DAY, 30, GETDATE()), 0); COMMIT; SET result 0; -- 成功 END TRY BEGIN CATCH IF TRANCOUNT 0 ROLLBACK; SET result -2; -- 异常 END CATCH ENDJava 侧只需要调用CallableStatement传入参数并读取输出值。这样代码量减少事务逻辑集中在数据库答辩时可以直接打开 SSMS 演示存储过程的执行计划。5.2 加一个简单的操作日志表课程设计通常不要求日志但加一张operation_logs表会让整个系统显得更完整CREATE TABLE operation_logs ( log_id INT PRIMARY KEY IDENTITY(1,1), operator VARCHAR(50), action NVARCHAR(200), created_at DATETIME DEFAULT GETDATE() );在借书、还书、新增图书的方法里各插一条日志。答辩时演示“谁在什么时候借了哪本书”比单纯展示 CRUD 界面更有说服力。日志表不需要复杂三个字段足够。5.3 用视图简化多表查询读者借阅列表需要关联 books、readers、borrow_records 三张表写起来容易出错。建一个视图CREATE VIEW v_borrow_detail AS SELECT r.record_id, b.title, rd.reader_name, r.borrow_date, r.due_date, r.return_date, r.status FROM borrow_records r JOIN books b ON r.book_id b.book_id JOIN readers rd ON r.reader_id rd.reader_id;之后查询直接SELECT * FROM v_borrow_detail WHERE status 0代码简洁答辩时也能解释“视图做了预关联简化了应用层逻辑”。这三个细节花不了多少时间但能让课程设计从“功能堆砌”变成“有设计感的系统”。我做了这么多版课程设计最大的教训是不要等到最后一天才连数据库。先把连接跑通把一张表的增删改查写透再往上叠功能。每次改完数据库结构立刻在 SSMS 里跑一遍所有相关 SQL确认没有语法错误再写 Java。这个习惯帮我省下了无数个通宵调试的夜晚。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站