☰
JSP+Servlet+MySQL图书馆系统源码部署与跑通实战
2026/9/26 5:40:23 网站建设 项目流程

简介:这是一份采用JSP+Servlet+MySQL经典三层架构开发的图书馆图书管理系统完整源码,主要面向计算机相关专业完成课程设计、毕业设计或希望掌握Java Web开发流程的初学者。系统内置管理员、学生、游客三类角色:管理员可登录后台进行用户管理、图书信息维护和罚款缴纳;学生可完成图书借阅、归还、查询个人借阅记录及修改密码;游客可浏览全部图书并查看详情。压缩包共112个文件,包含45个Java源文件,覆盖Servlet控制器、DAO数据访问层、实体类与登录过滤器,24个JSP页面负责前台展示和后台管理界面,另有37张PNG图片、2个GIF动图、PDF/MD说明文档等,整体仅1.76MB,轻量易部署。目前已有263人学习下载,借助这份源码可快速理清Servlet+JSP+JDBC的典型分层、请求转发、登录过滤与数据增删改查等核心实现,是一套既可运行演示又可二次拓展的Web项目参考。

1. 从图书馆管理系统源码.zip 到本机跑通:这 30 分钟值得花

你手上这份基于 jsp+mysql+servlet 的 JSP 图书馆图书管理系统源码.zip,解压前是个压缩包,解压后其实是一整套 JavaWeb 课程设计作业的骨架子:管理员登录、图书增删改查、读者借还书,SQL 建表脚本和 Tomcat 部署配置都藏在里面。这类源码最大的价值不是直接交作业,而是让你在半小时内看明白 JSP 怎么发请求、Servlet 怎么接参数、MySQL 怎么落数据。适合三类人:被毕设题目卡住的在校生、想从 Spring Boot 回头补 JavaWeb 基础的开发者、以及需要给客户演示管理系统的外围实施。别急着双击 jsp 文件,先按下面的顺序把 MySQL 和 Tomcat 搭起来。

2. 先看懂 jsp+mysql+servlet 的分工:JSP 是脸面,Servlet 是入口,MySQL 是账本

2.1 Servlet 生命周期:init 只跑一次,service 每次请求都跑

很多源码包里把 Servlet 和 JSP 混在一起讲,实际拆开就三条:JSP 负责 HTML 输出,Servlet 负责接表单参数、调 JDBC、控制跳转,MySQL 负责数据落地。Servlet 生命周期不是面试题,是你排查 404 和空数据的依据。

老式源码里最常见的配置是 web.xml 手动映射 Servlet:

<servlet> <servlet-name>BookServlet</servlet-name> <servlet-class>com.library.servlet.BookServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>BookServlet</servlet-name> <url-pattern>/book</url-pattern> </servlet-mapping>

这段配置里<servlet-name>只是 ID,<servlet-class>才是真正干活儿的 Java 类。<url-pattern>/book</url-pattern>表示只要请求命中/library/book,Tomcat 就把它交给 BookServlet。load-on-startup让它随应用启动就初始化,不写也问题不大,只是第一次访问才创建。

Servlet 是单例多线程的:init()在生命周期里只跑一次,service()每个请求都会跑。所以不要在doGet或doPost里去修改共享成员变量,否则并发借书时容易把库存扣出负数。这就是为什么后面说事务要用数据库条件更新,而不是先查再改。

2.2 一条“查书”请求穿过 JSP、Servlet、MySQL 的完整链条

拿到源码后,先找表单里的 action。常见写法是:

curl -X POST "http://localhost:8080/library/book" \ --data "action=query&keyword=三体"

这个action=query是 Servlet 内部自己约定好的业务标识。有人把所有操作都塞进一个 BookServlet,再用 if/switch 分发;也有人拆成 AddBookServlet、QueryBookServlet。你拿到源码先找这个分发逻辑,别在 JSP 里乱改超链接。

