JavaWeb图书管理系统课程设计:从E-R图到借还事务的完整实现

📅 发布时间:2026/10/17 0:11:48
JavaWeb图书管理系统课程设计:从E-R图到借还事务的完整实现
简介这份资源是面向JavaWeb初学者与课程设计者的图书管理系统完整项目文档基于JavaWeb技术栈结合HTML、CSS与SQL Server数据库帮助读者理解信息管理系统的需求分析、功能设计与数据库建模全过程。压缩包内仅含1个docx文件大小约12.82MB以文档形式集中呈现源代码说明、文件目录截图与E-R关系图便于对照阅读与整理。系统涵盖登录、查询、借还、管理四个子系统并给出书籍与书库的存放关系、用户与书籍的借阅关系等概念设计以及关系模式转换思路适合作为数据库系统开发综合训练的参考案例。目前已有588人学习下载读者可借此掌握从需求分析到功能模块划分、E-R图绘制与表结构设计的完整流程快速搭建课程设计或毕业设计的文档框架。1. 从手工台账到自动化流转图书管理系统要解决的真实问题某高校图书馆的借阅台前管理员还在用纸质台账登记每一本借出的书读者想查一本书是否在馆得先翻卡片目录再跑到书库逐排找。这个场景在课程设计里被反复提起不是因为它古老而是因为它把问题暴露得足够清楚信息分散、状态不同步、超期靠人脑记。基于 JavaWeb 的图书管理系统要解决的正是这三件事——把书籍、读者、借阅记录收进同一套数据库让借还状态实时更新让超期规则由程序强制执行。这套方案适合正在做数据库系统开发综合训练的学生也适合需要一套可演示、可答辩、结构完整的 JavaWeb 课设的开发者。它不追求高并发或分布式而是把登录、查询、借还、管理四个子系统做扎实把 E-R 图到关系模式的推导过程讲清楚让读者能照着搭出自己的版本。2. 需求拆解与角色划分四个子系统如何各司其职2.1 管理员与普通用户的权限边界系统里有两类角色边界必须在需求阶段就划死否则后面写 Servlet 时会出现“这个页面到底谁能看”的反复拉扯。管理员拥有读者管理和图书管理的完整增删改查权限可以按读者账号查借阅数量按书号查书名、作者、价格。普通用户只能查书、借书、还书、看自己的借阅记录。这里有一个容易被忽略的约束当用户存在超出还书期限的未还书籍时系统必须拒绝新的借书请求。这个规则不是前端弹窗提醒而是要在借书业务逻辑里做数据库查询判断否则用户绕过页面直接调接口就能借走。通常的做法是在用户表里冗余一个“当前借阅数量”字段同时在借阅记录表里保留每本书的借出日期和应还日期。借书前先查该用户是否有超期记录有则直接返回失败并给出具体是哪本书超期。这样管理员在后台也能一眼看到谁被限制了。2.2 四个子系统的功能切分登录子系统负责身份验证和会话保持管理员和普通用户走同一个登录入口但根据角色跳转到不同的主页。查询子系统对用户开放按书号、书名、作者的模糊查询对管理员额外开放读者信息查询。借还子系统是核心业务借书时校验库存和超期状态还书时更新库存和借阅记录。管理子系统是管理员的后台包括图书的增删改查、读者的增删改查、借阅历史的查看。这四个子系统不是四个独立应用而是同一套 Web 工程里的不同 Servlet 和 JSP 页面。我一般会把它们放在不同的包下比如com.library.login、com.library.query、com.library.borrow、com.library.admin每个包里的 Servlet 只处理自己那部分请求共用同一个数据库连接工具类。这样后期改借书规则时不会误伤查询逻辑。2.3 功能层次图与模块职责功能层次图在课程设计文档里是必画项但画的时候容易变成一堆方框堆叠。更实用的画法是按“用户可见操作”来分层第一层是登录和注册第二层是查询、借阅、归还、我的图书馆第三层是管理员后台的图书管理、读者管理、借阅查询。每一层对应一组 JSP 页面和 Servlet 映射。比如“我的图书馆”页面它背后要查两张表借阅记录表和书籍表把当前用户未归还的书连同借书时间、应还日期一起列出来。这个查询在借还子系统里实现但页面入口放在用户主页。管理员后台的借阅查询则是另一套 SQL按读者账号或书号筛选显示所有历史记录。两者共用同一个借阅记录表但查询条件和返回字段不同不要为了省事写一个万能查询否则后期加筛选条件会越改越乱。3. 数据库概念设计从 E-R 图到关系模式的完整推导3.1 实体识别与属性确定概念设计阶段要先把实体和属性定下来。这个系统里有四个实体书籍、书库、普通用户、管理员。书籍的属性包括书号、书名、作者、价格、出版社书库的属性包括书库编号、书库地址普通用户的属性包括用户名、密码、性别、借书数量管理员的属性包括管理员名、密码。这里有一个设计决策借阅记录要不要作为独立实体在 E-R 图里借阅是用户和书籍之间的多对多联系联系本身可以带属性比如借书时间、应还日期、归还状态。但到了关系模式阶段多对多联系必须转成一张独立的表所以借阅记录表是必然存在的。同理书籍和书库之间是一对多联系一本书只能存放在一个书库一个书库可以放多本书这个联系在关系模式里可以通过在书籍表里加一个书库号外键来实现也可以单独建一张存放表。原文选择了单独建存放表并带上“数量”属性这样同一个书库可以存放同一本书的多个副本更贴近真实图书馆的场景。3.2 联系类型与 E-R 图绘制要点“书籍—书库”是 1:n 联系绘制时在书库端标 1在书籍端标 n联系名写“存放”。“用户—书籍”是 m:n 联系两端都标 n 和 m联系名写“借阅”。E-R 图里不要出现属性直接连属性的情况所有属性必须挂在实体或联系上。比如借书时间挂在“借阅”联系上而不是挂在用户或书籍上。画 E-R 图时容易翻车的地方是主码标注。书籍的主码是书号用户的主码是用户名书库的主码是书库号。借阅联系转成表后主码是用户名书号的组合同时用户名引用用户表书号引用书籍表两个都是外码。存放表的主码是书号书库号的组合书库号引用书库表书号引用书籍表。这些约束在画图时就要想清楚否则建表时会出现没有主键或者外键指向错误的情况。3.3 关系模式转换与表结构定义根据 E-R 图转换出五张表书籍书号书名作者价格出版社用户用户名性别借阅数量密码书库书库号书库地址借阅/归还用户名书号借书时间应还日期归还状态存放书号书库号数量原文的关系模式里借阅表只写了用户名书号实际开发中必须加上借书时间和应还日期否则无法判断超期。归还状态可以用一个字段表示比如 0 表示未还1 表示已还。存放表里的数量字段用来记录该库房该书的库存借书时减一还书时加一。建表时 SQL Server 的语法要注意自增列和主键的设置。书号如果是字符串类型就不能设自增需要手动输入或按规则生成。用户名的密码字段要留够长度如果做加密存储至少 64 位。下面是一段建表参考-- 书籍表 CREATE TABLE Book ( BookId VARCHAR(20) PRIMARY KEY, BookName NVARCHAR(100) NOT NULL, Author NVARCHAR(50), Price DECIMAL(10,2), Publisher NVARCHAR(100) ); -- 用户表 CREATE TABLE Users ( UserName VARCHAR(50) PRIMARY KEY, Gender CHAR(2), BorrowCount INT DEFAULT 0, Password VARCHAR(64) NOT NULL ); -- 借阅记录表 CREATE TABLE BorrowRecord ( UserName VARCHAR(50), BookId VARCHAR(20), BorrowDate DATETIME, DueDate DATETIME, ReturnStatus INT DEFAULT 0, PRIMARY KEY (UserName, BookId, BorrowDate), FOREIGN KEY (UserName) REFERENCES Users(UserName), FOREIGN KEY (BookId) REFERENCES Book(BookId) );借阅记录表的主键用了用户名书号借书时间的组合因为同一个用户可能多次借同一本书只用用户名和书号做主键会导致第二次借阅插不进去。归还状态默认 0还书时更新为 1。这个设计比原文的关系模式多了一个借书时间字段是为了支持历史记录查询和超期判断。4. 开发环境搭建与核心模块实现4.1 工具链选型与项目结构开发工具用 Eclipse数据库用 SQL Server 2012服务器用 Tomcat 7.0运行在 Windows 10 上。这套组合在课程设计里很常见版本不算新但胜在稳定资料多遇到问题容易搜到解决方案。项目结构按标准 JavaWeb 工程来src下放 Java 源码WebContent下放 JSP、CSS、JS 和WEB-INF/web.xml。图片资源需要自己找背景图、按钮图标这些不包含在源码里但 CSS 里引用的路径要提前规划好比如统一放在WebContent/images/下。数据库连接建议写一个工具类用 JDBC 直连不要在每个 Servlet 里重复写驱动加载和连接代码。连接字符串里注意 SQL Server 的端口和实例名如果用的是默认实例可以写成jdbc:sqlserver://localhost:1433;databaseNameLibraryDB。用户名和密码不要硬编码在 Java 文件里放到web.xml的context-param里或者用一个db.properties文件加载。4.2 登录与权限过滤器的实现登录子系统不只是比对用户名和密码还要在 Session 里存角色标识后续每个受保护的页面都要检查这个标识。我一般会写一个AuthFilter拦截/admin/*和/user/*路径根据 Session 里的角色决定放行还是跳回登录页。public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; HttpSession session request.getSession(false); String uri request.getRequestURI(); // 管理员页面检查 if (uri.contains(/admin/)) { if (session null || !admin.equals(session.getAttribute(role))) { response.sendRedirect(request.getContextPath() /login.jsp); return; } } // 普通用户页面检查 if (uri.contains(/user/)) { if (session null || !user.equals(session.getAttribute(role))) { response.sendRedirect(request.getContextPath() /login.jsp); return; } } chain.doFilter(req, res); } }过滤器的逻辑说明先拿到当前请求的 URI判断它属于管理员区域还是用户区域然后检查 Session 里有没有对应的角色。如果没登录或者角色不对直接重定向到登录页。这里用request.getSession(false)是为了避免自动创建新 Session减少无效会话。web.xml里配置过滤器的url-pattern为/admin/*和/user/*这样静态资源不会被拦截。4.3 借书与还书的事务处理借书操作涉及三张表的更新借阅记录表插入一条新记录存放表对应库存减一用户表借阅数量加一。这三步必须在一个事务里完成否则可能出现库存减了但借阅记录没插进去的情况。JDBC 默认是自动提交需要手动关掉。Connection conn null; PreparedStatement ps1 null, ps2 null, ps3 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 检查是否有超期未还 String checkSql SELECT COUNT(*) FROM BorrowRecord WHERE UserName? AND ReturnStatus0 AND DueDate GETDATE(); ps1 conn.prepareStatement(checkSql); ps1.setString(1, userName); ResultSet rs ps1.executeQuery(); if (rs.next() rs.getInt(1) 0) { conn.rollback(); return 有超期未还书籍无法借阅; } // 2. 插入借阅记录 String borrowSql INSERT INTO BorrowRecord(UserName, BookId, BorrowDate, DueDate, ReturnStatus) VALUES(?,?,GETDATE(),DATEADD(DAY,30,GETDATE()),0); ps2 conn.prepareStatement(borrowSql); ps2.setString(1, userName); ps2.setString(2, bookId); ps2.executeUpdate(); // 3. 更新库存和借阅数量 String updateSql UPDATE Storage SET QuantityQuantity-1 WHERE BookId? AND Quantity0; ps3 conn.prepareStatement(updateSql); ps3.setString(1, bookId); int affected ps3.executeUpdate(); if (affected 0) { conn.rollback(); return 库存不足; } conn.commit(); return 借阅成功; } catch (Exception e) { if (conn ! null) conn.rollback(); e.printStackTrace(); return 系统异常; } finally { DBUtil.close(conn, ps1, ps2, ps3); }这段代码的关键点先查超期再插记录最后扣库存。扣库存时加了Quantity0条件防止库存扣成负数。还书操作反过来更新借阅记录的归还状态为 1库存加一用户借阅数量减一。还书不需要检查超期但需要判断这条借阅记录是否存在且未归还。4.4 查询子系统的模糊查询与分页用户查书时输入书名的一部分也能搜到这需要 SQL 的LIKE配合%。管理员查读者时按账号精确查询即可。查询结果如果很多要做分页否则一个页面列出几百条记录浏览器渲染会卡。分页的 SQL Server 写法用OFFSET FETCH比如每页 10 条第 2 页的查询是ORDER BY BookId OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY。前端传页码和每页条数后端拼 SQL 时注意参数化不要把页码直接拼进字符串防止 SQL 注入。模糊查询的参数用%拼接后传给PreparedStatement比如ps.setString(1, % keyword %)。5. 避坑与常见问题排查5.1 中文乱码从请求到响应逐层排查现象JSP 页面输入中文书名提交后数据库里存的是问号或者页面显示乱码。原因通常有三层JSP 页面编码、请求编码、数据库编码。解决顺序是先在 JSP 顶部加% page contentTypetext/html;charsetUTF-8 %再在 Servlet 里request.setCharacterEncoding(UTF-8)最后检查 SQL Server 的排序规则是否支持中文建库时选Chinese_PRC_CI_AS。Tomcat 的server.xml里 Connector 加URIEncodingUTF-8也能解决 GET 请求的乱码。5.2 数据库连接泄漏导致 Tomcat 启动后越来越慢现象系统跑一段时间后页面响应变慢最后报“Too many connections”。原因是在 Servlet 里拿了 Connection 但没有在 finally 里关闭或者关闭顺序不对。解决方法是统一用工具类关闭先关 ResultSet再关 PreparedStatement最后关 Connection每个关闭操作单独 try-catch避免一个异常导致后面的关不掉。如果用了连接池要在context.xml里配置maxActive和maxIdle并确保每次用完归还而不是关闭。5.3 超期判断的日期边界与数据库时区现象用户明明在应还日期当天还书系统却判定超期。原因是 SQL 里用了DueDate GETDATE()而GETDATE()包含时分秒应还日期如果是当天 00:00:00到当天下午还书时已经小于当前时间。解决方法是把应还日期存成当天的 23:59:59或者判断时用CONVERT(DATE, DueDate) CONVERT(DATE, GETDATE())只比较日期部分。借书时设置应还日期用DATEADD(DAY, 30, GETDATE())还书判断时统一转成日期比较。5.4 库存扣减的并发问题现象两个用户同时借同一本书库存显示还有 1 本但两个人都借成功了库存变成 -1。原因是先查库存再扣减两步之间没有锁。解决方法是在 UPDATE 语句里直接加Quantity0条件用executeUpdate()的返回值判断是否扣减成功。如果返回 0说明库存已经被别人扣完了当前事务回滚。这个做法比在 Java 里加 synchronized 更可靠因为锁的范围只在数据库层面不依赖应用服务器的单实例。5.5 JSP 页面引入 CSS 和图片路径错误现象登录页样式全丢背景图不显示。原因是 JSP 被 Servlet 转发后相对路径的基准变了。解决方法是在 JSP 里用%request.getContextPath()%拼绝对路径比如link relstylesheet href%request.getContextPath()%/css/style.css。图片资源需要自己找放在WebContent/images/下CSS 里引用图片也用相对路径但要注意 CSS 文件本身的位置如果 CSS 在css/目录下引用../images/bg.jpg才能找到。6. 验收与答辩怎么证明这套系统真的能跑验收不是把项目跑起来点几下就完事答辩老师通常会盯着几个关键点问超期规则怎么实现的、事务有没有回滚、E-R 图到表结构的推导是否一致。我的习惯是提前准备一份验收清单按功能模块逐条过每条都留下可复现的操作路径和预期结果。验收项操作路径预期结果检查点登录鉴权未登录直接访问 /admin/bookList跳转登录页Session 为空时过滤器生效超期拦截用户有超期书尝试借新书提示“有超期未还书籍”借书事务回滚库存不变库存扣减两个浏览器同时借同一本书只有一个成功UPDATE 返回值为 0 时回滚还书更新点击还书后查库存库存加一借阅记录状态变 1事务提交后数据一致模糊查询输入书名部分关键字返回匹配列表LIKE 参数化无 SQL 注入分页图书列表超过 10 条显示分页导航OFFSET FETCH 语法正确答辩时如果被问到“为什么用 SQL Server 而不是 MySQL”可以从课程要求或工具链熟悉度回答不要贬低另一种数据库。如果被问到“系统有什么不足”可以说没有做密码加密存储、没有做并发压力测试、前端没有用框架这些都是可以后续改进的点但当前版本已经覆盖了需求分析里的全部功能。我一般会在答辩前把整个流程走三遍第一遍用管理员账号走增删改查第二遍用普通用户走借书还书和超期拦截第三遍故意制造异常比如借一本库存为 0 的书看系统是否优雅报错而不是抛 500 页面。从那以后我每次交课程设计都强制走一遍这个清单避免在演示时翻车。希望帮到你。本文还有配套的精品资源点击获取