☰
JAVA+SQL Server图书管理系统课程设计:从E-R图到借还书事务的完整实现
2026/10/10 11:18:53 网站建设 项目流程

简介:这份资源是面向高校计算机相关专业学生的《数据库原理与应用》课程设计完整报告,以Java与SQL Server为技术主线,围绕图书管理系统的设计与开发展开,适合正在准备课程设计、需要参考规范文档结构与数据库设计流程的学习者。压缩包内仅含1个doc文档,体积约672KB,内容涵盖需求分析、E-R图概念结构设计、关系模式与主外码等约束的逻辑设计、物理结构设计,以及功能模块划分、编码调试与测试分析等环节,并附有成绩评定表与参考文献格式示例。报告正文按课程设计规范组织,从系统目的、业务流程图到数据库实施与前端开发均有对应章节,可帮助读者理清从需求到实现的完整链路。目前已有4270人学习下载,适合作为课程设计写作模板与数据库建模思路的参考。

1. 从一份课程设计报告说起:这套 JAVA + SQL Server 图书管理系统到底能跑通什么

如果你手头正躺着一份《数据库原理与应用》课程设计的任务书,要求里写着“需求分析、E-R 图、逻辑结构、物理设计、编码调试、撰写报告”,那这套基于 JAVA 和 SQL Server 的图书管理系统,基本就是照着这份任务书长出来的。它不是玩具级的增删改查演示,而是把管理员和读者两个角色、登录注册、图书管理、借还书、日志记录这几条业务线全部串起来的一个可运行闭环。前端用 Eclipse 写 JAVA,后端用 SQL Server 建库建表,中间靠 JDBC 打通,整个链路是课程设计里最经典也最容易被答辩老师追问细节的那一类。

适合谁用?一是正在做同类课程设计、需要一份能跑通且结构完整的参考实现的人;二是刚学完 JDBC 想找个真实场景练手的新手;三是需要快速搭一个图书借阅原型来验证业务逻辑的开发者。这套东西的价值不在于代码多高级,而在于它的数据库设计流程是完整的——从 E-R 图到关系模式再到建表脚本,每一步都有据可查,答辩时被问到“为什么这么设计”你能答得上来。下面我按实际复现的顺序,把这份资源拆开讲。

2. 数据库设计先落地:从 E-R 图到四张核心表的建表脚本

2.1 需求分析要落到实体和联系上,别停在功能列表

很多课程设计报告的需求分析写成了功能清单——“管理员能管理图书,读者能查询图书”,这种写法答辩时容易被追问“那你的数据模型呢”。正确的做法是从业务描述里抽实体和联系。这套系统的业务场景很清晰:读者借书、管理员管书、系统记日志。抽出来的实体有三个——读者、图书、管理员,联系有两个——管理员管理图书(1:n)、读者借阅图书(m:n)。借阅这个联系本身带属性:借还书状态、操作日期、已付款额,所以它不能只画成一条线,要上升为实体来处理,这就是后面 ManageLog 表存在的原因。

提示:E-R 图里如果联系带属性,逻辑设计阶段就要考虑把它转成独立的关系模式,否则属性没地方挂。

2.2 四张核心表的关系模式与约束声明

概念设计完成后,转关系模式这一步是有固定规则的:实体转表,1:n 联系把“1”端的主键放到“n”端做外键,m:n 联系单独转表。这套系统最终落成四张表:

表名主键核心字段约束要点
Passwordnamename, password, typename 唯一且非空,type 区分管理员/读者
Readerreadernoreaderno, name, sex, grade, depname, classreaderno 唯一且非空,其余允许空
Bookbooknobookno, bookname, price, author, press, intime, isborrowbookno 唯一且非空,isborrow 标记借出状态
ManageLogbooknobookno, bookname, readerno, bhandle, time, pay记录每一次借还操作,bookno 唯一

这里有个容易翻车的点:Password 表的 type 字段用 char(10) 存“管理员”或“读者”,而不是用数字编码。好处是可读性强,坏处是如果前端传参时大小写或空格不一致,登录校验会莫名其妙失败。我一般会在 JDBC 里对 type 做 trim 处理。

2.3 建表脚本与 JDBC 连接配置

下面这段 SQL 可以直接在 SQL Server Management Studio 里执行,建库建表一步到位。注意 nvarchar 和 nchar 的选择——中文姓名、书名用 nvarchar,性别这种固定长度的用 nchar。

