简介:这是一套面向高校计算机专业学生与Java Web初学者的图书管理系统完整项目,可直接用于毕业设计、课程大作业或自学练手。项目基于JSP、Servlet、Layui与MySQL开发,界面美观,并带有公告功能,涵盖用户、图书管理员、系统管理员三类角色:用户可查看公告、浏览图书与个人违章;图书管理员负责借还图书及借阅、还书报表;系统管理员则管理书籍、借阅规则、图书信息查询与借阅统计,业务闭环完整。压缩包共1046个文件,约40.91MB,包含212个Java源码、156个JSP页面、120个JS脚本、57个CSS样式及3个SQL脚本,另附文档与war包,源码、数据库与说明文档齐全。运行环境为IDEA、JDK1.8、MySQL5.7与Tomcat9,导入即可调试。目前已有94人学习,适合需要完整赛题方案、分层代码参考与数据库设计思路的读者。
1. 图书管理系统用 JSP+Servlet 做到界面美观:毕业设计里最容易被低估的得分点
很多同学做 Java Web 图书管理系统,功能跑通了,答辩时却被一句“界面太像十年前的后台”问住。JSP + Servlet 这套技术栈本身不新,但毕业设计评审看的不是技术新旧,而是你能不能把借阅、公告、权限这些模块串成一个完整可演示的系统,并且界面让人愿意多看两眼。这个标题背后其实藏着三层需求:一是用 JSP + Servlet 搭出 MVC 骨架,二是把公告、图书、借阅、用户这些核心表跑通,三是界面不能停留在裸 HTML 表格阶段。适合正在做计算机毕业设计、课程大作业,或者想用一套完整 CRUD 项目补齐 Java Web 实战经验的人。下面按我实际带学生改项目的顺序,把选型、建表、页面美化、公告模块和避坑点一次讲透。
2. 技术选型与 MVC 分层:为什么 JSP+Servlet 仍然是毕业设计的稳妥牌
2.1 不追 Spring Boot 的理由:答辩现场要的是可控
我见过太多毕业设计选题一上来就写 Spring Boot + Vue,结果环境跑不起来,答辩时连数据库连接都调不通。JSP + Servlet 的优势在于:Tomcat 一挂就能跑,不需要 Node、不需要 Maven 多模块、不需要前后端分离联调。对于图书管理系统这种以 CRUD 为主、并发量几乎为零的场景,Servlet 做控制器、JSP 做视图、JavaBean 做模型,三层职责清晰,代码量可控,老师一眼能看懂你的架构。
常见做法是:web.xml或注解方式注册 Servlet,用@WebServlet("/book")映射路径,JSP 放在WEB-INF/views下防止直接访问。数据库用 MySQL 8,驱动mysql-connector-j,连接池可以用 Druid 也可以手写DBUtil。我一般建议毕业设计不要上连接池,直接DriverManager封装一个工具类,少一个依赖少一个坑。
提示:如果学校要求必须用框架,优先选 Servlet + JSP + JDBC 原生组合,而不是 Spring MVC,因为后者配置量大,出问题不好排查。
2.2 项目目录结构与最小可运行骨架
一个能跑通的图书管理系统,目录结构建议这样组织:
src/ com.library.dao/ BookDao.java, UserDao.java, NoticeDao.java com.library.entity/ Book.java, User.java, Notice.java com.library.servlet/ BookServlet.java, LoginServlet.java com.library.util/ DBUtil.java web/ WEB-INF/views/ book_list.jsp, notice_list.jsp static/css/ style.css static/js/ main.jsDBUtil负责拿连接和关资源,DAO 只写 SQL,Servlet 只做参数接收和跳转,JSP 只做展示。这个分层不是形式主义,而是当你改一个字段时,只需要动一处。
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PWD = "123456"; public static Connection getConn() throws SQLException { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("驱动加载失败", e); } return DriverManager.getConnection(URL, USER, PWD); } public static void close(Connection c, PreparedStatement p, ResultSet r) { // 依次关闭,注意 null 判断 } }参数说明:serverTimezone必须写,否则 MySQL 8 会报时区错误;useSSL=false在本地开发时避免证书警告。close方法里先关 ResultSet 再关 Statement 再关 Connection,顺序反了在某些驱动下会抛异常。
2.3 数据库表设计:四张表撑起整个系统
图书管理系统的核心表不多,但字段类型和索引要提前想清楚:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| user | id, username, password, role | role 区分 admin/reader |
| book | id, isbn, title, author, stock | stock 控制可借数量 |
| notice | id, title, content, create_time | 公告按时间倒序 |
| borrow | id, user_id, book_id, borrow_date, return_date | 借阅记录 |
borrow表要加user_id和book_id的外键索引,否则联表查询借阅列表时会全表扫描。book.stock在借书时减一、还书时加一,这个逻辑必须放在同一个事务里,否则并发下会出现超借。
3. 从登录到借阅:Servlet 控制器与 JSP 页面的完整串联
3.1 登录鉴权:一个 Filter 挡住所有未登录请求
很多同学在每个 Servlet 里写一遍session.getAttribute("user")判断,代码重复且容易漏。正确做法是写一个LoginFilter,拦截/book、/notice、/borrow等路径,放行/login、/static。
@WebFilter(urlPatterns = {"/book", "/notice", "/borrow"}) 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; Object user = request.getSession().getAttribute("user"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }逻辑说明:Filter 在 Servlet 之前执行,未登录直接重定向到登录页。参数urlPatterns里不要写/*,否则登录页和 CSS 也会被拦,导致页面样式丢失。如果登录后要区分管理员和读者,在 Filter 里再判断role,管理员才能访问公告发布和图书删除。
3.2 图书列表分页:SQL 的 limit 和 JSP 的页码条
图书列表是使用频率最高的页面,数据一多就必须分页。Servlet 接收page参数,默认 1,每页 10 条。
int page = Integer.parseInt(req.getParameter("page") == null ? "1" : req.getParameter("page")); int pageSize = 10; int offset = (page - 1) * pageSize; String sql = "SELECT * FROM book ORDER BY id DESC LIMIT ? OFFSET ?";参数说明:LIMIT ? OFFSET ?是 MySQL 的写法,offset从 0 开始。总页数用SELECT COUNT(*) FROM book单独查一次,传给 JSP 生成页码链接。JSP 里用 JSTL 的<c:forEach>渲染列表,不要用 Java 脚本片段,否则页面难维护。
<c:forEach items="${bookList}" var="b"> <tr> <td>${b.title}</td> <td>${b.author}</td> <td>${b.stock > 0 ? '可借' : '已借完'}</td> </tr> </c:forEach>3.3 借书与还书:事务和库存扣减的边界
借书操作涉及两张表:borrow插入一条记录,book的stock减一。这两步必须在一个事务里。
Connection conn = DBUtil.getConn(); conn.setAutoCommit(false); try { // 1. 检查库存 // 2. 插入借阅记录 // 3. 更新库存 conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn, null, null); }血泪经验:如果先插入借阅记录再检查库存,库存不足时回滚,自增 ID 会跳号,但不影响业务。真正要防的是两个用户同时借最后一本书,所以检查库存的 SQL 要写成SELECT stock FROM book WHERE id=? FOR UPDATE,锁住行再判断。
4. 界面美观的实操:JSP 页面怎么摆脱“裸表格”既视感
4.1 用一套现成 CSS 框架快速提升观感
JSP 页面美观不需要自己写几百行 CSS。常见做法是引入 Bootstrap 5 的 CDN,或者把 Bootstrap 的bootstrap.min.css放到static/css下。表格加table table-striped table-hover,按钮加btn btn-primary btn-sm,导航栏用navbar navbar-expand-lg navbar-dark bg-dark,十分钟就能从裸表格变成能看的后台。
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/bootstrap.min.css"> <table class="table table-striped table-hover align-middle"> <thead class="table-dark"> <tr><th>书名</th><th>作者</th><th>状态</th><th>操作</th></tr> </thead> <tbody>...</tbody> </table>注意:${pageContext.request.contextPath}不能省,否则项目部署到非根路径时 CSS 会 404。如果学校机房不能联网,就提前把 Bootstrap 下载到本地,不要用 CDN。
4.2 公告模块:首页轮播和列表页的两种展示
公告是毕业设计里容易被忽略但答辩时很讨巧的模块。首页放一个公告轮播,列表页按时间倒序展示。轮播用 Bootstrap 的carousel组件,数据从NoticeServlet查出来放进request域。
List<Notice> notices = noticeDao.findLatest(5); req.setAttribute("notices", notices); req.getRequestDispatcher("/WEB-INF/views/index.jsp").forward(req, resp);参数说明:findLatest(5)取最新 5 条,SQL 用ORDER BY create_time DESC LIMIT 5。JSP 里用<c:forEach>生成carousel-item,第一条加active类。公告内容如果超过两行,用 CSS 的text-overflow: ellipsis截断,避免撑破轮播高度。
4.3 登录页和注册页:第一眼决定印象分
登录页是老师打开系统看到的第一屏。我一般会做一个左右分栏:左边放一张图书馆相关的背景图,右边放登录表单。背景图用background-size: cover,表单加box-shadow和圆角。注册页复用同样的样式,只改字段。
.login-box { width: 380px; padding: 40px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); background: #fff; }如果时间紧,至少把默认的<input>加上form-control类,按钮加上btn btn-primary w-100,观感立刻不一样。不要用<center>标签,不要用表格布局。
5. 避坑与排查:JSP+Servlet 图书管理系统最常见的 5 个翻车现场
5.1 中文乱码:POST 和 GET 要分开处理
现象:表单提交后数据库里是问号,或者页面显示乱码。原因:Tomcat 默认 ISO-8859-1 解码。解决:POST 请求在 Servlet 第一行写req.setCharacterEncoding("UTF-8");GET 请求在 Tomcat 的server.xml里给 Connector 加URIEncoding="UTF-8"。JSP 页面顶部写<%@ page contentType="text/html;charset=UTF-8" %>。三处都写,缺一处都可能翻车。
5.2 404:路径多了或少了项目名
现象:点登录按钮跳 404。原因:action写成了/login,但项目部署在/library下。解决:所有链接和表单 action 都用${pageContext.request.contextPath}/login,不要写死/。如果用了 Filter 重定向,也要拼上request.getContextPath()。
5.3 数据库连接泄漏:Tomcat 跑一天就崩
现象:系统用一段时间后报Too many connections。原因:DAO 里拿了连接没关,或者异常时跳过了close。解决:close放在finally块里,或者用 try-with-resources。检查每个 DAO 方法,确保Connection、PreparedStatement、ResultSet都有对应的关闭。
5.4 JSP 页面修改后不生效
现象:改了 JSP,刷新浏览器还是旧页面。原因:Tomcat 缓存了编译后的 Servlet。解决:在server.xml的 Context 里加reloadable="true",或者直接删掉work目录。IDEA 里可以配置热部署,但毕业设计演示前建议重启一次 Tomcat,确保最新代码生效。
5.5 借阅记录联表查询字段对不上
现象:借阅列表显示的书名是 null。原因:borrow表只存了book_id,查询时没联book表。解决:SQL 写成SELECT b.*, k.title FROM borrow b LEFT JOIN book k ON b.book_id = k.id,JSP 里用${b.title}取书名。如果字段名冲突,用别名区分。
6. 答辩前值得做的三件事:从能跑到能拿高分
第一件事,把公告模块的发布时间做成“几分钟前”的相对时间。答辩时老师看到“3 分钟前发布”比“2025-01-01 12:00:00”更有感知。写一个简单的工具方法,在 JSP 里用 EL 函数调用,或者直接在 Servlet 里算好放进实体。
第二件事,给图书列表加一个搜索框。按书名或作者模糊查询,SQL 用LIKE CONCAT('%',?,'%')。这个功能代码量不到二十行,但演示时能体现交互完整度。
第三件事,准备一个README.md,写清楚数据库建表语句、Tomcat 版本、JDK 版本、默认账号密码。答辩现场如果老师要自己跑,你能三十秒内把环境搭起来,印象分直接拉满。
-- 建库建表最小语句 CREATE DATABASE library DEFAULT CHARSET utf8mb4; USE library; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT 'reader' ); INSERT INTO user(username, password, role) VALUES('admin', '123456', 'admin');我自己的习惯是:每次改完 DAO 层,先用main方法单独跑一遍 SQL,确认能查出数据,再接到 Servlet 里。这样出问题时能快速定位是 SQL 错了还是页面传参错了。毕业设计不需要多高深的技术,把借阅流程跑顺、界面收拾干净、公告能发能看,就已经超过大多数同题项目。希望帮到你。
本文还有配套的精品资源,点击获取