
简介这是一份采用JSPServletMySQL经典三层架构开发的图书馆图书管理系统完整源码主要面向计算机相关专业完成课程设计、毕业设计或希望掌握Java Web开发流程的初学者。系统内置管理员、学生、游客三类角色管理员可登录后台进行用户管理、图书信息维护和罚款缴纳学生可完成图书借阅、归还、查询个人借阅记录及修改密码游客可浏览全部图书并查看详情。压缩包共112个文件包含45个Java源文件覆盖Servlet控制器、DAO数据访问层、实体类与登录过滤器24个JSP页面负责前台展示和后台管理界面另有37张PNG图片、2个GIF动图、PDF/MD说明文档等整体仅1.76MB轻量易部署。目前已有263人学习下载借助这份源码可快速理清ServletJSPJDBC的典型分层、请求转发、登录过滤与数据增删改查等核心实现是一套既可运行演示又可二次拓展的Web项目参考。1. 从图书馆管理系统源码.zip 到本机跑通这 30 分钟值得花你手上这份基于 jspmysqlservlet 的 JSP 图书馆图书管理系统源码.zip解压前是个压缩包解压后其实是一整套 JavaWeb 课程设计作业的骨架子管理员登录、图书增删改查、读者借还书SQL 建表脚本和 Tomcat 部署配置都藏在里面。这类源码最大的价值不是直接交作业而是让你在半小时内看明白 JSP 怎么发请求、Servlet 怎么接参数、MySQL 怎么落数据。适合三类人被毕设题目卡住的在校生、想从 Spring Boot 回头补 JavaWeb 基础的开发者、以及需要给客户演示管理系统的外围实施。别急着双击 jsp 文件先按下面的顺序把 MySQL 和 Tomcat 搭起来。2. 先看懂 jspmysqlservlet 的分工JSP 是脸面Servlet 是入口MySQL 是账本2.1 Servlet 生命周期init 只跑一次service 每次请求都跑很多源码包里把 Servlet 和 JSP 混在一起讲实际拆开就三条JSP 负责 HTML 输出Servlet 负责接表单参数、调 JDBC、控制跳转MySQL 负责数据落地。Servlet 生命周期不是面试题是你排查 404 和空数据的依据。老式源码里最常见的配置是 web.xml 手动映射 Servletservlet servlet-nameBookServlet/servlet-name servlet-classcom.library.servlet.BookServlet/servlet-class load-on-startup1/load-on-startup /servlet servlet-mapping servlet-nameBookServlet/servlet-name url-pattern/book/url-pattern /servlet-mapping这段配置里servlet-name只是 IDservlet-class才是真正干活儿的 Java 类。url-pattern/book/url-pattern表示只要请求命中/library/bookTomcat 就把它交给 BookServlet。load-on-startup让它随应用启动就初始化不写也问题不大只是第一次访问才创建。Servlet 是单例多线程的init()在生命周期里只跑一次service()每个请求都会跑。所以不要在doGet或doPost里去修改共享成员变量否则并发借书时容易把库存扣出负数。这就是为什么后面说事务要用数据库条件更新而不是先查再改。2.2 一条“查书”请求穿过 JSP、Servlet、MySQL 的完整链条拿到源码后先找表单里的 action。常见写法是curl -X POST http://localhost:8080/library/book \ --data actionquerykeyword三体这个actionquery是 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”。常见回答不是“我不会”而是“这个选型对课程设计够用结构足够清晰”。对比点jspmysqlservletSpring Boot Thymeleaf依赖管理靠 lib 目录放 jarMaven/Gradle 管理坐标页面技术JSP 由 Tomcat 编译模板引擎直接渲染路由方式web.xml 或 WebServletController RequestMapping数据库访问JDBC 手写JdbcTemplate / MyBatis / JPA适合场景教学、实训、课程设计企业项目、快速迭代Spring Boot 2 也能集成 JSP但要额外配视图前缀后缀而且很多老源码连 Maven 结构都没有硬迁成本远高于重写。除非你要做线上商用否则先跑通这份 jspmysqlservlet 源码是理解 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-INFWEB-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和.projectIDEA 会把它当 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 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, phone VARCHAR(20) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 里的utf8mb4_general_ci在 MySQL 5.7 和 8.0 都能用兼容性最好。remaining_count单独存是为了查询列表时不用算total_count - 已借数量性能更好但牺牲了一点一致性后面借书事务要靠它做条件更新。数据库建好后把 JDBC 连接串也改掉。源码里的数据库配置通常在db.properties或DBUtil.javadrivercom.mysql.jdbc.Driver urljdbc:mysql://127.0.0.1:3306/library_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot 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(); ListBook 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 contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 tr th书名/th th作者/th th库存/th th操作/th /tr c:forEach items${bookList} varbook 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:ifpageEncodingUTF-8是第一个守卫没有它中文直接乱。${pageContext.request.contextPath}负责拼出正确的上下文路径不管你的 war 叫library还是library-system页面链接都不会断。${empty bookList}这个判断很便宜但很多源码忘了加结果查不到书时页面白屏老师以为程序崩了。如果源码的 lib 目录里没有 jstl jar就别纠结 JSTL 了直接用% for (Book b : (ListBook)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): Cant 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 rootlocalhost (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 librarylocalhost IDENTIFIED BY 123456; GRANT ALL PRIVILEGES ON library_db.* TO librarylocalhost; FLUSH PRIVILEGES;然后把连接串改成usernamelibrary。这一步同时解决了安全问题和密码插件问题比硬改 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 explodedApplication context 改成/library。然后访问curl -I http://localhost:8080/library/login.jsp返回 200 就通了。如果还是 404打开 Artifacts 看 Output LayoutWEB-INF/lib下必须有 mysql 驱动 jarWEB-INF/classes下必须有编译好的 class。很多人导入工程后 classes 没进 ArtifactsServlet 一直是空的Tomcat 当然找不到。5.4 把 war 放错目录Nginx 不解析 JSPTomcat 才解析现象打包好的 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 跑传统 jspmysqlservlet 项目最稳别用 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;charsetUTF-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 脚本再动手这个习惯救了我很多次希望帮到你。本文还有配套的精品资源点击获取