ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java课程设计实战:Servlet+JSP+MySQL图书管理系统从建表到跑通

Java课程设计实战:Servlet+JSP+MySQL图书管理系统从建表到跑通 简介面向Java初学者的IDEA图书管理系统完整项目覆盖用户注册登录、图书信息管理、借阅归还等核心业务适合课程设计、毕业设计或Java Web入门实践。项目基于Java Web技术栈采用MVC分层架构通过JSP、Servlet和DAO模式实现前后端交互涵盖用户、书籍、借阅等主要功能模块并配有设计模式类图文档便于理解系统模块划分与数据库访问逻辑。资源包共68个文件包含16个Java源码、16个编译后class文件、JSP/HTML页面、CSS/JS静态资源、XML配置以及少量JAR依赖压缩包整体约7.09MB目录结构清晰可导入IDEA后直接运行调试。已有1642人学习下载。借助完整源码、前端页面与配套文档读者可系统掌握使用IDEA搭建Java Web项目、配置Tomcat服务器、连接MySQL数据库以及测试部署的完整流程也能从实际代码中学习Servlet生命周期、数据库操作封装和会话管理技巧是一份适合边学边练的实战参考。1. 图书管理系统Java 课设里最怕的不是写代码是跑不通图书管理系统可能是 Java 课程设计里出场率最高的题目没有之一。它业务简单到三张表就能转起来技术栈又刚好踩在 Java Web 的门槛上Servlet、JSP、JDBC、MySQL全是面试里躲不开的地基。但每年都有大量人不是死在业务逻辑上而是倒在 IDEA 的配置里Tomcat 起不来、MySQL 连不上、一个 404 卡一晚上。这篇笔记按我自己做这个题目的顺序来写先讲清楚为什么默认选 Servlet JSP MySQL 这套组合再从 IDEA 里新建项目一路做到借书、还书跑通最后把最高频的五个坑单独列出来逐个拆。适合正在做 Java Web 课程设计的人也适合想用一个小而完整的系统把 Java 基础语法、面向对象、数据库操作串起来练手的人。2. 选型先行Servlet JSP MySQL 为什么是图书管理系统的默认答案2.1 为什么课程设计默认选 Servlet JSP答辩时讲得清是硬指标图书管理系统拆开看就是图书的增删改查、读者的信息维护、借阅归还这三件事外加两个表之间的关联和一个事务操作。如果直接上 Spring Boot MyBatis-Plus代码量确实能少一半但答辩时老师问“一次查询请求经过了哪几个类”“浏览器发来的参数是怎么进到方法的”如果只能回答“框架自动处理的”这一题基本就悬了。Servlet JSP 的路子有一条肉眼可见的完整链路浏览器发请求 → Tomcat 找到对应的 Servlet → doGet 或 doPost 里收参数 → 调用 DAO → JDBC 操作 MySQL → 结果放回 request → forward 到 JSP 渲染。每一步都能指给老师看代码量不大但每一层都是自己写出来的。这背后其实是 Java 基础能力的缝合。图书管理系统要把面向对象、集合、异常处理、JDBC 连接、请求响应模型全部用一遍做完以后你对“Java 是怎么跑起来”的体感会比刷一百道语法题都实在。Spring Boot 把这些都打包成了黑匣子你只需要写个接口注解但里面的请求分发、参数绑定、事务管理全是框架在替你兜底。课程设计阶段把 Servlet 和 JDBC 吃透之后再看 Spring Boot 源码你会知道它在帮你做什么而不是遇到报错只能搜答案。如果你的项目要求里明确写了“用 Spring Boot”那另说否则我一般建议老老实实走三件套。2.2 版本搭配表JDK、Tomcat、MySQL 与 JDBC 驱动怎么配对版本搭配是图书管理系统里最容易被忽略、但又最值得花十分钟确认的事。很多人的第一个翻车现场不是代码写错而是 JDK 1.8 的机器上配了 Tomcat 10或者 MySQL 8.0 的库配了 5.x 的驱动。下面这张表是我自己反复用过的稳定组合课程设计直接照抄问题最少。组件推荐版本说明JDK1.8如 8u202教学和面试场景最常见的版本兼容性最好IDEA2023 或 2024 社区版 / 正式版社区版免费但建 Java Web 项目要绕一段路正式版自带模板Tomcat9.0.x对应 Servlet 4.0仍使用 javax.servlet 命名空间老教程全部适用Tomcat 备选8.5.x对应 Servlet 3.1老机器和老环境常见用法和 9.0 一致MySQL5.7教学环境最普及InnoDB 支持事务和行锁JDBC 驱动5.1.49配 MySQL 5.7驱动类名是 com.mysql.jdbc.DriverJDBC 驱动备选8.0.x配 MySQL 8.0驱动类名是 com.mysql.cj.jdbc.DriverURL 必须带 serverTimezone这里有个容易踩的版本坑Tomcat 10 把 javax.servlet 整个换成了 jakarta.servlet所有类名都变了。你照着老教程写的import javax.servlet.http.HttpServlet在 Tomcat 10 上直接报 ClassNotFoundException。所以做这个项目我强烈建议别碰 Tomcat 10老老实实用 9.0 或 8.5能省下大量排查时间。另外IDEA 装好后第一次新建项目时很多人跳过了一个关键步骤把项目 SDK 指到 JDK 1.8 的根目录。如果没设置IDEA 可能默认拿内置 JBR 或高版本 JDK编译能过但 Tomcat 和依赖的兼容性就会变得玄学。最好在 File → Project Structure → Project 里确认 SDK 是 1.8Language Level 也选 8。2.3 环境自检三个命令开工前先确认三件套真的能用我自己的习惯是写代码前先花三分钟验证环境这能过滤掉后面至少八成的启动问题。三个命令分别验证 JDK、Tomcat、MySQL 三样东西# 1. 确认 JDK 版本是 1.8输出里能看到 java version 1.8.0_xxx java -version # 2. 确认 Tomcat 版本Windows 用 catalina.batmacOS/Linux 用 catalina.sh /opt/tomcat/bin/catalina.sh version # 3. 确认 MySQL 能登录并查看版本输入密码后输出 5.7.xx mysql -u root -p -e SELECT VERSION();第一个命令的预期输出是java version 1.8.0_202这类如果看到 11、17 或 21说明装了高版本 JDK建议再装一个 1.8 并切换。第二个命令输出 Server version 那一行会显示 Apache Tomcat/9.0.x如果提示 command not found通常是环境变量 PATH 没配直接进 Tomcat 的 bin 目录执行即可。第三个命令如果提示 access denied说明用户名密码不对或者 MySQL 服务没启动Windows 用户可以先用管理员身份打开“服务”确认 MySQL 在运行macOS 可以用 brew services list 查看。这三个命令全部通过后后面不管报什么错你都知道不是环境层面的问题了。3. 用 IDEA 建出能跑的 Java Web 骨架Tomcat、Artifact 与三张核心表3.1 创建 Java Web 工程正式版走模板社区版走 Maven archetypeIDEA 正式版Ultimate和社区版Community在创建 Java Web 项目这件事上差别很大这大概是“idea 运行 javaweb 项目配置”搜索量常年居高不下的原因。正式版自带 Jakarta EE 模板操作路径是 New Project → 左侧选 Jakarta EE → 右侧勾选 Web Application → Application Server 下拉框里选本地 Tomcat 9.0。如果没有现成的选项点 New 按钮把 Tomcat 安装目录加进去。项目生成后自动带 web/WEB-INF 目录和一个 index.jsp你只需补上 lib 目录就行。社区版没有 Jakarta EE 模板但可以用 Maven archetype 走通。New Project → Maven → 勾选 Create from archetype → 选择 org.apache.maven.archetypes:maven-archetype-webapp生成的骨架会包含 src/main/webapp 目录和一个 web.xml。然后要在 pom.xml 里补上 Servlet API 依赖否则代码里 import javax.servlet 会直接标红dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency这里 scope 用 provided原因在于 Tomcat 自带 Servlet 容器实现war 包里如果重复打包 Servlet API启动时反而可能冲突。社区版创建完项目后如果运行配置里找不到 Tomcat Server 入口常见做法是安装 Smart Tomcat 插件或者在 pom.xml 里配 Tomcat Maven 插件。我一般用后者配好后执行一行命令就能把项目跑起来plugin groupIdorg.apache.tomcat.maven/groupId artifactIdtomcat7-maven-plugin/artifactId version2.2/version configuration port8080/port path/book-manage/path uriEncodingUTF-8/uriEncoding /configuration /plugin不管走哪条路建完项目后都要确认三件事一是项目里有 web.xml 或者使用了 Servlet 3.0 的注解配置二是有地方放 MySQL 驱动 jar三是 Artifact 能正确打 war 包。Artifact 配置在 File → Project Structure → Artifacts点加号选 Web Application: Exploded把编译输出和依赖 jar 都加进去。这一步漏了后面就会出现“代码没问题但 Tomcat 跑起来 404”的尴尬局面。3.2 建库建表book、reader、borrow 的字段设计与 SQL 脚本图书管理系统的核心是三张表图书表、读者表、借阅记录表。三张表的关系很直接一个读者可以借多本书一本书可以被多个读者借过所以借阅记录表用外键关联图书和读者。下面这套建表 SQL 我一直在用字段不冗余但足够支撑答辩时的提问。CREATE DATABASE IF NOT EXISTS book_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE book_manager; CREATE TABLE book ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL COMMENT 书名, author VARCHAR(100) NOT NULL COMMENT 作者, publisher VARCHAR(100) DEFAULT COMMENT 出版社, isbn VARCHAR(32) UNIQUE COMMENT ISBN号, category VARCHAR(50) DEFAULT COMMENT 分类, total_stock INT NOT NULL DEFAULT 0 COMMENT 总库存, stock_remain INT NOT NULL DEFAULT 0 COMMENT 当前可借库存, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 上架时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; CREATE TABLE reader ( id INT AUTO_INCREMENT PRIMARY KEY, reader_no VARCHAR(32) UNIQUE NOT NULL COMMENT 学号或工号, name VARCHAR(50) NOT NULL COMMENT 姓名, max_borrow INT NOT NULL DEFAULT 5 COMMENT 最大借阅数量 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT读者表; CREATE TABLE borrow ( id INT AUTO_INCREMENT PRIMARY KEY, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATE NOT NULL COMMENT 借出日期, due_date DATE NOT NULL COMMENT 应还日期, return_date DATE DEFAULT NULL COMMENT 实际归还日期, status TINYINT NOT NULL DEFAULT 0 COMMENT 0借出中1已归还, KEY idx_book_id (book_id), KEY idx_reader_id (reader_id), 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 COMMENT借阅记录表;这里有几个细节值得留意。字符集为什么用 utf8mb4 而不是 utf8MySQL 的 utf8 是 utf8mb3不支持四字节字符遇到生僻字或 emoji 存进去就变问号虽然图书管理系统大概率用不到 emoji但数据库层面一次到位省心。book 表为什么要分 total_stock 和 stock_remain 两个字段total_stock 是这本书一共采购了多少本stock_remain 是现在还有几本可借。借出时只扣 stock_remain还书时加回来总数保持不变。这个设计在答辩时基本必问回答“方便统计每本书总馆藏量和当前可借量同时判断能否继续借出”就是加分项。borrow 表加索引和物理外键则明确告诉老师你知道关联查询需要索引支撑。3.3 JDBC 工具类驱动类名、URL 参数与连接的封装三张表建好后下一步是让 Java 代码能连上 MySQL。这里需要写一个最基础的 JDBC 工具类作用只有一个任何 DAO 需要连接时直接调用DBUtil.getConnection()拿到一个 Connection。工具类本身不复杂但 URL 参数和驱动类名最容易配错。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/book_manager ?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动加载失败确认jar包已放入WEB-INF/lib); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }逐个看参数useSSLfalse是因为本地开发环境 MySQL 没配 SSL 证书不关会有警告甚至连接失败characterEncodingutf8保证 Java 和 MySQL 之间传输中文不乱码serverTimezoneAsia/Shanghai在 MySQL 5.7 和驱动 5.1.49 的组合下不写也能跑但换成 MySQL 8.0 驱动后必须写否则会报时区相关的 SQLException。驱动类名也要和驱动包版本对上5.1.49 用com.mysql.jdbc.Driver8.0.x 用com.mysql.cj.jdbc.Driver。拿 5.x 的代码配 8.x 的 jar报错会让人一头雾水。这个工具类用的是最朴素的 DriverManager每次 getConnection 都新建物理连接性能确实一般但课程设计完全够用。我不建议一上来就上 Druid 或 C3P0 连接池原因很简单如果代码还没跑通连接池报错和业务报错混在一起新手很难分清是配置问题还是自己的代码问题。等核心功能全部跑通了再考虑把 DBUtil 内部替换成连接池对调用方毫无影响这也是封装的价值。4. 图书 CRUD 与借还事务落地从 Servlet 接参到 MySQL 事务的完整链路4.1 图书列表与模糊查询Servlet 接收关键字DAO 用 PreparedStatement 拼条件图书列表是系统第一个要实现的页面它要回答的问题是“有多少书、条件怎么查”。在 DAO 层我习惯用一个方法同时支持全部列表和关键字查询关键字为空时查全部不为空时按书名或作者模糊匹配。JDBC 这层最容易犯的错是写字符串拼接 SQL正确做法是 PreparedStatement 占位符这既防 SQL 注入也避免用户输入的单引号把 SQL 打断。public class BookDAO { public ListBook findByCondition(String keyword) { ListBook list new ArrayList(); String sql SELECT id, title, author, publisher, category, total_stock, stock_remain FROM book WHERE title LIKE ? OR author LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setString(2, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setPublisher(rs.getString(publisher)); b.setCategory(rs.getString(category)); b.setTotalStock(rs.getInt(total_stock)); b.setStockRemain(rs.getInt(stock_remain)); list.add(b); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }关键点有几个。LIKE 查询里%要拼在参数两侧而不是写 SQL 里因为 PreparedStatement 的参数占位符只支持传入完整值。try-with-resources语法保证了 Connection、PreparedStatement、ResultSet 三个资源无论正常执行还是抛异常都会自动关闭这比手动 finally close 简洁得多也能防止连接泄漏。连接泄漏是新手最容易犯的问题每个请求都 new 一个连接忘记关闭的话一段时间后数据库会报 too many connections系统直接卡死。Servlet 层负责收参数、调 DAO、把结果传给 JSPWebServlet(/book/list) public class BookListServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String keyword request.getParameter(keyword); if (keyword null) { keyword ; } BookDAO dao new BookDAO(); ListBook bookList dao.findByCondition(keyword); request.setAttribute(bookList, bookList); request.setAttribute(keyword, keyword); request.getRequestDispatcher(/WEB-INF/jsp/book-list.jsp) .forward(request, response); } }这里的WebServlet(/book/list)是 Servlet 3.0 的注解式映射省去 web.xml 里的servlet-mapping配置。forward 和 sendRedirect 的区别很关键forward 是服务器内部转发浏览器地址栏不变适合查询后带数据去 JSP 渲染sendRedirect 是告诉浏览器重新发一次请求适合新增、删除之后跳回列表页防止刷新时表单重复提交。4.2 新增与删除图书表单回显、POST 提交与重定向刷新新增图书是表单提交的标准场景。JSP 页面里用一个 form 表单收集数据method 必须写 post因为用户输入的长文本走 GET 会拼在 URL 上既不安全还容易超长。前端加 required 做必填校验是基础但后端 Servlet 里也要自己判断一遍因为绕过前端直接 POST 请求是可以做到的。form action%request.getContextPath()%/book/add methodpost input typetext nametitle placeholder书名 required/ input typetext nameauthor placeholder作者 required/ input typetext namepublisher placeholder出版社/ input typetext namecategory placeholder分类/ input typenumber nametotalStock value1 min1 required/ button typesubmit保存/button /formaction 里用%request.getContextPath()%拼上下文路径这样部署到任何 context path 下都不会写死链接。对应 Servlet 的 doPost 方法WebServlet(/book/add) public class BookAddServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String title request.getParameter(title); String author request.getParameter(author); String publisher request.getParameter(publisher); String category request.getParameter(category); int totalStock Integer.parseInt(request.getParameter(totalStock)); Book book new Book(); book.setTitle(title); book.setAuthor(author); book.setPublisher(publisher); book.setCategory(category); book.setTotalStock(totalStock); book.setStockRemain(totalStock); // 新书初始可借数量等于总库存 new BookDAO().insert(book); response.sendRedirect(request.getContextPath() /book/list); } }这里有几个习惯要养成。第一request.setCharacterEncoding(UTF-8)必须在读取任何参数之前调用写在 getParameter 前面才有效否则中文参数进到 Java 里就已经乱码了。第二新增成功后基本不用转发而是 sendRedirect 到列表页因为重定向是一次新的 GET 请求用户按 F5 刷新只会重新加载列表不会再次提交表单。第三Integer.parseInt会在参数不是数字时抛 NumberFormatException课程设计里可以先不写全局异常处理但心里要知道这个边界。如果要做校验回显可以在 catch 里把用户输入的原值 setAttribute 回表单页value 属性里带上用户不用重新打字。删除操作我用 POST 而不是 GET。原因很简单GET 请求的 URL 会被浏览器历史记录、代理服务器存下来如果删图书的接口是/book/delete?id1别人从历史记录里点开就可能误删数据。课程设计答辩时这个细节是加分项老师会问“为什么不用超链接直接删”你能回答“GET 有副作用删除这种写操作应该用 POST 或 DELETE 方法”比埋头写代码强很多。4.3 借书与还书事务边界、行锁与库存扣减顺序借书是整个系统里最有技术含量的一块。一个借书动作要干两件事在 borrow 表插入一条借阅记录同时把 book 表里这本书的 stock_remain 减一。这两条 SQL 必须放在同一个事务里否则就会出现“记录借出了但库存没减”或“库存减了但没记录”的脏数据而且这种错误在演示时很难向老师解释。事务代码用四个动作串起来关自动提交、执行 SQL、提交、失败回滚。public boolean borrowBook(int bookId, int readerId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String lockSql SELECT stock_remain FROM book WHERE id ? FOR UPDATE; try (PreparedStatement ps conn.prepareStatement(lockSql)) { ps.setInt(1, bookId); try (ResultSet rs ps.executeQuery()) { if (!rs.next() || rs.getInt(stock_remain) 0) { conn.rollback(); return false; } } } String insertSql INSERT INTO borrow (book_id, reader_id, borrow_date, due_date, status) VALUES (?, ?, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 30 DAY), 0); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, bookId); ps.setInt(2, readerId); ps.executeUpdate(); } String updateSql UPDATE book SET stock_remain stock_remain - 1 WHERE id ?; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, bookId); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }代码里几个细节值得单独说。SELECT ... FOR UPDATE是行级锁两个并发请求同时借同一本书时第二个请求会等第一个事务提交后再执行防止“库存剩 1 但两个人都借成功”的问题。事务内先锁行再判断库存比先SELECT判断再UPDATE更安全因为普通 SELECT 不加锁时两个事务可能同时读到库存为 1然后都执行扣减变成负库存。DATE_ADD(CURDATE(), INTERVAL 30 DAY)是 MySQL 的函数直接算出应还日期为借出日期加 30 天。finally 块里setAutoCommit(true)这行很多人会漏它的意义在于当前连接如果不是新建的而是从连接池里拿的不重置自动提交状态下一个请求用到这个连接时事务行为就是错的。还书操作是反向逻辑把 borrow 表对应记录的 status 置为 1、return_date 置为当天同时把 book 表的 stock_remain 加一同样包在事务里。这里还要注意一个细节更新 borrow 记录时SQL 里加AND status 0条件如果返回的影响行数是 0说明这条记录不存在或已经还过了直接 rollback 返回提示避免重复还书把库存加多。5. 图书管理系统常见问题排查Tomcat、端口、乱码与驱动四个重灾区5.1 IDEA 里找不到 Tomcat Server 入口新版配置面板去哪了现象照网上教程打开 Run → Edit Configurations点加号以后列表里翻到底也看不到 Tomcat Server。原因有三类一是你在用社区版社区版默认不集成应用服务器需要装 Smart Tomcat 插件或改用 Maven 插件运行二是你用正式版但新建项目时没有选 Jakarta EEIDEA 没把当前项目识别成 Web 项目Tomcat Server 运行类型就不会出现三是新版本 IDEA 把入口藏进了两级菜单需要在加号列表里展开 App Server 子项。解决先确认项目里有 web/WEB-INF/web.xml 或者添加了 Web Facet如果是社区版直接按 3.1 节的 Maven archetype 方案走不要跟正式版的界面较劲。这个入口问题属于典型的“配置面板版本漂移”网上教程截图和你本机不一致时优先看 Edit Configurations 的加号列表里有没有“App Server”或“Smart Tomcat”分类。5.2 启动即报 Cannot start internal HTTP server端口被谁占了现象IDEA 启动项目时弹窗提示 Cannot start internal HTTP server或者控制台报 Port 8080 already in use项目页面打不开。原因这个报错里的 internal HTTP server 有可能是 IDEA 内置服务比如内置浏览器、插件通信用的端口被占用也可能是你自己的 Tomcat 端口被别的进程抢了。遇到这个报错先别怀疑代码99% 是端口问题。解决先换一条命令查出占用端口的进程# Windows输出里看 PID然后到任务管理器结束进程 netstat -ano | findstr :8080 # macOS / Linux lsof -i :8080查到 PID 后结束进程或者直接把 Maven 插件和 Tomcat 端口改成 8081、8082 这类不常用的端口。Tomcat 端口冲突是运行环境里最常遇到的玄学问题和业务代码没有任何关系花十分钟把端口捋顺剩下的时间都该留给功能实现。5.3 页面中文全部变成问号三层编码要统一现象页面标题栏正常但表单提交后存进数据库的中文变成 ??或者查询出来 JSP 页面显示乱码。原因编码断在了三层里的任何一处都会出问题。JSP 页面声明没写contentTypetext/html;charsetUTF-8Servlet 里没调request.setCharacterEncoding(UTF-8)JDBC URL 里少了characterEncodingutf8或者建表时表字符集是 latin1。这不是一处配置的问题是一条链路上的每个环节都要一致。解决按“JSP → Servlet → JDBC → 数据库”的顺序逐一检查。JSP 开头要有完整的 page 指令% page contentTypetext/html;charsetUTF-8 languagejava %Servlet 里请求和响应都要指定编码而且要写在读参数之前。JDBC URL 加上characterEncodingutf8和serverTimezoneAsia/Shanghai。数据库建库时用DEFAULT CHARACTER SET utf8mb4已经建错的表可以ALTER TABLE book CONVERT TO CHARACTER SET utf8mb4。编码问题最忌讳只改一处就测试正确姿势是一次性把三层全部补齐再重启 Tomcat 验证。5.4 ClassNotFoundExceptioncom.mysql.jdbc.Driverjar 没进 Artifact现象Tomcat 启动时或第一次执行数据库查询时抛ClassNotFoundException: com.mysql.jdbc.Driver项目直接 500。原因MySQL 驱动 jar 要么没放在 WEB-INF/lib 下要么放了但 IDEA 的 Artifact 没有把它打进去。第二个情况很容易忽略IDEA 会把你手动添加的 jar 放到 Available Elements 区但只有把它加入 Artifact 输出war 包里才会带上。解决File → Project Structure → Artifacts → 选中当前的 Web 应用 → 左侧 Available Elements 里找到 mysql 驱动 jar → 右键选 Put into Output Root然后重新部署。另外确认一下驱动类和 MySQL 版本匹配5.7 的库用 5.1.49 驱动和com.mysql.jdbc.Driver8.0 的库用 8.x 驱动和com.mysql.cj.jdbc.Driver混着用会报 connection 相关的诡异错误。5.5 浏览器访问 404Application context 与 Artifact 部署路径对不上现象Tomcat 正常启动访问http://localhost:8080/能看到 Tomcat 默认首页但访问http://localhost:8080/book/list就 404控制台也没有任何业务异常。原因你部署的应用上下文路径和你想的不一样。比如 Artifact 部署后默认路径可能是空或者带了一长串项目名导致 Servlet 的映射路径前面拼的 context path 对不上。解决Run → Edit Configurations → Tomcat Server → Deployment 标签选中当前部署的 Artifact把 Application context 改成/book-manage然后访问http://localhost:8080/book-manage/book/list。如果用的 Maven 插件context path 由 pom.xml 里path/book-manage/path决定。这里的规律是最终访问路径 context path Servlet 映射路径。只要这个公式在心里404 就不会是玄学。6. 演示前最后三步登录拦截、演示数据与固定访问路径6.1 加一个 Filter 登录拦截防止演示时被人直接打开后台系统功能全跑通后我一般会加一个最简单的 Filter把图书管理相关的所有路径都拦在登录后面。这一步代码量很小但对演示效果提升很大老师打开系统先看到登录页再从登录进入管理界面整个流程就像个“完整系统”而不是裸奔的增删改查。WebFilter(/book/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; Object admin request.getSession().getAttribute(admin); if (admin null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }登录验证可以在一个 LoginServlet 里完成比对账号密码后session.setAttribute(admin, admin)Filter 里判断 Session 是否存在该属性。配置WebFilter(/book/*)会拦截所有以 /book 开头的访问比如图书列表、新增、借阅操作全部受保护。这样演示时你还能顺势讲一句“我通过 Filter 统一做了登录校验”又是一个答辩加分项。6.2 预置演示数据与固定访问路径把演示节奏握在自己手里演示前我习惯往数据库里预置十本中文图书、三五个读者、两条借阅记录其中一条保持“借出中”状态。这样打开列表页数据是饱满的点进借阅记录能看到状态字段的差异演示完新增和还书功能后列表页的数据变化也很直观。不要指望现场敲数据演示现场打字浪费时间还容易暴露异常没有处理的边界情况。预置数据的 SQL 写在一个 init-data.sql 里和建表 SQL 放在一起随时可以重新执行。访问路径固定为http://localhost:8080/book-manage/login.jsp把 Tomcat 端口和 context path 稳定下来不要每次演示前临时改配置。浏览器建议提前打开一个带完整书签的窗口避免现场手输 URL 手滑出错。演示顺序我一般这样走登录 → 图书列表 → 按关键字查一本书 → 新增一本 → 借出 → 回到列表确认库存减一 → 还书 → 确认库存恢复。这个顺序覆盖了整个系统的核心链路每一步都能看到数据变化老师想打断提问也基本会落在已经准备好的事务、防注入、Filter 这几个点上。我自己养成的一个习惯是任何关于依赖、端口、字符集的改动都先重启 Tomcat 再看页面而不是改完代码直接刷新浏览器然后怀疑 IDEA。很多课设现场翻车不是功能没写完是没把运行环境当成程序的一部分去维护。你把这个系统完整做完一遍Servlet、JSP、JDBC、事务这几块就有了实感后面再接触 Spring Boot 或 MyBatis 源码时心态会完全不一样——你知道它们在替你做什么而不是只会对着报错复制粘贴。希望帮到你。本文还有配套的精品资源点击获取
返回列表