
简介面向Java Web初学者这份图书管理项目源码以图书信息增删改查为主线完整整合了JSP、JDBC、MySQL与Servlet技术栈演示了从页面展示、请求处理到数据库读写的基本路径适合用来理解MVC分层与原生Web开发流程。压缩包共95个文件核心代码以17个Java类、13个HTML页面、11个CSS样式、5个XML配置、3个JS脚本和1个SQL数据库脚本为主另含若干jpg、png等图片素材整体仅1.7MB目录结构清晰便于按模块对照学习。学习者可结合web.xml中的Servlet映射、JSP页面表单提交和JDBC数据库连接逐段查看登录验证、图书添加删除等功能的请求流转掌握PreparedStatement执行SQL、结果集处理等写法同时理解Servlet负责控制、JSP负责视图展示的分工。目前已有275人学习下载对希望打通Java Web开发流程、为后续框架学习打基础的人来说是一份轻量而完整的实战参考资料。1. 从 ZIP 到能演示的图书管理项目这套老技术栈为何还没过时如果你手里拿到的是一份名为“jspjdbcmysqlservlet图书管理项目源码.zip”的压缩包大概率是课程设计、毕业设计或者某些企业内部遗留系统的交付物。第一次解压的人通常会遇到两个极端要么觉得太旧了想直接换成 Spring Boot要么照着网上教程一步步配却在 Tomcat、MySQL、JDBC 驱动之间卡住一整天。我的建议是先别急着替换技术栈这个组合在“快速跑通一个可演示的 CRUD 系统”这件事上仍然是最短路径之一。JSP 负责页面渲染Servlet 处理请求转发JDBC 直接操作 MySQL四者分工清晰、调试链路短特别适合用来理解 Java Web 最底层的请求-响应模型。你不需要引入 Maven、Spring 容器和大量注解就能看到一次完整的数据从浏览器到数据库再返回的旅程。本文会从解压源码开始一路走到配置环境、改造连接池、处理乱码和部署上线帮你在半天内把这个项目变成能演示、能答辩、能迁移的可用系统。2. 四个组件如何分工先想清楚 JSP、JDBC、MySQL、Servlet 各自的边界2.1 Servlet 是控制器别把业务逻辑堆在 JSP 里很多老的图书管理项目里写代码的人喜欢直接在 JSP 页面里写 Java 脚本片段也就是% ... %里连数据库、写判断、拼 HTML。这种写法在源码包中非常常见尤其是早期教学项目。它的缺点是当页面和业务逻辑耦合在一起改一个查询条件就得在 JSP 里翻半天。我更推荐的做法是把 Servlet 当作唯一的入口浏览器请求/listBookServlet 的doGet里调用 DAO 层查询数据然后把结果放进request.setAttribute(bookList, list)最后forward给book_list.jsp。JSP 里只负责用 EL 表达式${book.name}和 JSTL 标签c:forEach展示数据。Servlet 的核心是继承HttpServlet并重写doGet/doPost。如果你发现项目里的 Servlet 类还在用service()方法一把梭建议重构为doGet和doPost分离这样以后接 POST 表单和 GET 链接跳转时会清晰很多。另外Servlet 的 URL 映射要避开 JSP 页面名比如/addBook就不要映射到addBook.jsp否则很容易造成逻辑循环。常见错误是把 Servlet 的WebServlet注解漏了导致访问路径直接 404——源码包里如果是 web.xml 配置方式记得检查servlet-mapping是否成对。2.2 JDBC 只做数据库桥梁连接管理是第一个性能分水岭JDBCJava Database Connectivity是 Java 操作数据库的标准接口不管中间用没用连接池最终都要落到DriverManager.getConnection()或DataSource.getConnection()上。图书管理项目里最常见的 JDBC 写法是在每个 DAO 方法里Class.forName(com.mysql.cj.jdbc.Driver)然后建立连接、执行 SQL、关闭连接。这种写法在低并发演示场景下没问题但如果同一时间有几十个人访问数据库会频繁创建和销毁连接很快出现Too many connections错误。我在重构这类项目时第一步永远是封装一个 JDBCUtil 工具类把驱动注册、连接获取、资源关闭统一起来。如果要进一步优化第二步是把 JDBCUtil 里的DriverManager换成 Druid 或 HikariCP 连接池。注意MySQL 8.x 以后驱动类名变成了com.mysql.cj.jdbc.Driver如果是 MySQL 5.7 及以下驱动名是com.mysql.jdbc.Driver。连接 URL 里必须带上useSSLfalse和serverTimezoneAsia/Shanghai前者避免 MySQL 8 的 SSL 握手警告后者解决时区导致的日期时间报错。很多人卡在这一步报错信息是The server time zone value Öйú±ê׼ʱ¼ä is unrecognized就是没设置时区。2.3 MySQL 表结构设计图书管理至少要这几张表图书管理系统的核心不是代码而是表设计。拿到的源码包如果附带了book.sql或library.sql直接导入即可如果没有你需要自己建。一套能跑通增删改查的图书表字段不需要多但要有代表性。我一般会建三张表图书分类表book_type、图书信息表book、借阅记录表borrow_record外加一张管理员用户表admin_user。图书表至少要包含主键id、书名name、作者author、出版社publisher、ISBN、库存数量、分类外键。如果还要做登录功能管理员表必须有username和password密码最好存 MD5 或 SHA-256 的散列值不要明文存储。建表时注意字符集要统一为utf8mb4排序规则用utf8mb4_general_ci或utf8mb4_unicode_ci。很多乱码问题的根源就是建表语句里写了DEFAULT CHARSETutf8而 MySQL 8 默认字符集已经是utf8mb4两者混用后中文会变成问号。如果你从源码包里拿到的 SQL 文件是老格式建议在导入前用文本编辑器把CHARSETutf8批量替换为CHARSETutf8mb4。另外关联查询依赖外键但外键约束在生产库中会拖慢插入速度图书管理这种小项目可以只在逻辑上关联不建物理外键。2.4 JSP 负责渲染EL 和 JSTL 能让你少写一半脚本JSP 本质是一个被 Tomcat 编译成 Servlet 的模板文件它最大的坑是“看着像 HTML实则能写 Java”。新手容易在 JSP 里嵌入% for(...) { %循环输出表格这不但让页面难以维护而且当数据为空时还会抛异常。正确姿势是引入 JSTL 标签库在 JSP 页面顶部加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %然后用c:forEach items${bookList} varbook遍历列表用c:if test${empty bookList}处理空集合。还需要注意 JSP 的 page 指令里的pageEncoding它必须与文件保存时的编码一致。如果源码包里 JSP 文件是 GBK 编码而你在 IDEA 中改成了 UTF-8页面标题和按钮文字全变乱码。我在处理这类项目时会先把所有 JSP 文件用 IDEA 的批量转码功能统一转成 UTF-8然后把每个文件头部的contentTypetext/html; charsetUTF-8和pageEncodingUTF-8统一。这一步做完乱码问题基本能解决八成。3. 从 ZIP 到可运行导入 IDEA 并配置 Tomcat 与 MySQL 的完整步骤3.1 解压后先看目录结构、lib、配置文件、SQL 脚本收到源码包后别急着双击.iml或.project文件先解压并展开目录确认四样东西是否存在。第一是src目录下有没有com/xxx/...的包结构这是 Java 源码的位置第二是WebContent或web目录下有没有WEB-INF/web.xml第三是lib目录里有没有mysql-connector-java的 jar 包第四是根目录有没有.sql脚本。如果这四样都在导入成功率会很高。如果缺了 lib 里的 jar 包需要去 MySQL 官网下载对应版本的 Connector/J但注意不要下载最新的 8.0.33 去跑 MySQL 5.7 项目驱动版本和数据库版本兼容性要提前确认。还有一个容易被忽略的文件是.classpathEclipse 项目或pom.xmlMaven 项目。如果是纯 zip 包且没有 Maven导入 IDEA 时选择“Import Project”并指定为 Eclipse 项目或直接选择源码目录。IDEA 识别出 Web 项目后需要手动添加 Web Facet。操作路径是右键项目 - Open Module Settings - Facets - 点 - Web - 设置Web Resource Directory为WebContent或webWeb Module Deployment Descriptor为WEB-INF/web.xml。设置完后Artifacts 里才会出现可以部署到 Tomcat 的 war exploded 包。3.2 创建数据库并导入数据mysql 命令行与 Navicat 两种方式拿到 SQL 脚本后有两种导入方式。如果你已经安装并配置好了 MySQL最简单的是在命令行中执行mysql -uroot -p输入密码后在 MySQL 交互中执行CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library; source D:/path/to/library.sql;注意source后面的路径不要带中文否则会读取失败。如果你用的是 Navicat操作更直观新建连接 - 右键“新建数据库” - 数据库名填写library字符集选utf8mb4排序规则选utf8mb4_general_ci- 双击进入该数据库 - 右键“运行 SQL 文件” - 选择library.sql并开始执行。执行成功后再到“表”列表里检查是否生成了book、admin_user等表。这里有一个很关键的细节SQL 脚本里如果带了USE database;语句你用 Navicat 运行时相当于手动切换了库如果没有带你必须在“运行 SQL 文件”前先双击选中目标库否则表会建到默认的test库里。判断是否导入成功不要只看左下角“成功”字样去刷新表列表并双击book表看有没有数据。很多时候脚本导入成功但只有表结构、没有数据后续页面列表永远是空的这会让人误以为代码有 bug。3.3 在 IDEA 中配置 Tomcat 与 Artifact解决“找不到类”和“404”问题IDEA 配置 Tomcat 的入口是右上角下拉框 - Edit Configurations - 点 - Tomcat Server - Local。如果没有 Tomcat 选项说明 IDE 没有安装 Tomcat 插件或者你还没在本地安装 Tomcat。解压一个 Apache Tomcat 8.5 或 9.0 到任意目录然后在 IDEA 的 Application Servers 里指定 Tomcat 主目录。Tomcat 版本与 JDK 版本有对应关系JDK 8 配 Tomcat 8.5/9 顺手JDK 11 配 Tomcat 9JDK 17 则要谨慎很多老项目在 JDK 17 下会因为模块化限制无法使用反射直接跑不起来。配置完成后点击 Deployment 标签页点 选择 Artifact再选中xxx:war exploded。这个 war exploded 是解压模式它允许 JSP 和静态资源热更新不用每次修改都重启 Tomcat。如果部署列表是空的说明你还没创建 Artifact。创建方式Project Structure - Artifacts - 点 - Web Application: Exploded - 从 Modules 中选择项目。还有个小技巧在 Server 标签页把 On frame deactivation 设置为 Update resources这样你改 JSP 后切回浏览器Tomcat 会自动把最新页面推上去省去手动重启。常见错误“404”分两种一种是访问根路径/时 404那是因为没有设置默认欢迎页或者 index.jsp 不在 web 根目录下另一种是访问/book/list时 404那是因为 Servlet 映射路径不对。把 IDEA 控制台里 Tomcat 启动日志拉到最底部看Deployment of web application archive ... has finished和INFO: Starting ProtocolHandler这两行确认没有异常。启动后打开浏览器输入http://localhost:8080/项目上下文路径/上下文路径在 Deployment 标签页里的 Application context 设置默认是/项目名_war_exploded建议改成/library方便演示。4. JDBC 连接与增删改查图书管理核心功能的代码拆解4.1 写一个 JDBCUtil 工具类把驱动注册和获取连接收敛到一处不管源码包里的代码怎么写的我建议你先新建一个com.library.util.JDBCUtil工具类把自己写的或原有的连接逻辑统一管理。最小实现如下package com.library.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class JDBCUtil { private static final String URL jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动加载失败请检查lib目录); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps ! null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }这段代码的逻辑很直白静态代码块负责加载驱动getConnection从DriverManager获取连接close方法按 ResultSet - PreparedStatement - Connection 的顺序逆序关闭资源。参数说明里最重要的是 URL 上的四个参数。useUnicodetruecharacterEncodingUTF-8保证中文以 UTF-8 传输useSSLfalse关闭 SSL 握手避免 MySQL 8 控制台刷大量 warnserverTimezoneAsia/Shanghai解决时区问题allowPublicKeyRetrievaltrue解决 MySQL 8 在非 SSL 连接下Public Key Retrieval is not allowed的报错。如果项目连的是 MySQL 5.7驱动类名要改回com.mysql.jdbc.Driver同时不需要allowPublicKeyRetrieval。4.2 图书列表页的 Servlet JSP 联动从 request 到 EL 输出图书列表是最典型的“查全表”场景。在 Servlet 中调用 DAO把 List 放进 request 域再转发到 JSP。Servlet 代码package com.library.servlet; import com.library.dao.BookDao; import com.library.entity.Book; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; WebServlet(/listBook) public class ListBookServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); BookDao dao new BookDao(); ListBook bookList dao.findAll(); request.setAttribute(bookList, bookList); request.getRequestDispatcher(/book_list.jsp).forward(request, response); } }逻辑说明WebServlet注解让此 Servlet 响应/listBook请求。findAll()返回数据库里的全部图书request.setAttribute把 List 绑定到请求作用域forward将请求转发给 JSP转发与重定向的区别是转发后浏览器 URL 不变且 request 里的属性在 JSP 中仍能访问。对应 JSP 只需要一段循环c:forEach items${bookList} varbook tr td${book.bookId}/td td${book.name}/td td${book.author}/td tda hrefbookDetail?bookId${book.bookId}详情/a/td /tr /c:forEach注意这里book.name对应Book实体类的getName()方法。如果 JSP 里items${bookList}输出空先检查 Servlet 里的setAttribute键名是否与 JSP 一致再检查 DAO 层的findAll()是否返回了 null。还有一个常见坑Book里的字段如果叫author数据库列名是author但实体类里写成了authorName那 EL 输出就会空白这个问题只能通过逐字段比对解决。4.3 实现图书新增、编辑和删除PreparedStatement 防注入是底线老项目里常看到用Statement拼接字符串写 SQL比如Statement stmt conn.createStatement(); String sql SELECT * FROM book WHERE name bookName ;这种写法在图书管理这种演示项目里也许能跑通但只要读者输入一个 OR 11就能把所有数据带出来。正确做法是使用PreparedStatement占位符public int addBook(Book book) { String sql INSERT INTO book (name, author, publisher, isbn, stock) VALUES (?, ?, ?, ?, ?); try (Connection conn JDBCUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, book.getName()); ps.setString(2, book.getAuthor()); ps.setString(3, book.getPublisher()); ps.setString(4, book.getIsbn()); ps.setInt(5, book.getStock()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } }这里的 try-with-resources 语法是 JDK 7 之后提供的它可以自动关闭实现了AutoCloseable的连接和语句对象省去了手动close的冗余代码。executeUpdate()返回受影响的行数大于 0 表示插入成功。删除和更新只是 SQL 不同套路完全一致。一个容易忽略的细节是参数绑定顺序必须与 SQL 中?的顺序一致否则数据会写错列。另外如果你的book表主键是自增的插入后想拿到自增 ID可以加一个参数Statement.RETURN_GENERATED_KEYS然后通过ps.getGeneratedKeys()获取。4.4 分页查询的三种写法和一个推荐实现图书管理项目做到列表展示后分页几乎是必经之路。常见做法有三种。第一种是LIMIT ?, ?每页查询代码简单但每次都要全表扫描第二种是先COUNT(*)得到总页数再查当前页这也是教学项目里最通用的做法第三种是使用 MySQL 8 的窗口函数ROW_NUMBER() OVER做通用分页但兼容性较差。我推荐第二种因为它逻辑直观而且性能足够应付几千条图书数据。public ListBook findByPage(int pageNum, int pageSize) { String countSql SELECT COUNT(*) FROM book; String querySql SELECT * FROM book ORDER BY book_id DESC LIMIT ?, ?; try (Connection conn JDBCUtil.getConnection(); PreparedStatement countPs conn.prepareStatement(countSql); PreparedStatement queryPs conn.prepareStatement(querySql)) { // 先查总记录数 ResultSet countRs countPs.executeQuery(); int totalCount 0; if (countRs.next()) { totalCount countRs.getInt(1); } // 计算总页数 int totalPages (int) Math.ceil(totalCount * 1.0 / pageSize); // 绑定分页参数 queryPs.setInt(1, (pageNum - 1) * pageSize); queryPs.setInt(2, pageSize); ResultSet rs queryPs.executeQuery(); ListBook list new ArrayList(); while (rs.next()) { Book book new Book(); book.setBookId(rs.getInt(book_id)); book.setName(rs.getString(name)); book.setAuthor(rs.getString(author)); book.setStock(rs.getInt(stock)); list.add(book); } return list; } catch (SQLException e) { e.printStackTrace(); return null; } }参数说明pageNum从 1 开始LIMIT ?, ?的第一个数是起始行偏移量所以(pageNum - 1) * pageSize。如果页码传入负数需要先做防御性判断。前端 JSP 里只需两个链接上一页和下一页把pageNum参数带在 URL 上。需要注意的是排序字段book_id DESC如果你改了表名或字段名记得同步修改。还有一种“假分页”是先把所有数据查出来再在内存里截取子列表这种写在小项目里也能看但数据量到 10 万条时内存就扛不住了不建议学习期使用。5. 图书管理项目避坑指南从环境变量到中文乱码的 5 个高频翻车点5.1 现象Tomcat 启动报 ClassNotFoundException 找不到 mysql-connector-java启动 Tomcat 时控制台出现java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或者页面访问时 500 异常提示包不存在。原因是 MySQL 驱动 jar 没有放到 Tomcat 能加载的 ClassPath 里。IDEA 部署 war exploded 时默认会把WEB-INF/lib下的 jar 复制到 Tomcat 的运行目录但如果你把 jar 手动放到了 Tomcat 的lib目录而 IDEA 部署的是项目的WEB-INF/lib两者不一致就会相互覆盖。解决方法是把mysql-connector-java-8.0.x.jar放到项目的WebContent/WEB-INF/lib下然后在 IDEA 的 Project Structure - Libraries 中确认该 jar 已被添加到模块依赖。如果你用的是 Tomcat 8.5注意不要同时把驱动放在 Tomcat 全局 lib 和项目 lib 里否则类加载器不同可能导致奇怪的 “ac cess denied” 权限异常。5.2 现象页面中文全是问号数据库里也是乱码先打开数据库命令行执行SHOW VARIABLES LIKE character_set%;把看到的结果与 JSP 文件的编码做对比。如果数据库连接结果是latin1说明数据库或表建库时字符集没指定为utf8mb4。解决办法分三步第一步修改JDBCUtil中的 URL加入characterEncodingUTF-8第二步执行ALTER DATABASE library CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;并重建数据表第三步在 JSP 页面顶部写% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %。有一个隐蔽问题Servlet 里用request.getParameter(name)获取表单中文时如果没有在doPost开头写request.setCharacterEncoding(UTF-8)拿到的一律是迟到乱码。因为 POST 请求的编码由浏览器发送的内容类型决定而setCharacterEncoding必须在读取任何参数之前调用才有效。5.3 现象修改 JSP 后不生效浏览器永远显示旧页面第一种可能是浏览器缓存。按 CtrlF5 强制刷新仍然旧说明 Tomcat 实际编译的 JSP 没有更新。此时打开 Tomcat 的工作目录在 IDEA 里一般是tomcat安装目录/work/Catalina/localhost/项目名/org/apache/jsp删除整个org子目录后重启。第二种可能是你修改的是web目录下的 JSP而 IDEA 部署的 Artifact 指向的是out/artifacts下的副本。检查 Project Structure - Artifacts - Output directory确保 Web Resource Directory 下的源路径映射正确。第三种是文件编码问题IDEA 有时显示的是 UTF-8但实际文件是 GBK改动保存后再编译导致乱码把 IDEA 的File - Settings - File Encodings全部改为 UTF-8并勾选 “Transparent native-to-ascii conversion”。5.4 现象JDBC 连接 MySQL 8.x 报 Public Key Retrieval 错误错误信息是Caused by: java.sql.SQLException: Public Key Retrieval is not allowed。这个只发生在 MySQL 8.0 的默认认证插件caching_sha2_password上当客户端使用useSSLfalse时服务器要求客户端通过 RSA 公钥加密传输密码但默认不允许自动获取公钥。解决方式有两种一是在 URL 上添加allowPublicKeyRetrievaltrue二是把 MySQL 用户认证插件改成mysql_native_password执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;两种我都试过开发环境用第一种更省事因为改用户插件会影响以后用其他客户端连接。需要注意的是如果你的项目部署在 Linux 服务器上连接串里的localhost要改成数据库服务器的内网 IP而不是写localhost否则 MySQL 会按 socket 连接而不是 TCP 连接导致Cant connect to local MySQL server through socket /tmp/mysql.sock这个经典报错。5.5 现象导出 WAR 部署到 Linux 后无法连接数据库本地 IDEA 能跑打包成war放到 Tomcat/webapps 下后解压自动部署但访问连接数据库超时或拒绝连接。原因通常是数据库连接串写死了localhost而 Linux 上的 MySQL 监听在3306端口且仅允许特定主机连接。排查思路先执行netstat -tlnp | grep 3306看 MySQL 是否监听外网地址再检查连接 URL 里的主机是否为服务器内网 IP最后用mysql -h192.168.x.x -uroot -p在服务器本机测试能否远程连接。另一个细节是防火墙云服务器安全组或firewalld没放行 3306 端口导致应用与数据库在跨机器通信时握手失败。此外WAR 包部署时IDEA 打包不会包含WEB-INF/lib其实会包含但如果你用的 Maven 项目没有把 scope 是provided的依赖打进去就需要手动确认mysql-connector-java的 jar 是否在WEB-INF/lib下。用jar tf xxx.war | grep mysql检查一下比一遍遍重启 Tomcat 更快。6. 让项目跳出“课程设计”水平连接池、登录过滤器与日志如果图书管理项目只是查表和增删改查那它充其量是“能用”。想让它能在答辩或真实环境中站得住脚至少要做三件事引入连接池、加一个登录校验的 Filter、记录操作日志。连接池首选 Druid因为它自带监控页面适合演示时直接打开控制台展示 SQL 执行次数。改造方式很简单在WEB-INF/lib加入druid-1.2.x.jar在JDBCUtil中改为private static DruidDataSource dataSource new DruidDataSource(); static { dataSource.setUrl(URL); dataSource.setUsername(USER); dataSource.setPassword(PASSWORD); dataSource.setInitialSize(5); dataSource.setMaxActive(10); dataSource.setMinEvictableIdleTimeMillis(30000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }Druid 的initialSize5表示启动时预建 5 个连接maxActive10表示最大并发连接数。图书管理系统的并发量一般不超过 50设置 10-20 即可。连接池的好处是复用连接不再反复创建销毁数据库压力明显下降。演示时访问http://localhost:8080/项目名/druid/index.html可以看到 SQL 监控这会成为答辩时的加分项。登录过滤器很好理解如果没有登录访问图书列表或新增页面时自动跳转到登录页。实现一个LoginFilterWebFilter(/*) 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(false); String uri request.getRequestURI(); boolean isLoginPage uri.endsWith(login.jsp) || uri.contains(loginServlet); boolean loggedIn session ! null session.getAttribute(adminUser) ! null; if (isLoginPage || loggedIn) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }这段代码里request.getSession(false)不会强制创建会话避免每个请求都生成新的 session 造成内存泄漏。日志记录则可以用一个最简单的 Servlet Filter在doFilter前后记录请求路径和处理耗时或者引入 Log4j2 打印到控制台。演示时如果能在控制台看到每条 SQL 的日志面试或答辩官会对你的工程化意识刮目相看。这三个改进做完项目依然保持 JSPServlet 的轻量结构但你已经掌握了连接池、过滤器和 Web 请求生命周期。以后迁移到 Spring Boot 时你会发现这些概念完全通用。我个人的习惯是拿到任何老项目源码第一步不是急着跑通而是先梳理请求流程和数据库连接方式因为这两个位置决定了系统的性能与会话安全性。希望这些踩过坑的经验能帮你把这份图书管理源码变成真正属于自己的实战项目。本文还有配套的精品资源点击获取