整条链路是:浏览器提交表单 → Tomcat 按 web.xml 找到/book对应的 Servlet → Servlet 读keyword参数 → 用 JDBC 查 MySQL → 把结果塞进request.setAttribute→request.getRequestDispatcher("book_list.jsp").forward(...)→ JSP 用 EL 或 JSTL 把结果渲染成 HTML。这里面最容易断的是第二步和第五步,后面章节会分别给代码和排查清单。

2.3 这个选型落后了吗:为什么这类源码不用 Spring Boot

答辩时老师大概率会问你“为什么不用 Spring Boot”。常见回答不是“我不会”,而是“这个选型对课程设计够用,结构足够清晰”。

对比点jsp+mysql+servletSpring Boot + Thymeleaf
依赖管理靠 lib 目录放 jarMaven/Gradle 管理坐标
页面技术JSP 由 Tomcat 编译模板引擎直接渲染
路由方式web.xml 或 @WebServlet@Controller + @RequestMapping
数据库访问JDBC 手写JdbcTemplate / MyBatis / JPA
适合场景教学、实训、课程设计企业项目、快速迭代

Spring Boot 2 也能集成 JSP,但要额外配视图前缀后缀,而且很多老源码连 Maven 结构都没有,硬迁成本远高于重写。除非你要做线上商用,否则先跑通这份 jsp+mysql+servlet 源码,是理解 JavaWeb 的必经之路。

3. 把源码 zip 变成能启动的工程:JDK、Tomcat、MySQL 的版本搭配和导入顺序

3.1 拿到 zip 后先检查目录结构,再定 Tomcat 版本

不要上来就双击解压。先在命令行里看清楚结构,能避免后面 80% 的配置问题。

mkdir -p ~/library-system && cd ~/library-system unzip ../JSP图书馆图书管理系统源码.zip -d source cd source find . -maxdepth 3 -type f | sort

这条命令在 Linux/macOS 下能用,Windows 用 WinRAR 解压后同样要看目录。常见结构是src/放 Java 源码,WebContent/或webapp/放 JSP 和 WEB-INF,WEB-INF/lib/放 mysql 驱动等 jar,另有一个sql/或database/放建表脚本。如果看到pom.xml,说明这是 Maven 工程;没有,就按传统 Dynamic Web Project 处理。

版本搭配很重要。我一般用 JDK 8 + Tomcat 8.5 或 9.0 + MySQL 5.7 + mysql-connector-java 5.1.49。Tomcat 10 以后把javax.servlet改成jakarta.servlet,老源码直接放进去会NoClassDefFoundError,这是最常见的一处翻车点。如果你的 JDK 是 11 以上,选 Tomcat 9 最稳。

3.2 导入 IDEA/Eclipse 的 3 个关键配置

在 IDEA 里不要选择 New Project,要选 Open,直接选中解压后的目录。如果里面有.classpath和.project,IDEA 会把它当 Eclipse 工程导入;如果只有src和WebContent,用 New Module from Existing Sources,手动勾选 Web Module。

导入后有三处必须检查。第一,Project Structure 里确认 Java 版本,老源码常带着 JDK 6/7 的编译级别,不改会报generics not supported。第二,Artifacts 里要有一个war exploded,且 Output Layout 里能看到WEB-INF/classes和WEB-INF/lib下的 jar。第三,确认 mysql 驱动 jar 在WEB-INF/lib,而不是只在 IDEA 的 External Libraries。

如果是 Maven 工程,直接执行:

