简介:基于Java Web的图书管理系统的设计与实现.docx是一份完整的毕业设计/课程设计论文文档,面向互联网及计算机相关专业学生、Java Web初级开发者,围绕学校图书管理中的基本问题与效率提升需求,给出了从需求分析、系统设计到具体实现的全流程方案。资源为单个docx文档,压缩包大小约2.34MB,便于直接阅读与编辑;目前已有385人学习/下载。文档内容较为完整,覆盖系统设置、读者管理、图书管理、图书借还、系统查询、更改口令六大功能模块,并详细设计了图书信息表、读者类型表、借阅记录表等数据库结构。技术实现上采用JSP页面结合MVC设计模式与Struts框架,通过JDBC连接SQL Server数据库,对模块接口、业务流程和关键代码均有说明。对于需要完成类似课题或学习传统Java Web分层开发的读者,可从中获取系统设计思路、模块划分方法及数据库建模参考。
1. 一套能跑起来的Java Web图书管理系统:它到底是什么
去年帮一所高职院校的图书馆做信息化改造调研,发现他们还在用Excel记录借还书,一个管理员每天光录入就要花两小时。翻遍各个毕业设计资源库,这类“基于Java Web的图书管理系统”下载量一直居高不下,但能真正部署起来、改得动二次开发的并不多。这份资源就是一套标准的JSP+Struts+Servlet+JDBC实现的图书管理系统,覆盖管理员、读者两类角色,包含系统设置、读者管理、图书管理、图书借还、系统查询和更改口令六个模块。它最大的价值不是演示代码,而是把借书、还书、续借、超期、读者分类、图书分类这条完整闭环以传统Java Web技术栈落地了。适合需要交毕设、准备Java Web面试复习、以及想把老项目跑起来做改造的从业者。下面从架构、数据库、核心代码到部署踩坑,一条条拆开讲。
2. 技术选型与架构:为什么是Struts+JSP而不是Spring Boot
2.1 三层结构在图书管理系统里的落地方式
这套系统采用典型的B/S三层结构:浏览器发起请求,Web容器(Tomcat)接收,Struts的ActionServlet做中央控制器,把请求分发给对应的Action类处理业务逻辑,最后通过JSP渲染页面返回给用户。它的数据流大致是:JSP表单提交 → struts-config.xml中找到对应Action映射 → Action调用DAO操作数据库 → 返回ActionForward跳转到下一个JSP页面。
在浏览器端,JSP负责展示和收集数据,用<jsp:useBean>把表单字段封装进JavaBean;Struts的ActionForm承担参数校验和临时存储;真正的业务逻辑放在Action里。这种分层有个直接好处:页面设计师可以只改JSP,不影响Java代码;数据库结构变化时,只需要调整DAO层和对应的SQL。
以登录模块为例,运行时的调用关系是这样的:
// struts-config.xml 中的关键映射配置 <action path="/login" type="com.bbs.action.LoginAction" name="loginForm" scope="request"> <forward name="success" path="/main.jsp"/> <forward name="failed" path="/error.jsp"/> </action>参数说明:path是浏览器访问的URL路径;type是对应的Action类全限定名;name指定ActionForm的名字,请求参数会自动填入该JavaBean;forward定义跳转逻辑名,Action返回ActionForward时按名字找到实际JSP地址。这套配置意味着新增一个功能模块,最少的改动就是加一个<action>节点和对应的Java类,不用动其他页面。
Struts 1.x虽然在今天看来框架笨重、配置繁琐,但它强制把控制器、表单和行为分离,对学习MVC原理和JSP/Servlet底层机制非常合适。很多公司的老系统至今还在跑Struts架构,面试问“过滤器、拦截器、MVC流程”都能从这套代码里找到直观答案。
2.2 JDBC连接SQL Server:从driver到连接池的取舍
系统通过JDBC与数据库保持“无缝连接”。资源里的数据库连接代码集中在DBConnection类,核心逻辑是加载驱动、获取连接、执行SQL、关闭资源。
public class DBConnection { private static final String DRIVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; private static final String URL = "jdbc:sqlserver://localhost:1433;DatabaseName=bookdb"; private static final String USER = "sa"; private static final String PASSWORD = "123456"; public static Connection getConnection() { Connection conn = null; try { Class.forName(DRIVER); conn = DriverManager.getConnection(URL, USER, PASSWORD); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException e) {} try { if (stmt != null) stmt.close(); } catch (SQLException e) {} try { if (conn != null) conn.close(); } catch (SQLException e) {} } }逻辑说明:Class.forName(DRIVER)在Java 6之后其实可以省略,但保留它的好处是明确告诉JVM加载哪个驱动类,避免多个数据库驱动共存时选错。DriverManager.getConnection每次调用都会创建一个物理连接,这在并发量小的毕设场景里完全够用;但生产环境要换成commons-dbcp或c3p0连接池,否则压力一大就会出现“too many connections”报错。
参数说明:DatabaseName=bookdb对应你要连接的库名,sa是SQL Server默认超级管理员账号。SQL Server 2005以前用com.microsoft.jdbc.sqlserver.SQLServerDriver,2008以后必须换成com.microsoft.sqlserver.jdbc.SQLServerDriver,这个坑在后面避坑章节会细讲。
2.3 这套选型在2024年的现实定位
不用拿Struts和Spring Boot硬比。这套系统的意义在于:你拿到手的是一套“裸奔”的传统Java Web代码,没有注解、没有自动装配、没有starter依赖,所有对象创建和数据流转都看得见摸得着。对于一个想搞清楚“请求从点击按钮到返回页面对应到哪行代码”的初学者,这套代码比Spring Boot的黑匣子友好得多。我在帮人梳理老系统的时候,经常是先让新人读一遍这套Struts代码,再去碰Spring系列框架就顺了。技术栈SQL Server和JSP的组合还有一个好处:和很多学校机房的实验环境一致,照着资源里的数据库脚本建库建表,不需要额外装MySQL。当然,如果你的本机只有MySQL,改一下DRIVER和URL即可,后面第6章会给迁移脚本。
3. 数据库设计:八张表把借阅闭环讲清楚
3.1 从E-R图到物理表的设计思路
打开资源的数据库设计章节,能看到完整的E-R图和数据库设计说明书。它把现实世界的借阅行为拆成了两个关键事实表和一个状态流转:读者和图书是多对多关系,借阅行为被拆成tb_borrow(借出信息)和tb_giveback(归还信息)两张表,中间通过readerID + bookID关联。为什么拆两张表?因为归还记录需要保存历史,不能像有些粗糙系统那样只在借阅表上改一个status字段。拆开后可以计算逾期时长、统计图书借阅频率、分析读者画像,这些都是后面统计查询模块的数据来源。
图书馆参数(办证费用、有效期限)单独建一张tb_library表,因为这类配置数据变化周期长、被高频读取,和业务数据放一起会增加查询负担。
3.2 核心表结构与字段约束
资源里给出了完整的建表SQL和字段说明。以借阅表和图书表为例,整理成下面这样可以直接对照建表:
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| tb_borrow.ID | int | 主键自增 | 借阅流水号 |
| tb_borrow.readerID | int | 非空 | 关联读者ID |
| tb_borrow.bookID | int | 非空 | 关联图书ID |
| tb_borrow.borrowTime | datetime | 非空 | 借出时间 |
| tb_borrow.backTime | datetime | 非空 | 应还时间(用于超期判断) |
| tb_borrow.ifback | tinyint | 默认0 | 0未还 / 1已还 |
| tb_bookinfo.ID | int | 主键自增 | 图书编号 |
| tb_bookinfo.bookname | varchar(50) | 非空 | 书名 |
| tb_bookinfo.booktype | int | 外键 | 关联图书类型 |
| tb_bookinfo.isbn | varchar(30) | 唯一 | ISBN号 |
| tb_bookinfo.bookcase | varchar(20) | 非空 | 所在书架 |
特别注意ifback字段:这是控制借阅状态的核心开关。还书操作实际上是一个事务里做两件事:往tb_giveback插一条归还记录,同时把tb_borrow对应行的ifback置为1。缺少事务控制的话,可能出现“归还表有记录但借阅表还是未还”的数据不一致,后面实现章节会给出标准写法。
3.3 表关联与查询路径
整套系统的查询逻辑都是围绕这层关联关系展开的:
-- 查询某本书当前是否可借:join借阅表 + 过滤未归还记录 SELECT b.bookname, b.isbn, br.readerID, br.borrowTime, br.backTime FROM tb_bookinfo b LEFT JOIN tb_borrow br ON b.ID = br.bookID AND br.ifback = 0 WHERE b.ID = ?写这条SQL的目的是展示一个高频踩坑点:如果写成JOIN tb_borrow br ON b.ID = br.bookID而不加ifback = 0条件,一本来回借了五次的书会出现五条历史借阅记录,导致“可借”状态误判。正确做法是左连接后只保留未归还的记录,如果查出来是NULL就说明当前可借。
还有一个容易忽略的表是tb_readertype。读者一次能借几本、能借几天,是由tb_booktype的可借阅天数和tb_readertype的可借本数联合决定的。借书Action里必须同时校验这两个值,不然会出现“读者类型允许借5本,但系统只校验了图书类型可借30天,没校验数量上限”的逻辑漏洞。
4. 五个核心模块的实现:从页面到SQL的完整链路
4.1 登录与系统设置:权限控制的起点
登录模块的Action从ActionForm里取出用户名密码后,交给UserDAO查询tb_user表。这里做了一个符合Struts惯例的处理:登录成功后把用户对象放进session,后续每个页面通过检查session里有没有该对象来决定是否放行。
public class LoginAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { LoginForm loginForm = (LoginForm) form; String username = loginForm.getUsername(); String password = loginForm.getPassword(); UserDAO dao = new UserDAO(); User user = dao.findUser(username, MD5Util.encode(password)); if (user != null) { HttpSession session = request.getSession(); session.setAttribute("user", user); session.setAttribute("role", user.getRole()); return mapping.findForward("success"); } else { request.setAttribute("errorMsg", "用户名或密码错误"); return mapping.findForward("failed"); } } }逻辑说明:MD5Util.encode是对tb_user表中存密码做的一次单向加密再比对。这里的密码字段在原始表结构里就是varchar明文,强烈建议照这个写法加上MD5处理,至少不能让密码直接裸在数据库里和页面上回显。findUser返回null的情况不止是“查无此人”,也可能是“密码不匹配”,提示信息统一用“用户名或密码错误”,避免暴露账号是否存在。
参数说明:登录成功后mapping.findForward返回逻辑名,由struts-config.xml映射到main.jsp或error.jsp。如果你的项目用了中文页面,注意JSP头部必须有<%@ page contentType="text/html; charset=GBK" %>这种编码声明,否则session里的中文名称会在页面上乱码。
4.2 图书管理:从入库到注销的完整操作
图书管理模块涉及tb_bookinfo和tb_booktype两张表的联调。页面端展示图书列表时,一次简单的内联查询即可;但新增和修改图书时,必须同时处理图书类型下拉框的联动。常见做法是这样:
public List<Book> findAllBooks() { List<Book> list = new ArrayList<Book>(); Connection conn = DBConnection.getConnection(); String sql = "SELECT b.ID, b.bookname, b.isbn, b.price, b.press, t.typename, t.days " + "FROM tb_bookinfo b LEFT JOIN tb_booktype t ON b.booktype = t.ID"; PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while (rs.next()) { Book book = new Book(); book.setId(rs.getInt("ID")); book.setBookname(rs.getString("bookname")); book.setIsbn(rs.getString("isbn")); book.setTypename(rs.getString("typename")); book.setDays(rs.getInt("days")); list.add(book); } DBConnection.close(conn, ps, rs); return list; }逻辑说明:这里用LEFT JOIN而不是INNER JOIN,目的是保证即使某本书的type在类型表里被误删,图书列表依然能正常加载,只是typename为null,不影响列表主流程。图书新增、删除、分类管理和图书类型管理都在这个模块内,删除时还需要考虑外键约束——如果该书有未归还的借阅记录,删除会抛外键异常,必须前置判断。
4.3 图书借还与续借:事务与状态联动
这是整套系统最核心、也是并发出错概率最高的模块。借书和还书都必须保证一组数据库操作全部成功或全部失败。借书时的操作路径是:检查读者可借数量、检查图书当前是否已被借出、插入借阅记录、更新读者已借数量。单个步骤失败必须回滚。
public boolean borrowBook(int readerId, int bookId) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBConnection.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 查读者当前未还本数 String countSql = "SELECT COUNT(*) FROM tb_borrow WHERE readerID=? AND ifback=0"; ps = conn.prepareStatement(countSql); ps.setInt(1, readerId); rs = ps.executeQuery(); rs.next(); int curCount = rs.getInt(1); // 2. 查读者类型允许的本数 String typeSql = "SELECT rt.number FROM tb_readertype rt " + "JOIN tb_reader r ON r.readertype = rt.ID WHERE r.ID=?"; ps = conn.prepareStatement(typeSql); ps.setInt(1, readerId); rs = ps.executeQuery(); int allowed = 0; if (rs.next()) allowed = rs.getInt("number"); if (curCount >= allowed) return false; // 超出可借数量 // 3. 插借阅记录,借出时间now,应还时间=now+30天 String insertSql = "INSERT INTO tb_borrow(readerID, bookID, borrowTime, backTime, ifback) " + "VALUES(?, ?, GETDATE(), DATEADD(day, 30, GETDATE()), 0)"; ps = conn.prepareStatement(insertSql); ps.setInt(1, readerId); ps.setInt(2, bookId); ps.executeUpdate(); conn.commit(); // 提交事务 return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } finally { try { conn.setAutoCommit(true); } catch (SQLException e) {} DBConnection.close(conn, ps, rs); } }逻辑说明:conn.setAutoCommit(false)告诉JDBC不要自动提交每条SQL,而是等到所有步骤成功后再调用commit()一次性提交。如果第3步插入失败,catch块里执行rollback(),把第1步和第2步的影响全部撤销。这个写法能防止“扣了读者额度但没生成借阅记录”这种数据错乱。
参数说明:30天是硬编码的。如果要让图书类型表tb_booktype.days起真实作用,第3步的SQL要改成DATEADD(day, (SELECT days FROM tb_booktype WHERE ID=(SELECT booktype FROM tb_bookinfo WHERE ID=?)), GETDATE()),把应还时间的计算改成动态取值。这是一个技术债务点,资源里的代码用了硬编码,建议二次开发时改掉。
还书模块的逻辑方向相反:插入tb_giveback归还记录、把tb_borrow对应记录的ifback置1、根据backTime和当前日期判断是否逾期并计算罚款。罚款计算规则在tb_library参数表里维护,开发时把“每本书每天罚款金额”做成一个字段即可。
4.4 系统查询:多条件组合与模糊匹配
查询模块支持按书名、作者、出版社、ISBN等多种维度模糊匹配。最需要注意的地方是SQL拼接时的空白和空格处理。
SELECT * FROM tb_bookinfo WHERE 1 = 1 <if test="bookname != null and bookname != ''"> AND bookname LIKE '%' + ? + '%' </if> <if test="author != null and author != ''"> AND author LIKE ? + '%' </if> <if test="press != null and press != ''"> AND press = ? </if>逻辑说明:WHERE 1 = 1不是性能问题,而是为了后续条件拼接时不需要判断“是不是第一个条件”,省去拼AND/WHERE的分支逻辑。各字段用LIKE实现模糊匹配,配合PreparedStatement的?占位符,避免SQL注入。如果你的实测环境中作者名精确匹配反而更准,把LIKE ? + '%'改成?即可。
查询模块的页面布局是单页多条件框:书名一个输入框、作者一个输入框、出版社一个下拉框,点击查询后请求同一个Action。这个模块也是读者角色唯一能用到的查询功能,所以权限控制上该放行读者,不要误配成“仅管理员可查”。
4.5 更改口令:账号安全的最后一道防线
修改密码的Action处理流程相对简单:从session取当前用户ID,比对旧密码,通过后执行UPDATE tb_user SET password=? WHERE ID=?。这里需要做两处防御,一是输入校验(旧密码不能为空、新密码不能和旧密码相同),二是防止新密码在HTTP传参过程中被中间人抓包看到明文。毕设场景下用MD5已经比明文强得多,但如果条件允许,建议升级为加盐MD5或直接切换到BCrypt。
5. 避坑指南:从乱码到驱动,六条反复出现的血泪经验
5.1 数据库驱动类找不到:ClassNotFoundException
现象:Class.forName(DRIVER)抛ClassNotFoundException,程序一启动就红。解决的标配是:先把sqljdbc4.jar或sqljdbc41.jar拷到WEB-INF/lib目录下,而不是只放在Tomcat的lib目录。如果你用的是Maven构建,要注意com.microsoft.sqlserver:sqlserverjdbc4:4.0只在Oracle的Maven仓库有,推荐手动依赖本地jar包的方式,不要用system scope那套配置,换电脑就废。
5.2 SQL Server端口连不上:TCP/IP协议被禁用
现象:Connection refused: connect,1433端口不通。这是SQL Server安装时的“默认陷阱”:安装时没启用TCP/IP协议,或者启用了但没重启服务。解决路径:打开SQL Server Configuration Manager → SQL Server网络配置 → 启用TCP/IP → 右键重启SQL Server服务。顺带确认Windows防火墙是否放行1433端口。
5.3 GET请求中文乱码
现象:页面上输入“红楼梦”,数据库里查出来的结果全变成“??????”或者直接查不到。原因链条是:JSP页面用GBK编码提交,Tomcat默认按ISO-8859-1解码GET请求。解决写法有两个,优先改JSP:
<%@ page contentType="text/html; charset=GBK" pageEncoding="GBK" %>更稳妥的是在项目里加一个CharacterEncodingFilter过滤器,统一设置请求和响应的编码。注意POST请求还要额外写request.setCharacterEncoding("GBK")。
5.4 SQL Server驱动在JDK 8下的兼容问题
现象:在JDK 8 + SQL Server 2012组合下,Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver")能加载,但创建连接时报UnsupportedClassVersionError。这个是驱动版本和JDK不匹配。JDK 8必须用4.1以上版本的驱动,JDK 7用4.0,老驱动3.0连不上新数据库。用户在下载驱动的网站上看到的一堆版本就是这里派上用场的。
5.5 外键约束导致删除“死锁”
现象:删除一本在借的图书时,SQL抛外键约束错误。原因是tb_bookinfo和tb_borrow有外键关系,如果该书存在未归还的借阅记录,删除图书必然违反外键。惯用解决是:删除前先执行SELECT COUNT(*) FROM tb_borrow WHERE bookID=? AND ifback=0,返回值大于0就提示“该书存在未归还记录,不能删除”,不要试图用ON DELETE CASCADE,那会让历史借阅记录一并消失,还不上书了。
5.6 Struts 1.x的Action线程共享实例
现象:多位读者同时登录,偶尔出现A读者的数据跑到B读者的页面上。原因:Struts 1.x的Action是单例的,所有请求共享同一个实例。你在Action里声明成员变量接收参数,A请求先来赋值,B请求再来覆盖,A页面渲染时读到的就是B的数据。标准解法是:Action里一律不定义可被修改的成员变量,所有状态放进局部变量和ActionForm,后台一次只处理一个请求,不做全局缓存。
6. 部署上线与二次开发:从WAR包到MySQL迁移的落地技巧
6.1 打包部署的完整路径
资源文件夹里拿到的是源码工程,部署前需要先在Eclipse或IDEA里导出WAR包。推荐路径:右键项目 → Export → WAR file → 勾选“Export source files”。把生成的BookSystem.war拷到Tomcat/webapps目录后,启动Tomcat自动解压,浏览器访问http://localhost:8080/BookSystem即可进入登录页。
如果访问报404,先检查WEB-INF/web.xml里的welcome-file配置,确保默认首页指向login.jsp或doLogin.action,不要默认指向一个不存在的路径。Tomcat的server.xml里如果配置了URIEncoding="UTF-8",GET请求乱码的概率会大幅下降,这是我每次部署必调的一项。
6.2 从SQL Server迁移到MySQL的改动清单
很多开发者的本机并没有SQL Server,安装又重,直接跑MySQL环境更省事。迁移时需要改动的文件集中在三处:
-- SQL Server写法 SELECT GETDATE(); SELECT DATEADD(day, 30, GETDATE()); -- MySQL写法 SELECT NOW(); SELECT DATE_ADD(NOW(), INTERVAL 30 DAY);驱动类从com.microsoft.sqlserver.jdbc.SQLServerDriver改成com.mysql.jdbc.Driver,URL从jdbc:sqlserver://localhost:1433;DatabaseName=bookdb改成jdbc:mysql://localhost:3306/bookdb?useUnicode=true&characterEncoding=GBK。自增主键语法从IDENTITY(1,1)改成AUTO_INCREMENT,分页语句从TOP n改成LIMIT n。整个过程大约半小时,改完记得测试一遍借还书流程。
6.3 给二次开发的一个起点:把借阅天数改成动态配置
前面第4章提到借书Action里硬编码了30天。可以这样改造:在tb_booktype表加一个days字段,借书时动态查询该字段并写入tb_borrow.backTime。“图书馆管理员想调整热门畅销书的可借天数时,不用改代码,直接在页面上改参数。”我第一次给学校机房部署这套系统时,就是按这个思路先改掉了硬编码,后面管理员维护借阅期限的一个需求就直接复用了这个逻辑。
从那以后,我每次接手这种老牌Java Web项目,都强制自己先走一遍“找遍所有硬编码参数”的流程——日期、分页条数、每类读者可借本数、罚款金额,全部挪到配置表或WEB-INF下的properties文件里。这套图书管理系统虽然整体风格偏老,但作为了解JSP+Struts+JDBC时代整体形态的完整样本,用来跑通流程、补全知识点、做二次开发脚手架,都有实实在在的价值。希望帮到你。
本文还有配套的精品资源,点击获取