-- 创建数据库 CREATE DATABASE LibraryDB; GO USE LibraryDB; GO -- 管理员/读者登录表 CREATE TABLE Password ( name NVARCHAR(50) NOT NULL PRIMARY KEY, password NVARCHAR(50) NOT NULL, type CHAR(10) NOT NULL ); -- 读者信息表 CREATE TABLE Reader ( readerno NVARCHAR(50) NOT NULL PRIMARY KEY, name NVARCHAR(50) NOT NULL, sex NCHAR(10) NOT NULL, grade NVARCHAR(50) NULL, depname NVARCHAR(50) NULL, class NVARCHAR(50) NULL ); -- 图书信息表 CREATE TABLE Book ( bookno NVARCHAR(50) NOT NULL PRIMARY KEY, bookname NVARCHAR(50) NOT NULL, price NCHAR(10) NOT NULL, author NVARCHAR(50) NOT NULL, press NVARCHAR(50) NOT NULL, intime NVARCHAR(50) NOT NULL, isborrow NCHAR(50) NULL ); -- 借还书日志表 CREATE TABLE ManageLog ( bookno NVARCHAR(50) NOT NULL PRIMARY KEY, bookname NVARCHAR(50) NOT NULL, readerno NVARCHAR(50) NOT NULL, bhandle NVARCHAR(10) NOT NULL, time NVARCHAR(50) NOT NULL, pay NVARCHAR(10) NOT NULL );

建完表之后,JAVA 端要能连上。SQL Server 的 JDBC 驱动版本要和数据库版本匹配,常见做法是用 mssql-jdbc 驱动,连接串里注意 encrypt 和 trustServerCertificate 参数,否则 SQL Server 2019 以后默认加密会直接拒连。

// 数据库连接工具类 public class DBUtil { private static final String URL = "jdbc:sqlserver://localhost:1433;databaseName=LibraryDB;" + "encrypt=true;trustServerCertificate=true"; private static final String USER = "sa"; private static final String PWD = "你的密码"; public static Connection getConnection() throws SQLException { // 加载驱动,SQL Server 新版驱动类名如下 return DriverManager.getConnection(URL, USER, PWD); } }

参数说明:encrypt=true是 SQL Server 新版驱动的默认行为,trustServerCertificate=true表示信任本地自签证书,本地开发够用;如果部署到正式环境,应该换成受信任的证书而不是一路 trust。databaseName指定库名,端口默认 1433,如果装的时候改了实例名,连接串要换成instanceName写法。

3. 三个功能模块的 JAVA 实现:登录、管理员、读者怎么串起来

3.1 登录模块:一次查询搞定角色分流

登录的逻辑不复杂,但它是整个系统的入口,写不好后面全是坑。核心就一条 SQL:根据用户名和密码查 Password 表,拿到 type 字段决定跳管理员界面还是读者界面。