mvn clean package -DskipTests ls target/*.war

这个包就是后面可以丢进 Tomcat webapps 的部署产物。不是 Maven 工程也没关系,IDEA 自带的 Artifacts 构建同样能生成 war,只是配置路径藏在 Project Structure 里,很多人找不到而已。

3.3 初始化 MySQL:执行 SQL 脚本而不是手工建表

源码包里通常已经贴心地放了 SQL 脚本。我的顺序是先看脚本,再决定补建还是全量执行。不要带着原有的旧数据库直接跑,表结构对不上会出现Unknown column。

典型的核心表有四张:管理员表、图书表、读者表、借阅表。自己补写时,最少要有下面这个骨架:

CREATE DATABASE IF NOT EXISTS library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library_db; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), total_count INT DEFAULT 0, remaining_count INT DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, phone VARCHAR(20) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE borrow ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT, reader_id INT, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME DEFAULT NULL, CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id), CONSTRAINT fk_borrow_reader FOREIGN KEY (reader_id) REFERENCES reader(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段 SQL 里的utf8mb4_general_ci在 MySQL 5.7 和 8.0 都能用,兼容性最好。remaining_count单独存,是为了查询列表时不用算total_count - 已借数量,性能更好,但牺牲了一点一致性,后面借书事务要靠它做条件更新。

数据库建好后,把 JDBC 连接串也改掉。源码里的数据库配置通常在db.properties或DBUtil.java:

driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/library_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username=root password=你的密码

注意 URL 里写127.0.0.1,不要写localhost。原因在第五章会细说。密码不要带特殊字符,课程设计项目没必要为难自己。

4. 借书还书链路实战:Servlet 接收参数、JDBC 操作 MySQL、JSP 回显的完整闭环

4.1 一个 QueryBookServlet:把表单参数转成 SQL 查询并回传 JSP

先看查询图书这个最小闭环。前端输入书名关键字,Servlet 接收后查表,再把结果交给 JSP。这是整个系统最标准的一段代码:

@WebServlet("/book/query") public class QueryBookServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String keyword = request.getParameter("keyword"); String pageNoStr = request.getParameter("pageNo"); int pageNo = 1; if (pageNoStr != null && pageNoStr.matches("\\d+")) { pageNo = Integer.parseInt(pageNoStr); } int pageSize = 10; int offset = (pageNo - 1) * pageSize; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement( "SELECT id, book_name, author, publisher, total_count, remaining_count " + "FROM book WHERE book_name LIKE ? " + "LIMIT ? OFFSET ?")) { ps.setString(1, "%" + keyword + "%"); ps.setInt(2, pageSize); ps.setInt(3, offset); ResultSet rs = ps.executeQuery(); List<Book> bookList = new ArrayList<>(); while (rs.next()) { Book b = new Book(); b.setId(rs.getInt("id")); b.setBookName(rs.getString("book_name")); b.setAuthor(rs.getString("author")); b.setPublisher(rs.getString("publisher")); b.setTotalCount(rs.getInt("total_count")); b.setRemainingCount(rs.getInt("remaining_count")); bookList.add(b); } request.setAttribute("bookList", bookList); request.setAttribute("keyword", keyword); request.setAttribute("pageNo", pageNo); request.getRequestDispatcher("/book_list.jsp").forward(request, response); } catch (Exception e) { throw new ServletException("查询图书失败", e); } } }

这段代码里最该抄的是三处。一是PreparedStatement,用字符串拼接关键词很容易被注入,课程设计也许没人攻击你,但答辩老师会看。二是LIMIT ? OFFSET ?配合setInt做分页,避免了pageNo直接拼进 SQL。三是request.setAttribute传对象,而不是在 Servlet 里把 HTML 拼成字符串输出。

有一个小坑:pageNo*pageSize如果后端不做上限检查,用户传99999会查出空列表。我一般会再加一句:

pageNo = Math.max(1, Math.min(pageNo, 1000));

这是给自己留的边界,避免异常参数把查询拖慢。

4.2 JSP 表格页怎么写才能不被乱码和空列表打断

Servlet 把数据放进request后,JSP 页面负责渲染。老源码里常看到 JSP 中直接写 Java 脚本片段,我不推荐,但你要看得懂。能跑通的写法是 JSTL:

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table border="1"> <tr> <th>书名</th> <th>作者</th> <th>库存</th> <th>操作</th> </tr> <c:forEach items="${bookList}" var="book"> <tr> <td>${book.bookName}</td> <td>${book.author}</td> <td>${book.remainingCount} / ${book.totalCount}</td> <td> <a href="${pageContext.request.contextPath}/book/borrow?bookId=${book.id}">借书</a> </td> </tr> </c:forEach> </table> <c:if test="${empty bookList}"> <p>没有找到对应图书</p> </c:if>

pageEncoding="UTF-8"是第一个守卫,没有它中文直接乱。${pageContext.request.contextPath}负责拼出正确的上下文路径,不管你的 war 叫library还是library-system,页面链接都不会断。${empty bookList}这个判断很便宜,但很多源码忘了加,结果查不到书时页面白屏,老师以为程序崩了。

如果源码的 lib 目录里没有 jstl jar,就别纠结 JSTL 了,直接用<% for (Book b : (List<Book>)request.getAttribute("bookList")) { %>也一样能跑。先跑通,再谈优雅。

4.3 借书事务:INSERT 借阅记录 + UPDATE 库存要放在同一个连接

图书系统的核心业务是借书。常见错误是借书时先SELECT remaining_count,判断大于 0,再UPDATE。两个用户同时借同一本书,两次都读到 1,就会超借。正确做法是让数据库来决定:

Connection conn = DBUtil.getConnection(); try { conn.setAutoCommit(false); PreparedStatement ps1 = conn.prepareStatement( "INSERT INTO borrow(book_id, reader_id, borrow_time) VALUES(?, ?, NOW())"); ps1.setInt(1, bookId); ps1.setInt(2, readerId); ps1.executeUpdate(); PreparedStatement ps2 = conn.prepareStatement( "UPDATE book SET remaining_count = remaining_count - 1 " + "WHERE id = ? AND remaining_count > 0"); ps2.setInt(1, bookId); int rows = ps2.executeUpdate(); if (rows == 0) { throw new SQLException("库存不足"); } conn.commit(); } catch (SQLException e) { conn.rollback(); throw new ServletException("借书失败", e); } finally { conn.setAutoCommit(true); conn.close(); }

这里有两个关键点。第一,remaining_count > 0写在 UPDATE 条件里,而不是先查再改,这样并发下也只有真正有库存的那笔能执行成功。第二,借阅记录插入和库存更新必须在同一个Connection,并且setAutoCommit(false),否则先插入记录、后更新库存失败时会留下脏数据。

你从源码里看到 DBUtil 每次用DriverManager.getConnection,对课程设计足够了。如果以后要上生产环境,就要换成数据库连接池,不然每次请求都新建连接,压测时第一个崩的就是它。

5. 运行与排查:MySQL 连不上、Tomcat 404、中文乱码与 war 部署的 4 个坑

5.1 MySQL 2002 socket 错误:localhost 和 127.0.0.1 不是一回事

现象:启动 Tomcat 后访问页面,控制台报error 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'。

原因:JDBC URL 里写的是localhost,在 Linux 上客户端默认走 Unix socket,而 MySQL 服务端实际监听的 socket 路径可能不在/tmp/mysql.sock。这不是玄学,是驱动和客户端各自带了不同的默认路径。

解决:先用命令行确认服务真的在跑:

mysql -u root -p -e "SELECT VERSION();"

能连上,就去 JDBC URL 里把localhost改成127.0.0.1。改成 IP 后会强制走 TCP 3306 端口,绕开 socket 路径问题。改完重启 Tomcat,这条报错立刻消失。

注意:如果 MySQL 服务根本没启动,先systemctl start mysqld或去 Windows 服务里把 MySQL 启动,再谈连接串。

5.2 Access denied:驱动版本和密码插件两边不匹配

现象:控制台报java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)。

原因:连接串里的用户名密码对不上,或者 MySQL 8 默认的caching_sha2_password驱动不被老 jar 支持,又或者 root 只允许从 localhost socket 登录。

解决:先确认db.properties里密码和mysql -u root -p的密码一致。如果是 MySQL 8,驱动别用太老的 5.1.x,换 8.0 的驱动,并把Class.forName改成com.mysql.cj.jdbc.Driver。

如果不想动 root,就建一个专用账号:

CREATE USER 'library'@'localhost' IDENTIFIED BY '123456'; GRANT ALL PRIVILEGES ON library_db.* TO 'library'@'localhost'; FLUSH PRIVILEGES;

然后把连接串改成username=library。这一步同时解决了安全问题和密码插件问题,比硬改 root 密码靠谱。

5.3 Tomcat 404:先看 context path,再看 Artifacts

现象:IDEA 里点启动,控制台显示Tomcat started,但浏览器访问http://localhost:8080/login.jsp返回 404。

原因:IDEA 的 Run Configuration 里 Application context 默认可能是/,也可能是/library_war_exploded。Servlet 和 JSP 的 URL 都带上 context path,你少写一段就 404。

解决:先确认部署配置。IDEA 里 Run Configurations -> Deployment,选择library-system:war exploded,Application context 改成/library。然后访问:

curl -I "http://localhost:8080/library/login.jsp"

返回 200 就通了。如果还是 404,打开 Artifacts 看 Output Layout,WEB-INF/lib下必须有 mysql 驱动 jar,WEB-INF/classes下必须有编译好的 class。很多人导入工程后 classes 没进 Artifacts,Servlet 一直是空的,Tomcat 当然找不到。

5.4 把 war 放错目录:Nginx 不解析 JSP,Tomcat 才解析

现象:打包好的 war 放到服务器某个目录后,浏览器访问 JSP 页面要么直接下载,要么显示源码,就是没有页面效果。

原因:Nginx 本身不支持 JSP,它只能托管静态资源。JSP 文件必须交给 Tomcat、Jetty 这类 Servlet 容器去编译执行。有人习惯把文件放进 Nginx 的 html 目录,结果自然翻车。

解决:war 要放到 Tomcat 的webapps目录。Tomcat 启动时会自动展开:

cp target/library-system.war $TOMCAT_HOME/webapps/ $TOMCAT_HOME/bin/startup.sh

启动后访问http://localhost:8080/library/,Tomcat 会把 war 解包成目录,JSP 页面也由它负责编译。Tomcat 8.5/9.0 跑传统 jsp+mysql+servlet 项目最稳,别用 Tomcat 10,因为 Servlet 包名从javax换成了jakarta,老源码编译出来的 class 会直接报错。

6. 从跑通到能答辩:Filter 统一编码、备份数据和按业务真跑一遍

6.1 用 EncodingFilter 统一处理编码,避免每个 Servlet 各写一遍

如果你在多个 Servlet 里都写了request.setCharacterEncoding("UTF-8"),不仅重复,还容易漏。正确姿势是写一个 Filter,拦下所有请求:

@WebFilter("/*") public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); chain.doFilter(request, response); } }

/*表示所有路径都经过这个过滤器,包括 JSP 和 Servlet。这个类放到com.library.filter包下,配上@WebFilter注解即可,不用改 web.xml。跑起来后,之前零零散散的中文乱码问题会少一大半。

6.2 答辩前用 mysqldump 备份数据,并按业务真跑一遍

改代码之前,先把数据库备份好,这是你最后悔药:

mysqldump -uroot -p library_db > library_db_backup.sql

备份完再动手改。很多源码改到最后不是代码坏了,是测试数据被反复插入删改搞乱了,备份能让你随时回到干净状态。

交付前我习惯按三条业务线手动跑一遍:管理员登录后新增图书、修改库存;读者注册后登录、查询图书、借书、还书;再回图书列表看库存是否减一、还书后是否加一。别只看页面跳转正常,要打开 MySQL 查borrow和book两张表的数据变化。数据对不上,页面再漂亮也过不了答辩。

我现在拿到任何 JSP 源码,都会先花十分钟看 web.xml 和 SQL 脚本再动手,这个习惯救了我很多次,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询