简介:本资源是一份面向高校计算机专业本科生的毕业设计文档,聚焦基于JSP与MySQL实现的B/S架构图书管理系统,解决高校图书馆信息化管理中借阅效率低、人工操作繁重等实际问题。文档完整覆盖系统需求分析、BS架构设计原理、JSP动态页面开发要点、MySQL数据库建表逻辑(含图书、用户、借阅记录三类核心表)、三层权限控制机制(管理员/教师/学生)及关键功能实现说明,适合作为课程设计参考或毕设开题与答辩支撑材料。资源为单文件docx格式,共1个438KB文档,内容结构清晰,含摘要、关键词、目录、开发环境介绍(JSP+MyEclipse+MySQL)、技术对比分析及中英文摘要等标准毕业论文模块。目前已有284人学习下载,读者可直接获取规范化的系统设计思路、可复用的权限划分方案、BS架构落地要点及完整技术栈整合说明,快速掌握Web应用开发全流程。
1. 为什么还在用 JSP 做图书管理系统?不是过时,而是“够用”和“可控”
你点开这个标题,大概率正面临一个现实任务:课程设计要交、毕设要答辩、实训项目要验收,或者某高校实验室需要一套能跑在本地 Tomcat 上、带借阅记录、用户权限、图书增删改查的轻量级系统。它不追求高并发、不对接微服务、不需要响应式前端——但必须三天内能部署、五天内能调通、老师能现场打开网页操作、答辩时能讲清楚每一层怎么串起来。这时候,“基于 JSP 的图书管理系统”不是技术怀旧,而是一条被反复验证过的最小可行路径:Servlet 控制流程清晰、JDBC 直连数据库无抽象层遮蔽、HTML+JSTL 模板可读性强,连报错堆栈都指向具体行号。它不炫技,但胜在每一步都可追溯、每个 request 都可打断点、每个 session 属性都能在调试器里展开看。如果你是刚学完《Java Web 编程基础》的学生,或带实训课的某导师,这套方案不是“将就”,而是把有限精力聚焦在理解 MVC 分层本质、掌握 HTTP 请求生命周期、建立前后端数据流转直觉上的务实选择。
2. 从零搭起 JSP 图书管理系统的最小可行骨架
2.1 环境准备:只装这 4 个东西,别碰 Maven 多模块
这不是企业级项目,不需要 Spring Boot 自动装配、不用 MyBatis 动态 SQL。我们回归最原始的 Java Web 开发链路:JDK + Tomcat + 数据库 + IDE。常见翻车点在于版本错配,我按实测稳定组合列明:
- JDK 8u291(非 JDK 11+):Tomcat 9 默认支持 JDK 8,JSP 编译器对泛型推导更宽容,避免
java.lang.ClassNotFoundException: javax.servlet.jsp.JspFactory这类玄学报错 - Apache Tomcat 9.0.83(非 10.x):Tomcat 10 启用 Jakarta EE 9 命名空间(
jakarta.servlet.*),所有 import 包名全变,而教材、参考文档、老代码全是javax.*,强行升级等于重写半个项目 - MySQL 5.7.36(非 8.0+):驱动用
mysql-connector-java-5.1.47.jar,放在WEB-INF/lib/下;8.0 默认启用 caching_sha2_password 认证插件,老 JDBC 驱动不兼容,连不上就是白忙 - IDE 推荐 Eclipse IDE for Enterprise Java and Web Developers(2021-09 版):比 IntelliJ 对传统 Dynamic Web Project 支持更傻瓜,新建项目时勾选 “Generate web.xml deployment descriptor” 是关键,否则后续 Servlet 映射全失效
提示:所有安装包统一解压到无中文、无空格路径,例如
D:\dev\tomcat9,否则 JSP 编译时org.apache.jasper.JasperException: Unable to compile class for JSP报错会卡死在路径解析环节。
2.2 项目结构:拒绝“src/main/webapp”,用经典 Dynamic Web Project
Eclipse 新建项目时选Dynamic Web Project,名称填BookMgmtSystem,Target runtime 选刚配好的 Tomcat 9,Dynamic web module version 必须选 3.1(对应 Servlet 3.1 规范)。生成后目录结构必须长这样:
BookMgmtSystem/ ├── WebContent/ ← 不叫 src/main/webapp! │ ├── index.jsp │ ├── login.jsp │ ├── admin/ │ │ └── book_list.jsp │ └── WEB-INF/ │ ├── web.xml ← 手动确保存在,不是空文件 │ └── lib/ │ └── mysql-connector-java-5.1.47.jar ├── src/ │ ├── servlet/ │ │ ├── LoginServlet.java │ │ ├── BookListServlet.java │ │ └── AddBookServlet.java │ └── dao/ │ ├── BookDAO.java │ └── UserDAO.java └── build/ └── classes/ ← 编译输出目录,Eclipse 自动生成关键动作:右键项目 → Properties → Project Facets → 确保Dynamic Web Module = 3.1,Java = 1.8,JavaScript = 1.0全部打钩。若web.xml不存在,手动在WEB-INF/下新建,内容必须包含<web-app>根节点和version="3.1"属性,否则 Tomcat 启动时直接忽略所有 Servlet 映射。
2.3 web.xml 配置:3 行代码定生死,少一行就 404
这是整个系统路由的总开关,也是新手最容易漏写的配置。WEB-INF/web.xml内容精简到极致,只保留必要项:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- 登录入口 --> <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <!-- 图书列表入口 --> <servlet> <servlet-name>BookListServlet</servlet-name> <servlet-class>servlet.BookListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>BookListServlet</servlet-name> <url-pattern>/book/list</url-pattern> </servlet-mapping> </web-app>逻辑说明:
<servlet>块声明 Servlet 类的全限定名(包名+类名),<servlet-class>必须与.java文件实际路径严格一致,大小写敏感;<servlet-mapping>将 URL 路径(如/book/list)绑定到指定 Servlet,浏览器访问http://localhost:8080/BookMgmtSystem/book/list时,Tomcat 才会实例化BookListServlet并调用其doGet()方法;- 没有
<servlet-mapping>,URL 就永远 404;没有<servlet>声明,映射就找不到目标类——这是 90% 的“页面打不开”问题根源。
3. 核心功能实现:3 个 Servlet + 2 个 DAO,撑起全部业务
3.1 数据库建表:5 张表足矣,字段命名拒绝驼峰
MySQL 中执行以下建表语句(注意字符集必须为utf8mb4,否则中文存入变问号):
-- 用户表:管理员与普通读者共用一张表,用 type 字段区分 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, realname VARCHAR(30), type ENUM('admin', 'reader') DEFAULT 'reader', created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 图书表:ISBN 作主键更符合业务实际,避免同书多版重复 CREATE TABLE books ( isbn VARCHAR(17) PRIMARY KEY, title VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), publish_year YEAR, stock INT DEFAULT 0, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 借阅记录表:外键关联 users 和 books,status 记录借/还状态 CREATE TABLE borrow_records ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, isbn VARCHAR(17) NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME NULL, status ENUM('borrowed', 'returned') DEFAULT 'borrowed', FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (isbn) REFERENCES books(isbn) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;参数说明:
VARCHAR(17)存 ISBN-13(含分隔符),足够覆盖所有标准 ISBN;ENUM类型比VARCHAR更安全,数据库层强制取值范围,避免代码里写错'adminn'导致权限失控;FOREIGN KEY必须显式声明,否则删除图书时可能残留借阅记录,造成数据不一致;DEFAULT CURRENT_TIMESTAMP减少 Java 代码中手动 set 时间的出错概率。
3.2 JDBC 工具类:1 个DBUtil.java,封装连接获取与关闭
src/util/DBUtil.java是整个数据访问层的基石,必须手写,不依赖任何框架:
package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/bookdb?useSSL=false&serverTimezone=UTC&characterEncoding=utf8mb4"; private static final String USER = "root"; private static final String PASSWORD = "123456"; // 实际使用请改密 static { try { Class.forName("com.mysql.jdbc.Driver"); // 注意是 com.mysql.jdbc.Driver,不是 com.mysql.cj.jdbc.Driver } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL Driver not found!", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void closeConnection(Connection conn) { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); // 生产环境应记日志,此处仅调试 } } } }逻辑说明:
Class.forName()必须显式调用,JDK 6+ 虽支持自动加载,但 Tomcat 类加载器隔离机制下常失效,不写这行,getConnection()会抛SQLException: No suitable driver found;- URL 中
useSSL=false是 MySQL 5.7 必加参数,否则连接超时;serverTimezone=UTC避免时区转换错误导致时间字段写入异常; com.mysql.jdbc.Driver是 5.1.x 驱动的类名,若误写成com.mysql.cj.jdbc.Driver(8.0+ 驱动类名),启动时直接ClassNotFoundException。
3.3 BookDAO:用 PreparedStatement 防 SQL 注入,不是 Statement
src/dao/BookDAO.java实现图书 CRUD,重点看参数化查询写法:
package dao; import util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class BookDAO { // 查询全部图书(带分页) public List<Book> findAll(int offset, int limit) throws SQLException { String sql = "SELECT isbn, title, author, publisher, publish_year, stock FROM books LIMIT ?, ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, offset); // 第一个 ? 设为 offset ps.setInt(2, limit); // 第二个 ? 设为 limit try (ResultSet rs = ps.executeQuery()) { List<Book> list = new ArrayList<>(); while (rs.next()) { Book book = new Book(); book.setIsbn(rs.getString("isbn")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setPublisher(rs.getString("publisher")); book.setPublishYear(rs.getInt("publish_year")); book.setStock(rs.getInt("stock")); list.add(book); } return list; } } } // 根据 ISBN 查询单本 public Book findByIsbn(String isbn) throws SQLException { String sql = "SELECT isbn, title, author, publisher, publish_year, stock FROM books WHERE isbn = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, isbn); // 参数 1 对应 ?,严格按顺序 try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { Book book = new Book(); book.setIsbn(rs.getString("isbn")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setPublisher(rs.getString("publisher")); book.setPublishYear(rs.getInt("publish_year")); book.setStock(rs.getInt("stock")); return book; } return null; } } } // 新增图书 public int add(Book book) throws SQLException { String sql = "INSERT INTO books (isbn, title, author, publisher, publish_year, stock) VALUES (?, ?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, book.getIsbn()); ps.setString(2, book.getTitle()); ps.setString(3, book.getAuthor()); ps.setString(4, book.getPublisher()); ps.setInt(5, book.getPublishYear()); ps.setInt(6, book.getStock()); return ps.executeUpdate(); // 返回影响行数,1 表示成功 } } }参数说明:
PreparedStatement的?占位符必须按 SQL 中出现顺序用setXxx()设置,索引从 1 开始,不能跳过或错位;try-with-resources自动关闭Connection、PreparedStatement、ResultSet,避免连接泄漏——这是 Tomcat 运行几小时后突然报Cannot create PoolableConnectionFactory的主因;executeUpdate()用于 INSERT/UPDATE/DELETE,返回整数表示影响行数;executeQuery()用于 SELECT,返回ResultSet。
4. JSP 页面开发:用 JSTL 替代 scriptlet,让 HTML 干净可维护
4.1 引入 JSTL:2 个 jar 包 + 1 行 taglib 声明
JSP 中禁止写<% java code %>(scriptlet),全部改用 JSTL 标签。需下载两个 jar 包放入WEB-INF/lib/:
jstl-1.2.jarstandard-1.1.2.jar
然后在每个 JSP 顶部声明:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>提示:
uri值必须一字不差,复制粘贴即可。若写成http://jstl.sourceforge.net/jstl/core或漏掉http://java.sun.com/,页面会直接报According to TLD or attribute directive in tag file, attribute xxx does not accept any expressions。
4.2 图书列表页(book_list.jsp):JSTL 循环 + EL 表达式取值
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>图书列表</title> <style> table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #ccc; padding: 8px; text-align: left; } .btn { background: #007bff; color: white; padding: 4px 8px; text-decoration: none; } </style> </head> <body> <h2>全部图书</h2> <a href="add_book.jsp" class="btn">新增图书</a> <br><br> <table> <tr> <th>ISBN</th> <th>书名</th> <th>作者</th> <th>出版社</th> <th>出版年</th> <th>库存</th> <th>操作</th> </tr> <c:choose> <c:when test="${empty bookList}"> <tr><td colspan="7">暂无图书</td></tr> </c:when> <c:otherwise> <c:forEach items="${bookList}" var="book"> <tr> <td>${book.isbn}</td> <td>${book.title}</td> <td>${book.author}</td> <td>${book.publisher}</td> <td><fmt:formatNumber value="${book.publishYear}" pattern="#"/></td> <td>${book.stock}</td> <td> <a href="book_detail.jsp?isbn=${book.isbn}" class="btn">详情</a> <a href="delete_book.jsp?isbn=${book.isbn}" class="btn" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </c:forEach> </c:otherwise> </c:choose> </table> </body> </html>逻辑说明:
${bookList}是 Servlet 中request.setAttribute("bookList", bookList)传入的 List ,EL 表达式自动调用 getter 方法(getIsbn()→${book.isbn});<c:forEach>替代<% for(...) { %>,var="book"在循环体内生成每个 Book 对象的引用;<c:choose>+<c:when>+<c:otherwise>替代<% if...else %>,语义清晰且无 Java 语法干扰 HTML 结构;<fmt:formatNumber>格式化数字,避免publishYear为null时页面显示0(YEAR 类型默认值)。
4.3 登录校验:Session 管理权限,不是 Cookie 存密码
LoginServlet.java中关键逻辑:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); UserDAO userDAO = new UserDAO(); User user = userDAO.findByUsername(username); if (user != null && password.equals(user.getPassword())) { // 登录成功:存用户信息到 Session,不存密码! HttpSession session = request.getSession(); session.setAttribute("currentUser", user); // 存整个 User 对象 session.setMaxInactiveInterval(1800); // 30 分钟无操作自动失效 // 根据角色跳转 if ("admin".equals(user.getType())) { response.sendRedirect("admin/book_list.jsp"); } else { response.sendRedirect("reader/my_borrows.jsp"); } } else { request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } }参数说明:
request.getSession()获取当前会话,首次调用创建新 Session,后续请求自动复用;session.setAttribute("currentUser", user)将用户对象存入 Session,后续所有页面可通过${sessionScope.currentUser.username}取值;setMaxInactiveInterval(1800)设定 Session 最大空闲时间(秒),防止长期未登出导致内存泄漏;- 绝不把密码存入 Session 或 Cookie,只存必要身份标识(如 user.id 或 user.type),密码仅用于本次校验。
5. 避坑指南:这 4 个错误占了调试时间的 70%
5.1 现象:启动 Tomcat 后访问http://localhost:8080/BookMgmtSystem/显示 404,但http://localhost:8080/能看到 Tomcat 首页
原因:项目未成功部署到 Tomcat。常见有三:① Eclipse 中项目右键 → Run As → Run on Server 未勾选该项目;②WebContent/目录未被识别为 Web Root,在项目 Properties → Deployment Assembly 中未添加WebContent/到/;③web.xml文件名大小写错误(如Web.xml或WEB.XML),Windows 下不敏感,Linux 服务器上直接失败。
解决:右键项目 → Properties → Deployment Assembly → 点击 Add → Folder → 选择WebContent/→ Deploy Path 填/→ Finish;确认web.xml文件名全小写。
5.2 现象:登录后跳转到book_list.jsp,页面空白,查看源码发现<c:forEach>标签原样输出未解析
原因:JSTL 标签库未正确加载。要么jstl.jar和standard.jar未放入WEB-INF/lib/,要么taglib声明的uri值拼写错误,要么 JSP 文件编码不是 UTF-8 导致声明行乱码。
解决:检查WEB-INF/lib/下两个 jar 包是否存在;复制标准uri值;用 Notepad++ 将 JSP 文件另存为 UTF-8 无 BOM 格式。
5.3 现象:点击“新增图书”提交后,页面报HTTP Status 405 – Method Not Allowed
原因:Servlet 中只写了doPost()方法,但表单 method="get",或只写了doGet()但表单 method="post",方法不匹配。
解决:统一约定——表单提交一律用 POST,Servlet 中只实现doPost();若需 GET 支持(如分页链接),在doGet()中调用doPost(request, response)复用逻辑。
5.4 现象:图书列表中中文显示为??,数据库里却是正常的
原因:JDBC URL 缺少characterEncoding=utf8mb4,或 Tomcat 的conf/server.xml中 Connector 未设置URIEncoding="UTF-8"。
解决:修改server.xml中<Connector port="8080" ... />为<Connector port="8080" protocol="HTTP/1.1" URIEncoding="UTF-8" ... />;重启 Tomcat;确认web.xml中<%@ page contentType="text/html;charset=UTF-8" %>存在。
6. 验证与交付:3 步自测清单 + 1 个答辩必答问题
6.1 本地自测清单:不靠老师,自己跑通这 3 条路径
| 测试路径 | 操作步骤 | 预期结果 | 关键检查点 |
|---|---|---|---|
| 登录流程 | 1. 访问http://localhost:8080/BookMgmtSystem/login.jsp2. 输入已知管理员账号(如 admin/123456)3. 点击登录 | 跳转至admin/book_list.jsp,页面显示图书表格 | 查看浏览器地址栏是否变为/book_list.jsp;F12 检查 Network 选项卡,确认login请求返回 302 重定向 |
| 数据写入 | 1. 在book_list.jsp点“新增图书”2. 填 ISBN 978-7-04-050694-6、书名Java Web 开发实战、作者张三3. 提交 | 页面跳回book_list.jsp,新书出现在第一行 | 登录 MySQL 执行SELECT * FROM books WHERE isbn='978-7-04-050694-6';,确认记录存在且字段值完全匹配 |
| 权限隔离 | 1. 用读者账号(如reader/123456)登录2. 手动在地址栏输入 http://localhost:8080/BookMgmtSystem/admin/book_list.jsp | 页面显示 404 或跳转回登录页 | 检查admin/book_list.jsp顶部是否有权限校验代码:`<% if (session.getAttribute("currentUser") == null |
6.2 答辩高频问题:如何回答“为什么不用 Spring Boot?”
这个问题本质是考察技术选型意识,不是比谁用的框架新。我的标准回答是:
“Spring Boot 适合快速构建云原生应用,但本次设计目标是透彻理解 Web 应用底层交互机制。用原生 Servlet,我能清晰看到
HttpServletRequest如何解析表单参数、HttpServletResponse如何写入 HTML、HttpSession如何通过 Cookie 维持状态、JDBC 如何建立物理连接——这些在 Spring Boot 的自动配置下被封装成黑匣子。比如,当NullPointerException发生在bookDAO.findAll()时,我立刻知道是Connection为空,进而排查DBUtil.getConnection()是否抛异常;而在 Spring 中,同样的错误可能隐藏在@Transactional代理或连接池配置里,初学者难以定位。这不是拒绝高级框架,而是先打好地基。”
6.3 我的血泪经验:打包交付前必做的 3 件事
- 清理
build/classes/下的.class文件:Eclipse 有时编译残留旧字节码,导致 Tomcat 加载了过期的 Servlet,行为与源码不符。交付前手动删除整个build/classes/目录,再 Clean Project 重建。 - 把
WEB-INF/web.xml中的注释全删掉:某些老旧 Tomcat 版本解析 XML 注释会出错,删掉<!-- 这是登录映射 -->这类注释,只留纯标签。 - 在
index.jsp里加一行跳转:<% response.sendRedirect("login.jsp"); %>,这样用户访问根路径时自动进登录页,避免看到空白页以为系统没跑起来。
希望帮到你。
本文还有配套的精品资源,点击获取