简介这是一套基于jspservletjavabean以及MVC模式开发的JavaWeb图书管理系统项目主要面向需要完成课程设计、毕业设计或系统学习JavaWeb开发的学生与初级开发者。系统围绕图书借阅与归还场景实现图书信息管理、读者借阅、归还处理、借阅记录查看等核心功能数据库采用MySQL 5开发环境可选用Eclipse或MyEclipse搭配JDK1.7与Tomcat8即可按运行文档部署启动。压缩包约8.74MB包含项目源代码、SQL脚本、运行文档、配套论文以及答辩PPT等材料既有可直接运行的Demo也有撰写课设报告所需的说明与答辩展示内容。代码已实际运行验证可以帮助读者快速理解Servlet、JSP、JavaBean、MySQL之间的协作方式并参考其分层思想完成类似管理系统。运行文档可指导环境配置与启动配套论文与答辩PPT则可作为报告撰写和答辩展示的参考。目前已有1321人浏览学习适合作为JavaWeb课程设计或毕设项目的起步模板。1. 先别急着解压这个JavaWeb图书管理系统到底能做什么如果你正在找课程设计或期末项目这个「java图书管理系统 servletjspmysql」的压缩包大概率会出现在搜索结果前列。它不是前后端分离的时髦项目而是把 Servlet 当控制层、JSP 当页面、MySQL 当存储的传统 JavaWeb 三层系统核心业务就是学生借书、还书、查记录这三件事。适合两类人一类是刚学完 JavaWeb想找一个完整项目把 Servlet 生命周期、会话、JDBC、事务这些散装知识点串起来另一类是课设截止日期临近需要一个能跑通、能讲清楚、最好连文档报告都齐活的项目交差。先说清楚它解决什么问题一个可运行的图书借阅闭环外加一份能支撑答辩的文档报告。下面我从技术拆解、部署路径、核心代码到高频故障把它一层层讲透。2. Servlet、JSP、MySQL各管什么三层架构拆解与为什么课设用它2.1 Servlet、JSP、MySQL在请求链路里的真实分工先看一次借书操作完整走过了什么浏览器提交表单请求进 Tomcat容器根据 URL 找到对应的 ServletServlet 负责把表单参数解析出来调用业务逻辑再把结果放到 request 作用域里forward 给 JSPJSP 渲染成 HTML 返回给浏览器而整个过程中数据从哪来、改到哪去都在 MySQL 里。三个组件各管一段边界非常清楚。Servlet 是控制层。它继承 HttpServlet重写 doGet 或 doPost再通过WebServlet注解Servlet 3.0 开始支持或者 web.xml 注册一个访问路径。你要记住的关键点是Servlet 里不写 SQL也不拼 HTML它只做「收参数、调方法、决定下一步跳哪」。判断一个项目中 Servlet 写得好不好的最简单标准就是看 doGet/doPost 里有没有大段业务逻辑和 SQL 字符串。JSP 是视图层。很多新手会把 JSP 当成一个独立的页面技术其实 JSP 的本质上还是一个 Servlet——第一次被访问时Tomcat 会把它编译成 .java再编译成 .class。你在 JSP 里写% %本质就是在 Java 类里拼字符串输出。它的正确定位是渲染从 request 里 getAttribute 取出 Servlet 放好的数据用 EL 表达式和 JSTL 标签呈现出来而不是在里面写 JDBC。MySQL 是数据层。Java 侧通过 JDBC 和它打交道先拿 Connection再创建 PreparedStatement执行 executeQuery 或 executeUpdate最后在 finally 里释放资源。这个项目里所有关于「库存够不够」「借了多久」「还了没有」的问题最后都变成 SQL 里的条件判断。下面这段代码是项目里最常见的「查询单本图书并跳转借阅页」的最小实现几乎所有 Servlet 的写法都是从这个模板扩出来的WebServlet(/borrow) public class BorrowServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String bookId request.getParameter(bookId); request.setAttribute(bookId, bookId); request.getRequestDispatcher(/borrow.jsp).forward(request, response); } }逻辑说明WebServlet(/borrow)把这个类注册到/borrow路径上浏览器访问http://localhost:8080/项目名/borrow?bookId1就会进这里的 doGet。常见做法是先用 getParameter 把前端传来的参数接住再放到 request 作用域里带向 JSP最后用 forward 完成服务端跳转。注意 forward 有个特点——浏览器地址栏不会变刷新页面会把这个请求再执行一遍这在答辩里经常被问到。参数说明request.setCharacterEncoding(UTF-8)必须在第一次 getParameter 之前调用否则 POST 提交的中文会乱码这个坑第 5 章会专门展开。getRequestDispatcher(/borrow.jsp)里的路径是项目内的 JSP 路径forward 是服务端内部转发和sendRedirect的浏览器重定向是两回事后者地址栏会变而且是一次新的请求。2.2 为什么这个组合在课设里比 Spring Boot 更容易讲深你肯定想过这个问题现在企业里写 JavaWeb 基本都是 Spring Boot课设为什么还在用 ServletJSP我的看法是ServletJSP 这个组合在教学场景里价值很大它把请求生命周期、会话管理、事务边界这些最底层的东西全部暴露在你面前没有框架替你包住。Spring Boot 的问题在于「黑匣子」太多内嵌 Tomcat 是自动配置的连接池是自动装配的MyBatis 的 Mapper 是扫描进去的你跑起来很容易但面试官问一句「一个请求从进 Tomcat 到返回中间经过了哪些对象」你就容易卡住。ServletJSP 项目里没有这些东西你在 web.xml 或者注解里配置的每一个路径映射、每一个 Filter、每一个监听器都是你自己的决定问起来你都能答。这个项目里常见的反面例子是在 JSP 里直接写 JDBC 查询。这样确实出得了数据页面也能跑但这种写法答辩时会被一句话问住「你的事务怎么控制的」因为 JSP 页面里执行 SQL根本无法保证多个数据操作的原子性。我一般建议拿到源码后先做分层检查Servlet 里只调 ServiceService 里只调 DAODAO 里只做 JDBC 操作JSP 里只读 request 里的数据。如果源码本身没有严格分层这就是第 6 章你要做的第一个改造。MySQL 在这个项目里通常是 5.7 或 8.0 两个版本它们的驱动类名和连接串写法有差异。别小看这个差异网上大量旧教程还在用 5.7 的com.mysql.jdbc.Driver按 MySQL 8.0 的安装教程配好环境之后直接复制旧代码启动就会报 ClassNotFoundException。这是运行期最典型的问题具体怎么排查放在第 5 章。3. 把源码跑起来JDKTomcatMySQL的环境配对与最小部署路径3.1 环境版本配对JDK 8、Tomcat 8.5/9、MySQL 5.7还是8.0拿到 zip 之后的第一件事不是解压而是把运行环境定下来。ServletJSPMySQL 这个技术组合对版本很敏感版本配错会出现一些看起来很「玄学」的错其实都是驱动、容器、Java 版本三方不匹配造成的。我一般推荐下面这组稳定搭配组件推荐版本说明JDK1.8Java 8老 JavaWeb 项目的默认运行时兼容性最好别直接上 JDK 21Tomcat8.5 或 9.08.5 对应 Servlet 3.19.0 对应 Servlet 4.0都支持WebServlet注解MySQL5.7 或 8.05.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.DriverIDEIntelliJ IDEA Community / Eclipse社区版免费功能足够跑通本项目为什么推荐 JDK 8因为 ServletJSP 项目大多是老结构很多用到了 JDK 8 才有的语法特性而且 tomcat 插件、某些依赖在高版本 JDK 下会有模块化限制。JDK 装上之后「java环境变量配置详细教程」里反复强调的 JAVA_HOME 和 PATH 才是多数人翻车的地方——只装了 JDK 不配环境变量命令行里java -version都不认。配完之后把%JAVA_HOME%\bin加进 PATH再开一个新命令行窗口验证。MySQL 的版本选择会直接影响 JDBC 驱动类和连接串写法。5.7 是经典版驱动类名还是com.mysql.jdbc.Driver8.0 以后改名成com.mysql.cj.jdbc.Driver而且默认启用 SSL 校验。如果你按最新的 MySQL 8.0 安装教程装好了库但代码里还写着老驱动名启动时就会直接报 ClassNotFoundException。建议先在命令行里执行mysql --version确认版本再决定后面连接配置怎么写。3.2 导入源码后先做三件事看目录、找连接配置、确认部署方式解压之后不要急着开 IDE先看三样东西第一项目里有没有pom.xml有就是 Maven 管理的 Web 项目没有大概率是纯 Eclipse/IDEA 的普通 Web 工程第二数据库连接配置写在哪个文件里常见位置是src/db.properties、DBUtil.java或者jdbc.properties第三压缩包附件里有没有 .sql 建库脚本有的话后面建库会省很多事。数据库连接配置是第一个要改的地方。最常见的形态是这样一段代码// DBUtil.java 里的连接配置不同项目变量名略有差异 Class.forName(com.mysql.jdbc.Driver); String url jdbc:mysql://localhost:3306/library?useSSLfalsecharacterEncodingutf8; String user root; String password 123456; // 改成你自己 MySQL 的密码逻辑说明Class.forName是加载驱动类的老写法5.7 版本用com.mysql.jdbc.Driver8.0 必须改成com.mysql.cj.jdbc.Driver。URL 里的localhost:3306指向本机 MySQLlibrary是数据库名。useSSLfalse关掉 SSL 握手能避免 8.0 驱动默认开启 SSL 之后报公钥检索错误characterEncodingutf8保证连接层面的中文编码正确。参数说明user和password改成你本机 MySQL 的实际账号密码。注意一个细节——如果你的密码里包含字符在 URL 里会变成参数分隔符把后面的characterEncodingutf8吞掉这种密码要整体做 URL 编码或者干脆换一个纯数字字母的密码课设场景不值得为这个纠结。3.3 建库与启动两条路径看你拿到的项目是不是Maven数据库连接配置改好之后接下来建库。压缩包自带的 .sql 脚本通常包含建库、建表、插入测试数据三部分优先用它-- 优先执行压缩包里的建库脚本 CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4; USE library; SOURCE /绝对路径/你的脚本名.sql;逻辑说明CREATE DATABASE IF NOT EXISTS加IF NOT EXISTS是防止重复执行报错DEFAULT CHARACTER SET utf8mb4让整个库支持中文和 emoji 特殊符号避免存储中文变成问号。SOURCE是 MySQL 命令行的导入指令路径要写绝对路径Windows 下注意用/而不是\。启动方式分两种。如果项目里有pom.xml最省事的是用 Maven 插件直接起内嵌 Tomcat# 路径一Maven 管理的 Web 项目直接起内嵌 Tomcat mvn tomcat7:run # 启动后访问 http://localhost:8080/项目名/如果项目是普通 Web 工程没有 Maven 结构就打包成 war 手动丢进 Tomcat# 路径二普通 Web 项目打包后手动部署 mvn package cp target/library.war /path/to/apache-tomcat-9.0.x/webapps/ sh /path/to/apache-tomcat-9.0.x/bin/startup.sh # 启动后访问 http://localhost:8080/library/逻辑说明mvn tomcat7:run里的tomcat7是插件名不代表只能用 Tomcat 7它实际能跑在 8.5/9.0 上。手动部署时war 包名决定访问路径——library.war就对应http://localhost:8080/library/想换路径就改 war 包名。启动失败先看logs/catalina.out端口被占会直接在这个文件里报错。提示如果压缩包自带 .sql 文件一定优先用它建库不要照着文档手敲建表语句手敲容易漏字段后面运行报「Unknown column」你还要回头对文档纯属浪费时间。4. 借阅归还的代码实现表结构、事务边界与PreparedStatement4.1 三张表的设计学生、图书、借阅记录怎么关联图书管理系统的核心数据就三块谁学生、借什么图书、借没还记录。所有功能都是围绕这三张表展开的。下面是这个项目最常见的表结构字段注释可以直接拿去做文档报告里的数据字典CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, status TINYINT DEFAULT 1 COMMENT 1可借 0禁借 ); CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) COMMENT ISBN号, title VARCHAR(100) NOT NULL COMMENT 书名, total INT NOT NULL COMMENT 馆藏总数, available INT NOT NULL COMMENT 当前可借数 ); CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL COMMENT 学生ID, book_id INT NOT NULL COMMENT 图书ID, borrow_date DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 借出时间, due_date DATETIME COMMENT 应还时间, return_date DATETIME COMMENT 实际归还时间, status TINYINT DEFAULT 0 COMMENT 0借出中 1已还, FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (book_id) REFERENCES book(id) );逻辑说明学生表和图书表通过borrow_record建立多对多关系——一个学生可以借多本书一本书可以被多个学生借过所以中间表里存两边的外键。borrow_date用DEFAULT CURRENT_TIMESTAMP让数据库自动写当前时间少写一行 Java 代码。due_date是应还时间一般在借书时用DATE_ADD加 30 天算出来后面超期判断就靠它和return_date比大小。参数说明book表里total和available是一对冗余字段一个存总数一个存可借数借书扣available还书加available。更规范的设计是只存total用的时候通过total减去某个时间窗口的借出数实时算但课设里保留冗余字段能让查询更快、代码更直白答辩时讲清楚这个取舍就行。4.2 借书操作扣库存和写记录为什么必须在同一个事务里借书这件事代码上不只是执行一条 INSERT 那么简单。完整流程是先校验学生能不能借再查这本书可借数是不是大于 0然后把available减一最后插入一条借阅记录。这里面最要命的是第三步和第四步——如果库存扣了但记录没写上这本书就凭空消失了反过来记录写了但库存没扣系统就会允许超借。所以这两步必须放在同一个事务里要么全成功要么全回滚。在没有 Spring 管事务的 ServletJSP 项目里事务要用手写。这个手写过程就是答辩时的高频考点Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交事务从这里开始 // 1. 校验学生状态 // 2. 查询 book 表 available 是否大于 0 // 3. 扣减库存UPDATE book SET available available - 1 WHERE id ? AND available 0 int updated ps.executeUpdate(); if (updated 0) { throw new SQLException(库存不足扣减失败); } // 4. 插入借阅记录INSERT INTO borrow_record (student_id, book_id, due_date) // VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY)) conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任一步失败库存和记录一起回滚 throw new ServletException(借书失败, e); } finally { DBUtil.close(conn); // 释放连接归还到连接池 }逻辑说明setAutoCommit(false)是整个事务的开关。关闭自动提交之后后面每一步 SQL 都不会真正落库只有执行到commit()才一次性生效。UPDATE ... WHERE id ? AND available 0是把「扣库存」和「校验库存是否够」合并成一条 SQL——如果可借数已经是 0这个 UPDATE 影响 0 行代码通过executeUpdate()的返回值判断出来并抛异常这是防止并发超借的关键。参数说明due_date用DATE_ADD(NOW(), INTERVAL 30 DAY)在数据库层计算出 30 天后的应还时间比在 Java 里new Date()再拼字符串更不容易出错。conn.rollback()必须放在 catch 里一旦第 3 步成功、第 4 步失败回滚会把第 3 步扣掉的库存恢复。finally 里DBUtil.close(conn)不能省——如果这个连接来自连接池close 是把它归还给池子而不是真的断开。4.3 还书操作幂等处理和库存回补还书比借书简单查这条借阅记录把return_date写成当前时间status改成 1再把这本书的available加一。但有个细节必须处理就是「重复还书」——用户在页面上双击提交或者刷新了确认页面同一笔记录被还两次库存就会被加两次。处理方式是在 UPDATE 的 WHERE 条件里加一个状态限制// 还书第一步更新借阅记录状态 String sql UPDATE borrow_record SET return_date NOW(), status 1 WHERE id ? AND status 0; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, recordId); if (ps.executeUpdate() 0) { throw new IllegalStateException(该记录不存在或已归还不能重复还书); } } // 还书第二步把图书可借数加一 String sql2 UPDATE book b SET b.available b.available 1 WHERE b.id (SELECT book_id FROM borrow_record WHERE id ?);逻辑说明第一个 UPDATE 里WHERE status 0保证了只有「借出中」的记录才能被还。executeUpdate()返回 0 行说明这条记录要么不存在、要么已经还过直接抛异常这就实现了幂等——同一请求重复执行结果一致不会把库存加两次。第二个 UPDATE 用子查询从borrow_record里拿book_id而不是前端直接传 bookId是为了防止调用方拿一个没借过的书来刷库存。参数说明PreparedStatement的占位符?从 1 开始计数ps.setInt(1, recordId)就是把第一个占位符替换成recordId。这里用try-with-resources的写法——JDK 7 以后PreparedStatement实现了AutoCloseable写在 try 的括号里就能自动关闭省掉 finally 里的ps.close()。注意返回的rows是受影响行数不是查询结果集这是新手最容易误解的地方。5. 避坑排查让JavaWeb项目运行期翻车的5个常见问题5.1 MySQL SSL连接错误8.0和5.7的驱动名、连接串完全不一样现象按网上的旧教程配好代码启动后报ClassNotFoundException: com.mysql.jdbc.Driver或者报SSLConnectionError、Public Key Retrieval is not allowed连接直接失败。原因MySQL 8.0 之后驱动类名改成了com.mysql.cj.jdbc.Driver旧类名还在但只能兼容旧版本同时 8.0 驱动默认开启 SSL 校验而 5.7 时代的老驱动没这么严格于是网上十年前的老连接串直接复制过来就翻车。解决先确认本机 MySQL 版本。8.0 就把驱动类名换成com.mysql.cj.jdbc.Driver并在 URL 里加两个参数useSSLfalseallowPublicKeyRetrievaltrue。第一句关掉 SSL 握手第二句允许获取公钥这两个参数是 8.0 驱动连本地库最常用的配置。5.7 保持老写法不动。建议在 DBUtil 里加一段日志打印conn.getClass().getName()能快速确认连接到底建没建立。5.2 启动后404war包名、Artifact和Servlet映射三者必须对齐现象Tomcat 正常启动但访问http://localhost:8080/项目名/直接 404或者访问某个具体功能 404首页却打得开。原因90% 是部署路径没对上。Maven 打包出来的 war 包名是library.war访问路径就是/library但你在 IDEA 里配置的 Artifact 名字可能是library_war或者自定义的名字两者不一致就 404。另外如果WEB-INF/classes里没有编译后的.class文件Servlet 根本没被加载Tomcat 不会报错只会给你一个 404。解决先看webapps目录下实际部署的文件夹名访问路径必须和它一致。再看项目的WEB-INF/classes里有没有对应包名下的.class文件没有就去 IDEA 的 Project Structure 里重新配置 Artifact把编译输出目录指到WEB-INF/classes。最后打开 Tomcat 的logs/catalina.out搜Deploying web application确认部署时间点有没有报错。5.3 中文乱码请求、响应、数据库三层编码各管一段现象页面显示中文正常但表单提交到 Servlet 后request.getParameter(name)拿到的全是问号或者数据库里存进去的中文直接变成乱码。原因Tomcat 8 及以上版本默认的 URL 编码已经是 UTF-8但 POST 表单体的解析编码默认不是——在第一次读取参数之前没有request.setCharacterEncoding(UTF-8)Tomcat 就按 ISO-8859-1 解码。另一个常见原因是 JDBC URL 里没带characterEncodingutf8连接握手时用了 MySQL 默认的 latin1。解决在 Servlet 的 doGet/doPost 第一行加request.setCharacterEncoding(UTF-8)响应之前加response.setCharacterEncoding(UTF-8)——这不是玄学是先设置后取值的基本顺序。数据库侧JDBC URL 补上characterEncodingutf8建表时统一用 utf8mb4。代码量不多但每个 Servlet 都写一遍容易漏第 6 章我会讲怎么用 Filter 一次性解决。5.4 JSP路径和Servlet映射冲突WEB-INF下的页面不能直接访问现象index.jsp在浏览器里能打开但点击按钮跳到/borrowBook就 404或者两个 Servlet 的WebServlet配了同一个路径Tomcat 启动时只打一行警告请求落到了错误的 Servlet 上。原因JSP 文件放在项目根目录可以直接被访问但放在WEB-INF目录下的 JSP 只能通过 Servlet 的forward跳转浏览器直接访问是 404这是 Tomcat 的保护机制。路径冲突则是复制粘贴代码时最常见的错误——第二个WebServlet注解没有改路径。解决表单的action属性建议写成${pageContext.request.contextPath}/borrowBook这样不管项目部署名怎么改路径都能正确拼接不用手写/library/borrowBook这种带死路径的写法。排查时打开 Tomcat 启动日志搜Duplicate mapping或The servlet named开头的警告这类不报错的问题最隐蔽。5.5 连接池耗尽跑一会就卡死连接没关闭是JavaWeb最常见的坑现象系统刚开始能跑用几分钟后所有请求开始报Connection is not available或Connections could not be acquired重启 Tomcat 又好了过一会又卡死。原因代码里拿了 Connection 却没关。每来一个请求就DBUtil.getConnection()请求结束没执行close()连接池里的连接被一个个泄漏掉最后池子被占满新请求拿不到连接。这个坑在本地调试时不容易暴露因为开发环境压力小池子还没空就重启了上线之后才集中爆发。解决规约一条——Connection、PreparedStatement、ResultSet 全都按顺序关闭优先用 try-with-resources。JDK 7 以后这三种资源都实现了AutoCloseable直接写在 try 的括号里就能自动释放try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { try (ResultSet rs ps.executeQuery()) { while (rs.next()) { // 处理结果集 } } } catch (SQLException e) { // 连接、Statement、ResultSet 全部自动关闭 }逻辑说明外层 try 管理 Connection 和 PreparedStatement内层 try 管理 ResultSet离开各自 try 块时按逆序自动关闭。如果连接来自连接池close()的真实行为是归还到池子不是物理断开所以这一步不用担心连接真正被关掉。养成这个习惯之后连接泄漏类问题基本可以绝迹。6. 从「能跑」到「能答辩」三个低成本改造和一份改动清单照着上面的步骤跑通项目只完成了第一步。想让这个课设项目在答辩时有东西可讲我建议做三个低成本改造每个都不需要动大手术但都能在「你改了什么、为什么改」这个环节给你加分。第一个改造是加一个编码过滤器把散落在每个 Servlet 里的setCharacterEncoding(UTF-8)收口成一段统一代码WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); chain.doFilter(request, response); } }逻辑说明/*匹配所有路径包括 Servlet 和 JSP这样所有请求都在进入业务代码之前就完成了编码设置。chain.doFilter这行必须保留它是把请求放行给下一层的开关漏掉的话所有请求都会被拦截死在 Filter 里。改造完之后原 Servlet 里那些 setCharacterEncoding 全都可以删掉代码干净一截答辩时讲「我用 Filter 统一了编码处理」比讲「我每个 Servlet 里都写了编码」更像工程实践。第二个改造是给 Service 层加事务控制。现在的代码如果是直接在 Servlet 里调多个 DAO 方法每个 DAO 各拿各的连接第 4 章说的事务边界就形同虚设。常见做法是用ThreadLocalConnection在同一个线程内共享同一个连接让同一个请求里的多次数据库操作落在同一个事务上。这个改动的代码量不大但能让你把「事务传播」这个概念讲出层次感。第三个改造是把 JSP 里的% %脚本片段替换成 JSTL EL。比如循环显示图书列表用c:forEach items${bookList} varbook替代 Java 的 for 循环页面整洁答辩时还能顺带说出 JSP 九个内置对象和你对 Servlet 生命周期的一点理解——这些都是 java 面试题里高频出现的基础课设阶段讲透一次后面面试复习省一半力气。最后准备一份改动清单不用长三行就够改了什么、为什么改、影响哪些页面。答辩时先讲原项目的功能再讲你的改造重点说事务控制和并发超借的边界处理。我当年第一次做这类项目把业务逻辑全写在 JSP 里数据能出来答辩老师问了一句「还书的时候库存已经是 0 你怎么处理」就把我问住了。后来我把这种边界场景写进文档报告的测试用例里才算把项目讲圆。希望帮到你。本文还有配套的精品资源点击获取