public String login(String name, String password) { String sql = "SELECT type FROM Password WHERE name = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, name.trim()); ps.setString(2, password.trim()); ResultSet rs = ps.executeQuery(); if (rs.next()) { // 返回角色类型,调用方据此跳转 return rs.getString("type").trim(); } } catch (SQLException e) { e.printStackTrace(); } return null; // 登录失败 }

这里用 PreparedStatement 而不是 Statement 拼接,是为了防 SQL 注入——课程设计答辩时老师大概率会问这个点。trim()不能省,因为 SQL Server 的 char 类型会自动补空格,不 trim 的话“管理员”可能变成“管理员 ”,比较就失败了。

3.2 管理员模块:图书管理与借还书的状态流转

管理员模块的功能点最多:修改密码、管理读者、管理图书、新书入库、管理日志。其中最容易出 bug 的是借还书的状态流转。借书时要把 Book 表的 isborrow 从“未借出”改成“已借出”,同时往 ManageLog 插一条记录;还书时反过来,还要计算超期天数对应的罚款金额。

// 借书操作:更新图书状态 + 写日志,放在同一个事务里 public boolean borrowBook(String bookno, String readerno) { String updateSql = "UPDATE Book SET isborrow = '已借出' WHERE bookno = ? AND isborrow = '未借出'"; String logSql = "INSERT INTO ManageLog(bookno, bookname, readerno, bhandle, time, pay) " + "SELECT bookno, bookname, ?, '借书', CONVERT(NVARCHAR(50), GETDATE(), 120), '0' " + "FROM Book WHERE bookno = ?"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 try (PreparedStatement ps1 = conn.prepareStatement(updateSql); PreparedStatement ps2 = conn.prepareStatement(logSql)) { ps1.setString(1, bookno); int rows = ps1.executeUpdate(); if (rows == 0) { conn.rollback(); return false; // 书已被借出或不存在 } ps2.setString(1, readerno); ps2.setString(2, bookno); ps2.executeUpdate(); conn.commit(); return true; } } catch (SQLException e) { if (conn != null) try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { if (conn != null) try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

逻辑说明:UPDATE语句里带了AND isborrow = '未借出'这个条件,是为了防止并发借同一本书——如果两个人同时点借书,只有一个人能更新成功,另一个人executeUpdate返回 0,直接回滚。日志插入用INSERT ... SELECT从 Book 表取书名,避免前端传参不一致。事务保证了两步操作要么都成功要么都失败,不会出现“书借出去了但日志没记上”的黑匣子情况。

3.3 读者模块:查询与借还书的权限边界

读者模块的功能相对轻:修改密码、改个人信息、查图书、借还书。但这里有个权限边界要注意——读者只能查自己的借阅记录,不能看别人的。实现上就是在查询日志时强制带上readerno条件。

// 读者查询自己的借阅记录 public List<String[]> myBorrowLog(String readerno) { List<String[]> list = new ArrayList<>(); String sql = "SELECT bookno, bookname, bhandle, time, pay FROM ManageLog WHERE readerno = ? ORDER BY time DESC"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, readerno); ResultSet rs = ps.executeQuery(); while (rs.next()) { list.add(new String[]{ rs.getString("bookno"), rs.getString("bookname"), rs.getString("bhandle"), rs.getString("time"), rs.getString("pay") }); } } catch (SQLException e) { e.printStackTrace(); } return list; }

参数说明:readerno从登录后的会话里取,不能从界面输入框取,否则读者改一下输入就能查别人的记录。ORDER BY time DESC让最近的记录排前面,体验上更合理。

4. 避坑与排查:这套系统跑不起来时先查这五个地方

4.1 现象:JDBC 连接直接抛异常,提示“无法建立连接”

原因通常是三个:SQL Server 的 TCP/IP 协议没启用、端口不是默认的 1433、或者驱动版本和数据库版本不匹配。SQL Server 安装后默认只开命名管道,TCP/IP 要在配置管理器里手动启用。解决步骤:打开 SQL Server 配置管理器 → SQL Server 网络配置 → 实例的协议 → 启用 TCP/IP → 重启服务。然后在连接串里确认端口,如果改过端口要显式写上portNumber。

4.2 现象:中文书名或姓名存进去变成问号

原因是数据库字段用了 varchar 而不是 nvarchar,或者 JDBC 连接串没指定字符集。SQL Server 的 varchar 按当前排序规则存字符,中文可能丢。解决:建表时所有可能存中文的字段一律用 nvarchar 或 nchar,连接串加sendStringParametersAsUnicode=true(部分驱动版本需要)。

4.3 现象:借书成功但日志表里没有记录

这是典型的没加事务导致的。如果先更新 Book 表成功,再插日志时因为主键冲突或其他原因失败,就会出现状态不一致。解决:把两步操作包在同一个 Connection 里,关掉 autoCommit,手动 commit/rollback。上面 3.2 的代码已经处理了这一点。

4.4 现象:登录时明明密码对却提示失败

最常见的原因是 char 类型的空格填充。Password 表的 type 字段是 char(10),存“管理员”实际占 10 个字符,后面补空格。如果 JAVA 端用equals比较没 trim,就会失败。解决:所有从 char 字段取出来的值都.trim()一下再比较。另一个可能是密码字段在插入时带了前后空格,注册时就要 trim。

4.5 现象:Eclipse 里编译通过但运行时报 ClassNotFoundException

说明 JDBC 驱动 jar 包没加到项目的 Build Path 里。解决:把 mssql-jdbc 的 jar 文件复制到项目 lib 目录,右键 Build Path → Add JARs。如果用 Maven,在 pom.xml 里加对应依赖,注意 scope 是 runtime。

5. 进阶技巧:用存储过程把借还书逻辑收进数据库,顺带聊聊答辩怎么讲

前面借书逻辑写在 JAVA 里,能跑,但答辩时如果老师问“如果换一个前端,这套逻辑是不是要重写”,你就被动了。更稳的做法是把借还书的核心逻辑做成存储过程,JAVA 端只负责调用。这样业务规则收在数据库层,前端换语言也不影响。

-- 借书存储过程:检查状态、更新、写日志,一次调用完成 CREATE PROCEDURE BorrowBook @bookno NVARCHAR(50), @readerno NVARCHAR(50) AS BEGIN SET NOCOUNT ON; BEGIN TRANSACTION; -- 检查图书是否可借 IF NOT EXISTS (SELECT 1 FROM Book WHERE bookno = @bookno AND isborrow = '未借出') BEGIN ROLLBACK; RETURN -1; -- 不可借 END -- 更新图书状态 UPDATE Book SET isborrow = '已借出' WHERE bookno = @bookno; -- 写日志 INSERT INTO ManageLog(bookno, bookname, readerno, bhandle, time, pay) SELECT bookno, bookname, @readerno, '借书', CONVERT(NVARCHAR(50), GETDATE(), 120), '0' FROM Book WHERE bookno = @bookno; COMMIT; RETURN 0; -- 成功 END;

JAVA 端调用就变成一行CallableStatement,参数和返回值都清晰。这个改法的好处在答辩时特别明显:你可以说“业务逻辑下沉到数据库层,前端只做展示和参数传递,符合分层设计原则”。老师一般会点头。

验证存储过程是否生效,最简单的办法是在 SSMS 里直接执行EXEC BorrowBook 'B001', 'R001',然后查 Book 表和 ManageLog 表看状态是否一致。如果返回 -1,说明书已经被借出或书号不存在。

还有一个容易被忽略的点:ManageLog 表的 bookno 设了主键,意味着同一本书只能有一条日志记录。但实际业务里一本书会被借还多次,这个设计是有问题的。我一般会改成用自增列做日志主键,bookno 只做普通索引。如果你拿到的源码里 bookno 是主键,借还第二次就会主键冲突,这是血泪经验,提前改掉能省很多调试时间。

从那以后我每次拿到这类课程设计源码,第一件事就是查日志表的主键设计,确认它能支撑多次借还。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询