简介:这份资源是一份基于JSP的图书管理系统毕业设计文档,面向计算机相关专业学生及需要完成课程设计或毕设的开发初学者。文档围绕B/S架构展开,讲解如何用JSP结合HTML与Java实现动态网页,并借助request、response、session等内置对象处理请求与会话,后端采用MySQL存储图书信息、用户资料与借阅记录,同时按管理员、教师、读者三类角色划分权限,涵盖图书查询、借阅归还、分类检索、逾期提醒与预约等功能模块。资源包共1个docx文件,大小约438KB,内容包含摘要、目录、开发环境与技术介绍等章节,结构完整,可直接作为选题参考与论文写作模板。目前已有284人学习下载,适合需要系统梳理设计思路、搭建功能框架并快速成稿的读者借鉴使用。
1. 基于 JSP 的图书管理系统:从课程设计到能跑通的最小闭环
如果你正在搜「基于jsp的图书管理系统设计与实现」,大概率不是想听 JSP 是什么,而是想知道:这东西到底能不能在本地跑起来、数据库怎么连、增删改查怎么落、答辩时老师会问什么。我见过太多人卡在第一步——Tomcat 版本对不上、JDBC 驱动没放对目录、中文乱码查了一下午。这篇笔记按「能复现」的标准来写,不堆概念,每一步都落到具体文件和命令上。适合两类人:一是课程设计/毕设需要交一个能演示的系统,二是刚接触 JSP 想拿一个完整项目练手。读完你应该能自己搭出一个带登录、图书增删改查、借阅记录的最小可用版本,并且知道哪些地方最容易翻车。
2. 技术选型与开发环境:为什么这套组合还在被用
2.1 JSP + Servlet + MySQL 的分工边界
很多人一上来就写页面,结果业务逻辑和展示混在一起,改一个字段要翻三个文件。JSP 这套技术栈的核心分工其实很清晰:JSP 负责渲染 HTML,Servlet 负责接收请求和调度,JavaBean/DAO 负责数据封装和数据库操作,MySQL 负责持久化。这个分层不是学院派洁癖,而是让你在调试时能快速定位问题——页面显示不对就看 JSP,请求参数收不到就看 Servlet,数据对不上就看 DAO 的 SQL。
具体到图书管理系统,典型的数据流是这样的:浏览器请求/bookList,Servlet 调用BookDAO.findAll(),DAO 通过 JDBC 查 MySQL 返回List<Book>,Servlet 把 list 塞进 request 作用域,转发到bookList.jsp,JSP 用 JSTL 遍历渲染表格。这条链路里任何一环断了,你都能通过日志或断点定位到具体位置。
提示:不要用 JSP 直接写 JDBC 代码。我见过把
Class.forName写在 JSP 脚本片段里的,页面一刷新就重新加载驱动,连接池直接爆掉。
2.2 环境版本搭配与目录结构
版本兼容是第一个大坑。Tomcat 9 对应 Servlet 4.0,Tomcat 10 开始包名从javax.servlet变成jakarta.servlet,如果你从网上抄的代码用的是javax,放到 Tomcat 10 里直接 404 或 ClassNotFound。稳妥组合是:JDK 8 或 11、Tomcat 9、MySQL 5.7 或 8.0、mysql-connector-java 8.0.x。
项目目录按 Maven 标准结构组织,不要用 Eclipse 默认的 WebContent 乱放:
src/main/java/com/library/ dao/ BookDAO.java, UserDAO.java entity/ Book.java, User.java servlet/ BookServlet.java, LoginServlet.java util/ DBUtil.java src/main/webapp/ WEB-INF/web.xml WEB-INF/lib/mysql-connector-java-8.0.28.jar bookList.jsp, bookEdit.jsp, login.jspWEB-INF/lib下的 jar 包是运行时依赖,Tomcat 启动时会自动加载。如果你用 Maven,在pom.xml里加依赖后执行mvn package,打出来的 war 包里会自动包含。手动放 jar 的话注意别放错到webapp/lib(没有这个目录),否则 ClassNotFound 找上门。
2.3 数据库建表与连接配置
先建库建表,字符集用utf8mb4,不然中文书名会变问号:
CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4; USE library; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(10,2), stock INT DEFAULT 0 ); CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL );连接配置我一般写在db.properties里,不硬编码到 Java 文件:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 jdbc.username=root jdbc.password=yourpasswordserverTimezone不写会报时区错误,characterEncoding=utf8不写中文参数会乱码。这两个参数是血泪经验,别省。
3. 核心功能实现:登录、列表、增删改查的代码落地
3.1 登录拦截与 Session 管理
登录不是比完密码就完事,还要防止未登录直接访问内部页面。常见做法是写一个LoginFilter,拦截/bookList、/bookEdit等路径,检查 session 里有没有 user 对象。
public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(); // 放行登录页和登录请求 String uri = request.getRequestURI(); if (uri.endsWith("login.jsp") || uri.endsWith("login")) { chain.doFilter(req, resp); return; } if (session.getAttribute("user") == null) { response.sendRedirect("login.jsp"); return; } chain.doFilter(req, resp); } }web.xml里配置 filter 的 url-pattern 为/*,然后在放行逻辑里排除登录相关路径。注意sendRedirect之后要return,否则代码继续往下走会报错。
3.2 图书列表分页与 JSTL 渲染
列表页最容易写成一次性查全表,数据一多就卡。分页的核心是LIMIT offset, size,offset 由当前页码算出来。
public List<Book> findByPage(int page, int size) { List<Book> list = new ArrayList<>(); String sql = "SELECT * FROM book LIMIT ?, ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, (page - 1) * size); ps.setInt(2, size); ResultSet rs = ps.executeQuery(); while (rs.next()) { Book b = new Book(); b.setId(rs.getInt("id")); b.setName(rs.getString("name")); b.setAuthor(rs.getString("author")); b.setPrice(rs.getBigDecimal("price")); b.setStock(rs.getInt("stock")); list.add(b); } } catch (SQLException e) { e.printStackTrace(); } return list; }JSP 页面用 JSTL 的c:forEach渲染,不要用脚本片段<% %>:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table> <tr><th>ID</th><th>书名</th><th>作者</th><th>价格</th><th>库存</th><th>操作</th></tr> <c:forEach items="${bookList}" var="b"> <tr> <td>${b.id}</td> <td>${b.name}</td> <td>${b.author}</td> <td>${b.price}</td> <td>${b.stock}</td> <td><a href="bookEdit?id=${b.id}">编辑</a></td> </tr> </c:forEach> </table>${b.name}这种 EL 表达式会自动调用 getter,前提是 Book 类有对应的getName()方法。如果没有,页面会报PropertyNotFoundException。
3.3 新增与修改共用一个表单页
新增和修改的字段完全一样,区别只是有没有 id。我一般让bookEdit.jsp同时承担两个功能:如果 request 里有 book 对象就填充,没有就留空。
// BookServlet 的 doGet 处理编辑请求 String id = request.getParameter("id"); if (id != null) { Book book = bookDAO.findById(Integer.parseInt(id)); request.setAttribute("book", book); } request.getRequestDispatcher("bookEdit.jsp").forward(request, response);表单提交到bookSave,根据 id 是否为空决定 insert 还是 update:
if (book.getId() == 0) { bookDAO.insert(book); } else { bookDAO.update(book); } response.sendRedirect("bookList");sendRedirect而不是forward,避免刷新页面重复提交。这个细节答辩时老师很爱问。
3.4 借阅记录与库存联动
借书不是简单插一条记录,还要扣库存。这两步必须在一个事务里,否则可能出现记录插了但库存没扣的情况。
public boolean borrow(int bookId, int userId) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 扣库存,stock > 0 才允许借 String sql1 = "UPDATE book SET stock = stock - 1 WHERE id = ? AND stock > 0"; PreparedStatement ps1 = conn.prepareStatement(sql1); ps1.setInt(1, bookId); int rows = ps1.executeUpdate(); if (rows == 0) { conn.rollback(); return false; } // 插入借阅记录 String sql2 = "INSERT INTO borrow(book_id, user_id, borrow_date) VALUES(?, ?, NOW())"; PreparedStatement ps2 = conn.prepareStatement(sql2); ps2.setInt(1, bookId); ps2.setInt(2, userId); ps2.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }stock > 0这个条件放在 UPDATE 的 WHERE 里,比先查再判断更安全,能防止并发下超借。事务的setAutoCommit(false)和commit/rollback必须成对出现,finally 里恢复自动提交是为了连接池复用时不带脏状态。
4. 避坑与排查:那些让你加班到凌晨的细节
4.1 中文乱码:POST 和 GET 要分开治
现象:表单提交的书名在数据库里变成????,或者页面显示问号。原因:Tomcat 默认用 ISO-8859-1 解码请求参数。解决:POST 请求在 Servlet 最前面加request.setCharacterEncoding("UTF-8");GET 请求在 Tomcat 的server.xml里给 Connector 加URIEncoding="UTF-8"。数据库连接串里也要带characterEncoding=utf8。三处都对了才不会乱码。
4.2 404:路径写错或 web.xml 配错
现象:访问/bookList报 404。原因通常是@WebServlet("/bookList")注解没生效(web.xml 的 metadata-complete 设成了 true),或者 url-pattern 写成了/bookList/。解决:检查注解和 web.xml 是否冲突,用注解就确保 web.xml 的 version 是 3.0 以上且没有 metadata-complete="true"。
4.3 连接池耗尽:Connection 没关
现象:用一会儿就报Too many connections。原因:每次getConnection后没有 close,或者异常路径下漏了 close。解决:用 try-with-resources 自动关闭,或者 finally 里显式 close。我一般把连接获取和释放封装在 DBUtil 里,DAO 只负责 SQL。
4.4 JSTL 标签不解析:jar 包缺失
现象:页面直接显示<c:forEach>源码。原因:缺jstl.jar和standard.jar。解决:在WEB-INF/lib下放入这两个 jar,或者在 Maven 里加javax.servlet:jstl:1.2依赖。Tomcat 10 的话要用jakarta.servlet.jsp.jstl的新坐标。
4.5 修改后列表不刷新:浏览器缓存
现象:改了数据,返回列表页还是旧的。原因:浏览器缓存了 GET 请求的页面。解决:在列表 Servlet 里设置response.setHeader("Cache-Control", "no-cache, no-store"),或者用 POST 跳转。这个坑不常见但很迷惑,因为数据库里数据确实变了。
5. 进阶技巧:让系统更像一个「能交差」的作品
5.1 用 Filter 统一编码和登录检查
把字符编码设置和登录检查都放进 Filter,Servlet 里就不用重复写setCharacterEncoding。一个 Filter 按顺序做三件事:设置编码、检查登录、放行。这样代码干净,答辩时也能说出「职责链模式」这个词。
5.2 前端用 Element UI 快速提升观感
JSP 页面里直接引 CDN 的 Vue 和 Element UI,表格和按钮的样式立刻上一个档次。注意 JSP 的 EL 表达式和 Vue 的{{ }}冲突,可以用<%@ page isELIgnored="false" %>配合 Vue 的v-text指令绕开。如果不想折腾,至少用 Bootstrap 把表格美化一下,比裸 HTML 强很多。
5.3 导出 Excel 和图片上传的坐标定位
热搜里有人问「jsp图片如何对坐标定位」,实际场景是图书封面裁剪或馆藏分布图。简单做法是用 HTML5 的<map>标签配合coords属性,在 JSP 里动态生成坐标数据。导出 Excel 则用 Apache POI,在 Servlet 里设置response.setContentType("application/vnd.ms-excel"),把List<Book>写进HSSFWorkbook输出到response.getOutputStream()。这两个功能加上去,系统看起来就完整了。
5.4 验证清单:交付前跑一遍
| 检查项 | 操作 | 预期结果 |
|---|---|---|
| 登录拦截 | 未登录直接访问 bookList | 跳转登录页 |
| 中文写入 | 新增书名「测试图书」 | 数据库和页面均正常显示 |
| 分页边界 | 访问 page=0 和 page=999 | 不报错,显示空或第一页 |
| 借阅事务 | 库存为 0 时借书 | 提示失败,库存不变 |
| 连接释放 | 连续操作 50 次 | 无 Too many connections |
这套系统我前前后后帮人调过不下十次,最大的体会是:不要一上来就追求功能多,先把登录、列表、新增、修改这四条链路跑通,再往上加借阅和导出。每次改完代码,重启 Tomcat 之前先 clean 一下,不然编译缓存会让你怀疑人生。希望帮到你。
本文还有配套的精品资源,点击获取