简介这是一份基于Java与SQL Server的简易图书馆管理系统课程设计资源专门面向计算机相关专业正在准备数据库课程设计的学生也适合入门级Java开发人员用于学习项目整合。系统围绕图书馆日常业务完整实现了图书信息录入与修改、读者信息管理、借书还书流程、登录验证以及选座管理等功能模块并附带可直接执行的编译版本大幅降低环境搭建门槛。资源包共127个文件核心为java源文件和class编译文件同时包含jar依赖库、SQL建库脚本、png与jpg界面截图、xlsx及xls表格数据压缩后仅1.43MB目录层次清晰方便按需查看和迁移部署。已有2386人学习下载对于需要快速搭建数据库课设、理解Swing界面与SQL Server联动、或在此基础上扩展功能的同学来说是一份很实用的参考模板。1. 这题目年年有人选翻车也年年一样每次到数据库课设季节“图书馆管理系统”一定是最热门的题目。表面看它只是图书增删改查加个借还书实际上一半人挂在同一个地方Java连不上SQL Server。不是代码写错而是驱动、实例名、认证方式、TCP/IP协议这几个环节里某个黑匣子没打开最后整组人默默换回MySQL或Access交差。本文将基于Java和SQL Server数据库把一个简易图书馆管理系统怎么做讲透。适合正在做课设、想从JDBC层面理解数据库连接的同学也适合想快速搭一套能答辩的最小系统的读者。你会拿到建表SQL、连接代码、借还书事务和五条踩坑记录照着搭一天能跑通。2. 数据库设计先行五张表怎么撑起借还书闭环2.1 简易不等于随便建表业务线要先分清楚很多课设拿到题目第一反应是建一张“图书表”再说结果做到借书功能时发现缺读者表做到统计时发现缺分类表来回改结构。我一般先画三条业务线谁在借读者、借什么图书、什么时候借还借阅记录再加一个管理员账号做登录入口分类表用于图书筛选和统计。简易系统最少五张表admin、readers、books、categories、borrow。别小看这五张表它能支撑登录、图书管理、读者管理、借书还书、逾期统计这几个课设必问的功能。表与表的关系也直接决定后面代码难度。readers和books之间是多对多借阅记录表borrow就是它们的关联表每本书当前是否在馆、被谁借走、什么时候该还都在borrow里查。管理员和读者分开建表是安全习惯我见过把管理员塞进读者表再用role字段区分的答辩时评委一句“权限如何控制”就答得支支吾吾。分开表之后登录逻辑只需要查一张表职责清楚。2.2 建表SQL把约束写在数据库里别只靠Java判断下面是建库建表的SQLSQL Server 2019 Express即可运行。课程设计不需要企业版Express完全够用注意Express默认实例名是SQLEXPRESS连接串里会用到这是后话。-- 创建数据库 CREATE DATABASE LibraryDB; GO USE LibraryDB; GO -- 管理员表 CREATE TABLE admin ( admin_id INT IDENTITY(1,1) PRIMARY KEY, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ); GO -- 读者表 CREATE TABLE readers ( reader_id INT IDENTITY(10001,1) PRIMARY KEY, name VARCHAR(30) NOT NULL, phone VARCHAR(20), max_borrow INT DEFAULT 5 ); GO -- 分类表 CREATE TABLE categories ( category_id INT IDENTITY(1,1) PRIMARY KEY, category_name VARCHAR(50) NOT NULL ); GO -- 图书表 CREATE TABLE books ( book_id INT IDENTITY(1,1) PRIMARY KEY, title VARCHAR(100) NOT NULL, author VARCHAR(50), category_id INT, total INT DEFAULT 1, available INT DEFAULT 1, FOREIGN KEY (category_id) REFERENCES categories(category_id) ); GO -- 借阅记录表 CREATE TABLE borrow ( borrow_id INT IDENTITY(1,1) PRIMARY KEY, reader_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATE DEFAULT CAST(GETDATE() AS DATE), due_date DATE, return_date DATE NULL, FOREIGN KEY (reader_id) REFERENCES readers(reader_id), FOREIGN KEY (book_id) REFERENCES books(book_id) ); GO建表的关键点有三个第一IDENTITY自增主键让Java端不用维护主键值少一类并发冲突第二available字段代表可借数量它是借书时判断能否借出的依据比每次现算已借数量高效得多第三due_date不是随便算出来的一般用DATEADD(DAY, 30, borrow_date)生成下面会写。2.3 借书还书的事务逻辑先查后扣任何一步失败都要回滚借书看起来是往borrow表插一条记录实际要动两张表borrow增加记录books的available减一。让Java代码用两条SQL循环完成不是不行但并发一高就会超借。最稳妥的做法是把事务写进存储过程数据库保证原子性。以下存储过程是课设答辩的加分项-- 借书存储过程 CREATE PROCEDURE usp_borrow_book reader_id INT, book_id INT AS BEGIN BEGIN TRANSACTION; BEGIN TRY DECLARE avail INT; SELECT avail available FROM books WHERE book_id book_id; IF avail 0 BEGIN RAISERROR(本书暂无可借数量, 16, 1); ROLLBACK TRANSACTION; RETURN; END; UPDATE books SET available available - 1 WHERE book_id book_id; INSERT INTO borrow (reader_id, book_id, borrow_date, due_date) VALUES (reader_id, book_id, CAST(GETDATE() AS DATE), DATEADD(DAY, 30, CAST(GETDATE() AS DATE))); COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; THROW; END CATCH END GO这段存储过程的逻辑值得反复看。SELECT avail available是检查库存小于等于零直接抛错回滚。更新库存和插入借阅记录放在同一事务里任何一步失败都能退回原状。RAISERROR和THROW的区别要记一下前者兼容旧版本后者是SQL Server 2012以后推荐的异常抛出写法错误信息能带中文直接显示在Java异常里答辩时演示“借已借完的书报错”很能说明问题。还书存储过程是逆操作把return_date写当前日期available加一。注意还书时不能盲目加一太简单的写法可能导致一本书还两次、库存虚高所以还书过程要校验return_date是否为空。这个细节写进存储过程比Java端判断更可靠。3. Java与SQL Server的连接驱动、连接串、最小可跑通的代码3.1 驱动选型mssql-jdbc版本和JDK的匹配关系Java连SQL Server官方驱动有两条路线。一条是Microsoft的mssql-jdbc从8.2到12.x版本还在更新另一条是jTDS老古董只支持到SQL Server 2008。课设用前者最稳的版本是12.2.0.jre8或11.2.0.jre8为什么强调jre8因为很多课设电脑装的是JDK 8用新版本驱动可能要求JDK 11以上你代码没写错启动却直接NoClassDefFoundError。捡麻烦最少的路走JDK 8配mssql-jdbc-12.2.0.jre8.jar。Maven项目在pom里加微软仓库依赖普通Eclipse课设直接下载jar丢进项目的lib目录Build Path添加进去即可代码完全一致。驱动加载的经典代码只有一行但面试和答辩常问Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver)是在干嘛简化说就是让驱动把自己注册到DriverManager里JDBC 4之后这行其实可以省略但写上能让老版本兼容性更好也方便评眼看懂。我习惯保留它代价可以忽略。3.2 连接串参数的三个坑实例名、TCP/IP协议、端口连接串是课设翻车重灾区常见写法String url jdbc:sqlserver://localhost:1433;databaseNameLibraryDB;encryptfalse;trustServerCertificatetrue;; String user sa; String password 你的密码; Connection conn DriverManager.getConnection(url, user, password);逐段说。localhost:1433是默认实例的地址端口如果你安装时选了命名实例比如SQLEXPRESS则要写成localhost\\SQLEXPRESS:1433反斜杠在Java字符串里要写成双反斜杠。这是SQL Server和MySQL最大的不同MySQL一个端口一台机SQL Server一台机可以跑多个实例每个实例独立端口。很多人没装默认实例装的是Express然后照着网上的localhost:1433改都不用改直接连接失败。encryptfalse;trustServerCertificatetrue这一段是最近几个驱动版本的必须项。SQL Server从2019开始驱动默认加密连接而课设机器自签名证书未必受信任不关加密就会报证书校验失败的SSL异常。这两个参数加上的顺序要放在databaseName后面用分号隔开。另外sa密码如果提示“已过期”多数是SQL Server密码策略强制90天更新右键实例改密码或执行ALTER LOGIN sa WITH PASSWORD新密码即可。还有个隐蔽问题SQL Server 2019安装时默认可能没开启TCP/IP协议连接串再对都白搭。检查方式是打开“SQL Server 配置管理器”找到实例的“客户端协议”和“网络配置”确保Named Pipes和TCP/IP都启用然后重启SQL Server服务。这一步不做网上的连接串抄一遍全报超时。这个配置不用改注册表图形界面三分钟收工。3.3 最小连通测试代码跑通再往后写功能写功能之前先写一个最简main方法做连通测试避免把连接错误和业务代码混在一起排查。以下代码是每个课设的第一道关卡import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class DBConnection { public static Connection getConnection() throws SQLException { String url jdbc:sqlserver://localhost\\SQLEXPRESS:1433;databaseNameLibraryDB;encryptfalse;trustServerCertificatetrue;; Properties props new Properties(); props.setProperty(user, sa); props.setProperty(password, 123456); props.setProperty(loginTimeout, 10); return DriverManager.getConnection(url, props); } public static void main(String[] args) { try (Connection conn getConnection()) { System.out.println(数据库连接成功: conn.getCatalog()); } catch (SQLException e) { e.printStackTrace(); } } }这个测试类的三个要点第一loginTimeout10写在Properties里连接超时最多等10秒而不是默认几十秒干瞪眼第二用try-with-resources把连接放在括号里语法结束后自动关闭课设代码不至于到处都是conn.close()还不一定执行得到第三打印conn.getCatalog()能直接验证当前连的是不是LibraryDB很多人的连接串没写错但手滑连到了master库后续SQL全报对象名无效。测试通过后其他类只需要调用DBConnection.getConnection()拿连接整个项目的高层代码不感知驱动和URL细节这是个好分层。Properties传用户名密码还有一种替代写法是把user/password直接拼进URL两种都对。我选Properties是因为密码里有特殊字符时URL容易写歧义比如密码带会被解析成分隔符。课设密码通常是123456这种简单密码两种写法都行但养成用Properties的习惯不亏。4. 核心功能落地登录、图书管理、借还书4.1 登录模块PreparedStatement是底线Statement的注入问题必须知道登录功能的SQL很多人这样写SELECT * FROM admin WHERE usernameadmin AND password123用Statement拼接字符串。课设系统演示没人攻击你但答辩被问一句“SQL注入怎么防”答不上来很扣分。PreparedStatement在防注入和SQL缓存两方面都有优势写起来也没多几行。以下是登录验证的标准写法public boolean login(String username, String password) { String sql SELECT password FROM admin WHERE username ?; try (Connection conn DBConnection.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getString(password).equals(password); } return false; } } catch (SQLException e) { e.printStackTrace(); return false; } }注意这里的细节SQL里只查password这一列不要SELECT *用户参数用?占位通过ps.setString()传入。PreparedStatement会把参数当普通字符串处理用户名里再带引号、分号都不会被拼接成新SQL。还有一点值得留意Java端应该比对的是哈希值而不是明文密码但课设一般没做到那一步。如果想让代码有记忆点可以在admin表存SHA-256哈希登录时先把输入的密码哈希再比对。这个升级成本低答辩却能多讲一分钟安全设计。4.2 借书还书Java调用存储过程事务放在数据库里上一章的存储过程在这里派上用场。Java端用CallableStatement调用它比在Java里手动管理事务更清晰。以下是借书方法的完整代码public String borrowBook(int readerId, int bookId) { String sql {CALL usp_borrow_book(?, ?)}; try (Connection conn DBConnection.getConnection(); CallableStatement cs conn.prepareCall(sql)) { cs.setInt(1, readerId); cs.setInt(2, bookId); cs.execute(); return 借书成功; } catch (SQLException e) { // 存储过程抛出的中文错误信息会进到异常消息里 return 借书失败: e.getMessage(); } }这段代码很短却把风险全堵住了。CALL语法是存储过程的标准调用格式两个?对应存储过程的两个入参出参和返回值在这里用不到就不声明。异常捕获为什么能拿到中文提示因为SQL Server的RAISERROR消息经驱动传到Java后会变成SQLException的message文本。我见过有的程序员收到异常后直接打印堆栈用户看到一堆at开头的东西这里返回用户可读的中文提示才像个能用的系统。还书方法与这个对称区别是SQL变成{CALL usp_return_book(?, ?)}存储过程里判断记录是否存在且未归还。Java端不必再写UPDATE books SET available available 1这种SQL数据库已经把细节管好了。存储过程中还要处理一个边界同一个人借同一本书两本可用数量不够时要报错。许多新手把“是否已借过”的判断放在Java里先查记录再执行这是典型的竞态窗口并发时两次查询都在更新前执行结果都认为可以借。放进存储过程后SELECT ... WITH (UPDLOCK)或直接靠事务锁能挡住这种情况课设不要求高并发但设计意识要有。4.3 图书查询和借阅列表分页与联表查询图书管理界面最常见的操作是书名搜索配分页列表。SQL Server分页有三种写法OFFSET...FETCH、ROW_NUMBER()窗口函数、TOP加子查询。SQL Server 2012以后OFFSET...FETCH是标准写法性能和可读性都好。以下代码是按书名模糊查询加分页public ListBook searchBooks(String keyword, int page, int size) { String sql SELECT b.book_id, b.title, b.author, c.category_name, b.available, b.total FROM books b JOIN categories c ON b.category_id c.category_id WHERE b.title LIKE ? ORDER BY b.book_id OFFSET ? ROWS FETCH NEXT ? ROWS ONLY; ListBook books new ArrayList(); try (Connection conn DBConnection.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, (page - 1) * size); ps.setInt(3, size); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { books.add(new Book( rs.getInt(book_id), rs.getString(title), rs.getString(author), rs.getString(category_name), rs.getInt(available), rs.getInt(total) )); } } } catch (SQLException e) { e.printStackTrace(); } return books; }这段代码值得拆开看两处。一是LIKE ?的占位符参数在Java端拼好%关键词%传入而不是SQL里写LIKE %?%——后者根本不会生效因为?是参数占位符不能嵌在引号里二是我把category_id通过JOIN换成了category_name这样前端直接显示分类名不用二次查询。分页的OFFSET计算有个边界问题page从1开始(page-1)*size才是跳过的行数。如果你直接把前端传的page数字传给OFFSET第一页数据会莫名其妙少几行。这个小问题我调试过一下午后来养成了前端传page后端减1的习惯。借阅列表是一张联表视图更合适避免每次查询都写三表JOINCREATE VIEW v_borrow_info AS SELECT b.borrow_id, r.name AS reader_name, b.book_id, bk.title, b.borrow_date, b.due_date, b.return_date FROM borrow b JOIN readers r ON b.reader_id r.reader_id JOIN books bk ON b.book_id bk.book_id; GO视图在课设里是加分项它让Java端查询变得像查单表一样简单SELECT * FROM v_borrow_info WHERE return_date IS NULL。视图不占存储空间只是一个虚拟表定义答辩时被问“什么是视图”也好解释。如果你的课设要求里还有“热门图书统计”基于视图再写一个GROUP BY查询就能出数据。5. 避坑课设里最常翻车的五个连接与数据问题5.1 现象连接超时或Access denied原因不在密码而在TCP/IP协议未启用我帮人排查的课设里十台有六台卡在这一步。控制台报错是The TCP/IP connection to the host has failedJava代码看起来和教程一模一样sa密码也对但就是连不上。打开SQL Server配置管理器一看TCP/IP协议状态是禁用状态而且协议列表里的“已启用”默认是“否”很多人安装时一路Next没改。解决步骤很固定SQL Server配置管理器 → SQL Server网络配置 → 实例名协议 → 右键TCP/IP → 启用 → SQL Server服务右键重启。重启后可以用telnet测试命令是telnet localhost 1433看到光标不消失基本就是通了。另外Windows防火墙也可能拦1433端口开发机能关防火墙就关关不了就加一条入站规则放行1433。这个坑和代码无关但一百篇博客里九十九篇没写让你误以为是自己Java写错了。5.2 现象报SSL证书错误原因在于驱动默认加密而证书不可信新版本jdbc驱动连接SQL Server时会自动加密连接默认验服务器证书。本机开发环境没有正确配置证书时报错常见关键词是SQLServerException: The driver could not establish a secure connection或PKIX path building failed。用旧教程抄来的连接串在旧版本驱动上能跑换了新驱动就打不开项目。解决方式就是连接串加两个参数encryptfalse;trustServerCertificatetrue。第一个关掉加密第二个即使启用了加密也信任自签名证书。课设环境两者加上即可。如果驱动版本太旧根本不认识这两个参数别硬调把驱动升级到11.x或12.x再跑。这是更新驱动带来的连锁问题不是SQL Server配置错了。5.3 现象sa密码登录总提示过期或登录失败原因在密码策略SQL Server有强制密码过期策略装完后sa密码默认有效期最多90天到期后所有应用都连不上日志里报“Login failed for user sa。原因必须更改密码”。另一个情况是安装时选了Windows身份验证模式sa账户根本没启用Java自然登不上。解决方法是先用Windows身份验证进SSMS执行以下SQLALTER LOGIN sa ENABLE; GO ALTER LOGIN sa WITH PASSWORD 123456; GO ALTER LOGIN sa WITH CHECK_POLICY OFF; GOCHECK_POLICY OFF是关掉密码复杂性策略和过期策略开发环境必要生产环境别学。如果嫌这条麻烦安装SQL Server时选“混合模式”并在下一步指定sa密码能省一次折腾。在很多网上的安装教程里默认推荐“Windows身份验证”Java连不上就卡在这了。5.4 现象事务日志暴涨原因在简单模式没开或删除操作太多课设项目开发期会反复建表、清数据、恢复数据库SQL Server默认的FULL恢复模式会不断累积事务日志现象是sql server writelog相关日志多、数据库日志文件膨胀到几个GB甚至把C盘塞满。开发环境永远不需要恢复数据库到分钟级改用简单模式即可ALTER DATABASE LibraryDB SET RECOVERY SIMPLE; GO DBCC SHRINKFILE (LibraryDB_log, 1);第一条把日志模式改为简单已经不自动截断的旧日志靠第二条DBCC SHRINKFILE收回空间。shrinkfile后面的1是目标文件大小1MB这个数字看你实际的日志文件大小来调整。另一层原因是循环测试借还书导致大量日志无论怎样简单模式一开writelog相关的磁盘空间问题基本消失。答辩时提一句“开发环境用简单恢复模式减少日志开销”评委知道这是懂行的。5.5 现象中文乱码或问号原因在JDBC连接未设置字符编码SQL Server的排序规则默认支持中文但Java读取时涉及驱动和数据库之间的字符转码。如果控制台打印中文显示成???或者写进数据库的中文变成乱码多半是连接串缺编码参数。JDBC连接串里加characterEncodingUTF-8不一定对SQL Server驱动生效SQL Server驱动默认按数据库排序规则处理常规做法是确保数据库排序规则是Chinese_PRC_CI_AS并且代码中使用setString正常传入。建库时可以在SQL里指定排序规则防患于未然CREATE DATABASE LibraryDB COLLATE Chinese_PRC_CI_AS; GO这条路比改连接串可靠。CI表示大小写不敏感AS表示区分重音。注意如果库已经建好再改排序规则比较麻烦直接删掉重建或迁移数据更省事。这个坑很多教程不提但中文系统下迟早碰到早设参数早安心。6. 验收前还能做的三件小事批处理、验证脚本与面试价值6.1 批量插入测试数据用一条Java方法让图书表有底气课设演示时图书表只有三五条数据评委问“能测一下分页吗”会出现第二页空白。我习惯在系统里埋一个“生成演示数据”的隐藏功能或者直接在main方法里跑批量插入。JDBC批处理有两种写法一种是用addBatch()和executeBatch()另一种简单粗暴是循环执行。批处理在大量数据时快很多因为减少了往返数据库的次数。生成500条图书数据的核心代码public void batchInsertBooks() { String sql INSERT INTO books (title, author, category_id, total, available) VALUES (?, ?, ?, ?, ?); try (Connection conn DBConnection.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 1; i 500; i) { ps.setString(1, 测试图书 i); ps.setString(2, 作者 (i % 20)); ps.setInt(3, (i % 5) 1); ps.setInt(4, 3); ps.setInt(5, 3); ps.addBatch(); if (i % 100 0) { ps.executeBatch(); } } ps.executeBatch(); } catch (SQLException e) { e.printStackTrace(); } }这段代码验证了批量操作和事务的配合关系。addBatch()把SQL暂存到驱动侧每100条调一次executeBatch()实际发送不至于500条挤在内存等最后一次性执行。批处理默认是非事务的其中一条失败前面成功的不会回滚不过课程设计数据是测试用失败也不影响。如果想让老师眼前一亮可以在前后加手动事务让500条要么全成、要么全不成这是“批处理和事务结合”的典型面试知识点。6.2 并发借书的验证脚本证明你的系统不会超借课设验收现场最怕被问“两个人同时借最后同一本书怎么办”。很多人答不上来但你的系统用存储过程做扣减理论上能防超借。验证方法也很直白写两个并发线程同时调用borrowBook(10001, 1)其中一本书total和available都是1。如果只有一次成功说明库存扣减被事务控制住了。可以用Java代码实现public static void main(String[] args) throws Exception { Runnable task () - { BorrowService svc new BorrowService(); System.out.println(借书结果: svc.borrowBook(10001, 1)); }; Thread t1 new Thread(task); Thread t2 new Thread(task); t1.start(); t2.start(); t1.join(); t2.join(); }注意这个测试结果不会每次都完美因为存储过程里用的是SELECT后判断再更新默认隔离级别下并发可能读到同一个旧库存值。要把这个测试跑稳存储过程里的查询要加锁提示SELECT avail available FROM books WITH (UPDLOCK) WHERE book_id book_id。UPDLOCK的作用是查询时就拿更新锁避免另一个事务读到相同快照。把这一行加进存储过程并发测试就能稳定输出“一个成功、一个失败”。这个细节是事务隔离级别和锁的一个典型应用课设里能讲明白就已经超出平均水平了。6.3 我把这个课设当成了面试敲门砖别小看一个简易图书馆管理系统。你把它做完之后会涉及JDBC连接管理、事务边界、批处理、分页查询、存储过程、数据库约束设计这些是Java后端面试的高频词。用spring boot mybatis重写一遍把JDBC这层换成框架剩下的表结构、存储过程和业务逻辑完全可以复用。如果简历上的项目是“基于Spring Boot和SQL Server的图书借阅系统”面试官至少会问SQL Server和MySQL的差异、Spring事务和数据库事务的关系、存储过程的优缺点、OFFSET分页在深页码时为什么慢。这些问题在你的课设里都有真实答案不是背来的。还有一个建议把存储过程脚本、建表SQL、Java代码按模块放好答辩时演示完功能现场跑一遍并发测试脚本。评委看到两个线程只有一个借书成功时通常不会再追问“你的系统有什么亮点”因为他们已经看到了。这个技巧是我从一个学长那学来的后来每次给学弟学妹看代码都让他们加这段测试代码效果比背PPT强十倍。如果你真打算靠这个项目在面试里说事建议把数据库从SQL Server换成MySQL再写一遍——很多公司内部用MySQL但SQL Server的事务、存储过程经验同样有价值。把两套数据库都摸一遍你对数据库连接、驱动、SQL语法的理解会比只用一个数据库的同学深一层。希望这些经验能帮你少走几步弯路让这套简易图书馆管理系统真正成为你的作品而不是负担。本文还有配套的精品资源点击获取