
简介本资源是一款基于JSPServletTomcat技术栈开发的电子图书管理系统完整源码面向Java Web初学者与高校课程设计实践者解决图书信息在线管理、用户交互与前后端协同开发等典型教学场景问题。压缩包共50个文件总计2.77MB包含22个Java业务逻辑类如登录验证、借阅处理等Servlet、11个XML配置文件含web.xml及MyBatis数据库映射、8个JSP页面覆盖首页、图书增删改查、用户注册登录等核心视图辅以CSS、JS、Bootstrap组件及标准项目结构src、webapp、.idea等目录齐全。已有123人学习下载源码结构规范、模块职责清晰具备完整MVC分层设计可直接导入IntelliJ IDEA或Eclipse运行配套readme.txt提供部署说明是理解Java Web基础架构与工程化实践的理想入门级实战项目。1. 为什么还在用 JSPServletTomcat 做电子图书管理系统不是过时而是“够用、可控、可教、可查”你点开这个标题大概率不是想重写一个豆瓣读书——而是手头正卡在一门《Java Web 程序设计》课程设计、毕业设计初稿被导师打回、或者公司老系统要临时补个图书借阅模块。JSPServletTomcat 这套组合在 Spring Boot 满天飞的今天听起来像古董但现实是它仍是高校 Java Web 教学的事实标准栈也是中小单位内部管理类系统最常被要求“必须用原生技术实现”的硬性边界。它不追求高并发、不玩微服务、不搞前后端分离但它能让你从 request.getParameter() 开始亲手串起 HTTP 请求怎么进、业务逻辑怎么转、HTML 怎么吐、session 怎么存、数据库怎么连、错误怎么拦——整条链路透明、无黑盒、可打断点、可单步追踪。本篇不讲“为什么淘汰”只讲“怎么把它跑通、调稳、交得出去”。全文基于真实部署场景Windows/Linux 双环境验证、MySQL 8.0 兼容、Tomcat 9.0 安全加固、JDK 11 编译兼容、所有源码结构按教学评审规范组织含 web.xml 显式配置、无注解替代、包名带学校/学号前缀。如果你需要的是一个能直接导入 IDEA/Eclipse、改两行数据库连接就能运行、答辩时能现场演示增删改查登录校验图书分类统计的完整方案这篇就是为你写的。2. 从零搭起最小可运行骨架Tomcat 部署、项目结构初始化与第一个 HelloBook Servlet2.1 Tomcat 安装与 JDK 兼容性确认别让环境先翻车JDK 版本和 Tomcat 版本错配是新手第一道墙。当前主流教学环境是JDK 11 Tomcat 9.0.x非 10因 Servlet 4.0 规范对部分老教材示例有兼容性断裂。Tomcat 9.0.x 官方明确支持 JDK 8–13但实测 JDK 17 在部分 Windows 旧版 IDE 中会触发java.lang.UnsupportedClassVersionError。安全做法是下载apache-tomcat-9.0.83.zip2023 年末稳定版非最新 9.0.87因后者对某些国产杀毒软件误报率升高解压后进入bin目录双击startup.batWindows或执行./startup.shLinux观察控制台是否输出Server startup in [xxx] milliseconds关键验证命令在终端中执行# 检查 JAVA_HOME 是否指向 JDK 11不是 JRE echo $JAVA_HOME # 输出应类似/usr/lib/jvm/java-11-openjdk-amd64Linux或 C:\Program Files\Java\jdk-11.0.20Windows # 检查 javac 版本编译器版本决定 class 文件兼容性 javac -version # 必须输出 11.x.x若为 17.x.x需切换 JDK 或重装提示IDEA 中配置 Tomcat 时务必在Run Configuration → Server → JRE里手动指定为 JDK 11不要选“Project default”否则即使系统环境变量正确IDE 内部仍可能用错 JDK。2.2 手动创建标准 Web 项目结构拒绝向导生成的“黑盒”很多学生用 IDEA 的 “Web Application” 向导建项目结果发现web.xml缺失、lib目录不自动识别、src下包路径混乱。教学评审明确要求“结构清晰、配置显式”。我们手动构建路径以E:\book-system为例book-system/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/example/book/ # 包名必须含公司/学校标识避免 com.book 这类泛化名 │ │ ├── servlet/ │ │ │ └── HelloBookServlet.java # 第一个 Servlet │ │ ├── dao/ │ │ │ └── BookDao.java # 数据访问层 │ │ └── model/ │ │ └── Book.java # 实体类 │ └── resources/ │ └── db.properties # 数据库配置非硬编码 ├── web/ │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置文件必须存在且合法 │ │ └── lib/ │ │ ├── mysql-connector-java-8.0.33.jar # MySQL 8.0 驱动关键用 5.1.x 会连不上 │ │ └── jstl-1.2.jar # JSTL 支持用于 c:forEach 等标签 │ └── index.jsp # 入口页面 └── pom.xml (可选若用 Maven)注意web/目录即传统 WAR 包的根目录WEB-INF/web.xml是 Servlet 2.5 规范强制要求的部署描述符。即使你后续用注解也建议保留并配置welcome-file-list这是教学答辩时“结构规范性”的硬指标。2.3 写出第一个 ServletHelloBookServlet.java 与 web.xml 映射HelloBookServlet.java不是打印 Hello World而是返回图书列表页的跳转指令体现 MVC 分离思想// src/main/java/com/example/book/servlet/HelloBookServlet.java package com.example.book.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class HelloBookServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 模拟从 DAO 获取数据实际会调用 BookDao.listAll() ListString bookTitles new ArrayList(); bookTitles.add(Java编程思想); bookTitles.add(深入理解计算机系统); bookTitles.add(数据库系统概念); // 将数据存入 request 域供 JSP 使用 req.setAttribute(bookList, bookTitles); // 转发到 index.jsp不是重定向转发保持 request 域数据 req.getRequestDispatcher(/index.jsp).forward(req, resp); } }对应web/WEB-INF/web.xml中必须声明该 Servlet 及其映射!-- web/WEB-INF/web.xml -- ?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- Servlet 定义 -- servlet servlet-nameHelloBookServlet/servlet-name servlet-classcom.example.book.servlet.HelloBookServlet/servlet-class /servlet !-- URL 映射 -- servlet-mapping servlet-nameHelloBookServlet/servlet-name url-pattern/hello/url-pattern !-- 访问 http://localhost:8080/book-system/hello -- /servlet-mapping !-- 默认欢迎页 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app逻辑说明doGet()处理 GET 请求req.setAttribute()将数据放入 request 作用域生命周期仅限本次请求forward()是服务器端跳转浏览器地址栏不变且 request 域数据可传递url-pattern/hello/url-pattern定义了外部可访问路径这是 Servlet 的入口契约。2.4 index.jsp用 JSTL 展示图书列表告别 scriptletindex.jsp不再写% while(...) { %这类易出错的 Java 脚本而用 JSTL 标签库需提前引入jstl-1.2.jar%-- web/index.jsp --% % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title电子图书管理系统 - 首页/title /head body h1 图书列表/h1 c:choose c:when test${empty bookList} p暂无图书数据/p /c:when c:otherwise ul c:forEach items${bookList} vartitle varStatusstatus li${status.count}. ${title}/li /c:forEach /ul /c:otherwise /c:choose a hrefhello刷新图书列表/a /body /html参数说明c:forEach的items绑定 request 域中的bookListvartitle是每次迭代的元素别名varStatusstatus提供循环状态对象status.count从 1 开始计数比status.index更符合中文习惯c:choose是 if-else 的结构化写法避免嵌套c:if。此写法通过了 OWASP ZAP 的基础 XSS 扫描因 EL 表达式默认 HTML 转义。3. 数据库驱动落地MySQL 8.0 连接、Book 表设计与 DAO 层封装3.1 MySQL 8.0 配置要点时区、密码认证插件与远程访问很多同学卡在“连接被拒绝”根源不在代码而在 MySQL 服务端配置。针对教学环境常见问题时区问题MySQL 8.0 默认时区为SYSTEM但 Tomcat 启动时若未指定-Duser.timezoneGMT%2B8会导致NOW()函数时间错乱。解决方案是在my.cnfWindows 下为my.ini的[mysqld]段添加default-time-zone 08:00密码认证插件变更MySQL 8.0 默认使用caching_sha2_password而mysql-connector-java-5.1.x不支持。必须用8.0.33驱动并在 JDBC URL 中显式指定# src/main/resources/db.properties jdbc.urljdbc:mysql://localhost:3306/bookdb?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.passwordyour_password关键参数serverTimezoneAsia/Shanghai解决时区allowPublicKeyRetrievaltrue允许公钥检索绕过caching_sha2_password握手限制useSSLfalse在本地开发环境关闭 SSL生产环境必须开启。远程访问限制若需从其他机器访问如小组协作演示执行 SQL 授权CREATE USER bookuser% IDENTIFIED BY Book2024; GRANT SELECT,INSERT,UPDATE,DELETE ON bookdb.* TO bookuser%; FLUSH PRIVILEGES;3.2 Book 表结构设计兼顾教学性与实用性教学系统不必过度设计但需覆盖主键、外键、索引、非空约束等核心概念。bookdb库下建表CREATE DATABASE IF NOT EXISTS bookdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE bookdb; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, isbn VARCHAR(17) NOT NULL UNIQUE COMMENT ISBN-13格式如978-7-04-050694-7, title VARCHAR(200) NOT NULL COMMENT 书名, author VARCHAR(100) NOT NULL COMMENT 作者, publisher VARCHAR(100) COMMENT 出版社, publish_date DATE COMMENT 出版日期, price DECIMAL(8,2) DEFAULT 0.00 COMMENT 定价, category ENUM(计算机,文学,经济,教育,艺术) NOT NULL DEFAULT 计算机 COMMENT 分类, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, INDEX idx_category (category), -- 分类查询加速 INDEX idx_author (author) -- 作者模糊查询加速 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书信息表;设计理由isbn用VARCHAR(17)精确匹配 ISBN-13 标准含分隔符ENUM类型强制分类值域避免脏数据INDEX为高频查询字段建索引utf8mb4支持 emoji 和生僻字如人名“䶮”COMMENT字段是教学文档检查项。3.3 BookDao.javaJDBC 模板化封装规避资源泄漏DAO 层不写业务逻辑只做 CRUD。关键点Connection 必须 try-with-resources 自动关闭ResultSet/Statement 不可遗漏// src/main/java/com/example/book/dao/BookDao.java package com.example.book.dao; import com.example.book.model.Book; import javax.sql.DataSource; import java.sql.*; import java.util.ArrayList; import java.util.List; public class BookDao { private DataSource dataSource; // 由 ServletContextListener 初始化 public BookDao(DataSource dataSource) { this.dataSource dataSource; } // 查询全部图书带分页参数为后续扩展留接口 public ListBook listAll(int offset, int limit) throws SQLException { String sql SELECT id, isbn, title, author, publisher, publish_date, price, category, stock FROM book ORDER BY id DESC LIMIT ?, ?; ListBook books new ArrayList(); try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, offset); ps.setInt(2, limit); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setIsbn(rs.getString(isbn)); book.setTitle(rs.getString(title)); book.setAuthor(rs.getString(author)); book.setPublisher(rs.getString(publisher)); book.setPublishDate(rs.getDate(publish_date)); book.setPrice(rs.getBigDecimal(price)); book.setCategory(rs.getString(category)); book.setStock(rs.getInt(stock)); books.add(book); } } } return books; } // 根据 ID 查询单本 public Book findById(int id) throws SQLException { String sql SELECT * FROM book WHERE id ?; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setIsbn(rs.getString(isbn)); book.setTitle(rs.getString(title)); // ... 其他字段赋值 return book; } } } return null; } // 新增图书返回自增主键 public int insert(Book book) throws SQLException { String sql INSERT INTO book (isbn, title, author, publisher, publish_date, price, category, stock) VALUES (?, ?, ?, ?, ?, ?, ?, ?); try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, book.getIsbn()); ps.setString(2, book.getTitle()); ps.setString(3, book.getAuthor()); ps.setString(4, book.getPublisher()); ps.setDate(5, book.getPublishDate() ! null ? new java.sql.Date(book.getPublishDate().getTime()) : null); ps.setBigDecimal(6, book.getPrice()); ps.setString(7, book.getCategory()); ps.setInt(8, book.getStock()); int affected ps.executeUpdate(); if (affected 0) throw new SQLException(插入失败无记录影响); try (ResultSet rs ps.getGeneratedKeys()) { if (rs.next()) { return rs.getInt(1); // 返回新生成的 id } } } return -1; } }关键细节PreparedStatement防止 SQL 注入try-with-resources确保Connection、PreparedStatement、ResultSet三层嵌套自动关闭Statement.RETURN_GENERATED_KEYS获取自增 IDjava.sql.Date与java.util.Date转换需显式处理new java.sql.Date(utilDate.getTime())ps.setDate()参数不能为 null需判空。4. 用户登录与权限控制Session 管理、MD5 密码加密与角色拦截4.1 LoginServlet接收表单、校验密码、写入 Session登录不是简单比对明文需加盐哈希。教学系统用MD5 固定盐值非生产推荐但满足课程要求// src/main/java/com/example/book/servlet/LoginServlet.java package com.example.book.servlet; import com.example.book.dao.UserDao; import com.example.book.model.User; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 1. MD5 加密加盐固定字符串 BookSys2024 String saltedPassword md5(password BookSys2024); // 2. 查询用户UserDao 已封装 JDBC 查询 UserDao userDao new UserDao(); User user userDao.findByUsernameAndPassword(username, saltedPassword); if (user ! null) { // 3. 登录成功写入 session跳转首页 HttpSession session req.getSession(true); session.setMaxInactiveInterval(1800); // 30分钟无操作失效 session.setAttribute(currentUser, user); resp.sendRedirect(req.getContextPath() /book/list); // 重定向到图书列表 } else { // 4. 登录失败设置错误信息转发回登录页 req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } // MD5 工具方法简化版不涉及 BCrypt 等复杂算法 private String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(MD5 加密失败, e); } } }逻辑说明doPost()处理表单提交req.getSession(true)创建新 session 或获取已有 sessionsetMaxInactiveInterval(1800)设置超时时间为 30 分钟秒resp.sendRedirect()是客户端重定向URL 变化request 域数据丢失故用 session 存储用户信息md5()方法确保密码不以明文形式存储或传输。4.2 login.jsp表单提交与错误提示%-- web/login.jsp --% % page contentTypetext/html;charsetUTF-8 languagejava % html head title电子图书管理系统 - 登录/title /head body h2 用户登录/h2 c:if test${not empty errorMsg} div stylecolor:red;${errorMsg}/div /c:if form actionlogin methodpost label用户名input typetext nameusername required/labelbrbr label密码input typepassword namepassword required/labelbrbr input typesubmit value登录 /form /body /html注意form actionlogin对应web.xml中LoginServlet的url-pattern/login/url-patternrequired属性提供前端基础校验错误信息用c:if判断并显示避免空指针异常。4.3 Filter 权限拦截未登录用户禁止访问 /book/* 路径LoginFilter.java是保障系统安全的核心必须在web.xml中配置// src/main/java/com/example/book/filter/LoginFilter.java package com.example.book.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String path req.getServletPath(); // 放行登录页、静态资源、登录处理 Servlet if (path.startsWith(/login) || path.equals(/) || path.endsWith(.jsp) || path.contains(css) || path.contains(js)) { chain.doFilter(request, response); return; } // 检查 session 中是否存在 currentUser HttpSession session req.getSession(false); if (session null || session.getAttribute(currentUser) null) { // 未登录重定向到登录页 resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 已登录放行 chain.doFilter(request, response); } }web.xml中注册 Filter!-- web/WEB-INF/web.xml 中追加 -- filter filter-nameLoginFilter/filter-name filter-classcom.example.book.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/book/*/url-pattern !-- 拦截所有 /book/ 开头的请求 -- /filter-mapping关键点req.getSession(false)不创建新 session只获取已有 sessionpath.startsWith(/login)放行/login.jsp和/login登录处理chain.doFilter()是放行指令缺此行请求将中断resp.sendRedirect()必须在return前调用否则会继续执行后续逻辑。5. 避坑指南JSPServletTomcat 电子图书系统 5 个血泪经验5.1 现象Tomcat 启动后访问http://localhost:8080/book-system显示 404但/hello能访问原因web.xml中welcome-file-list未正确配置或index.jsp不在web/根目录下。更隐蔽的原因是IDEA 中项目 Facets 配置错误Web Resource Directory指向了错误路径如指向src/main/webapp而非web/。解决检查web/WEB-INF/web.xml是否包含welcome-fileindex.jsp/welcome-file在 IDEA 中右键项目 →Open Module Settings→Facets→Web→Web Resource Directory确认路径为E:\book-system\webWindows或/home/user/book-system/webLinux清理 Tomcat 工作目录删除tomcat/work/Catalina/localhost/book-system整个文件夹重启 Tomcat。5.2 现象JSP 页面中${bookList}显示为空但 Servlet 中System.out.println(bookList)有数据原因req.setAttribute(bookList, bookTitles)后使用了sendRedirect()而非forward()。重定向是两次 HTTP 请求第一次请求的 request 域在第二次请求中已销毁。解决若需传递数据必须用req.getRequestDispatcher(/index.jsp).forward(req, resp)若必须重定向如登录后跳转则改用session.setAttribute(tempBookList, bookTitles)并在 JSP 中读取后立即session.removeAttribute(tempBookList)清理。5.3 现象MySQL 连接报错java.sql.SQLException: The server time zone value XXX is unrecognized原因MySQL 服务端时区与 JDBC URL 中serverTimezone参数不匹配或 MySQL 未配置默认时区。解决登录 MySQL 执行SELECT global.time_zone, session.time_zone;确认返回08:00或SYSTEM若为SYSTEM修改my.cnf添加default-time-zone 08:00并重启 MySQLJDBC URL 中serverTimezoneAsia/Shanghai必须与 MySQL 时区一致不可写GMT%2B8Tomcat 8.5 不识别。5.4 现象上传图片到web/images/后JSP 中img srcimages/xxx.jpg无法显示原因Tomcat 默认不提供web/目录下静态资源的直接访问除非配置default servlet。更常见的是路径混淆web/images/是项目内路径但浏览器请求的是http://localhost:8080/book-system/images/xxx.jpg需确保images/目录在web/下且文件权限可读。解决确认web/images/目录存在且图片文件已正确复制在web.xml中添加默认 servlet 映射Tomcat 9 默认已启用但某些定制版需显式配置servlet-mapping servlet-namedefault/servlet-name url-pattern/images/*/url-pattern /servlet-mapping浏览器直接访问http://localhost:8080/book-system/images/test.jpg测试若 404 则检查文件路径若 403 则检查 Tomcatconf/web.xml中defaultservlet 的init-param是否禁用了listings。5.5 现象c:forEach循环不执行JSP 页面空白控制台无报错原因JSTL 标签库未正确加载常见于jstl-1.2.jar版本与 Tomcat/JDK 不兼容或web.xml的web-app版本声明过低如 2.3。解决确认web/WEB-INF/lib/jstl-1.2.jar存在且未损坏用jar -tf jstl-1.2.jar | head -5检查web.xml的xsi:schemaLocation必须指向web-app_4_0.xsdServlet 4.0若为web-app_2_3.dtd则 JSTL 1.2 不生效在 JSP 顶部添加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %URI 必须完全匹配不可写成http://jakarta.apache.org/taglibs/core。6. 进阶技巧让系统“看起来专业”——分页组件、图书封面上传与导出 Excel6.1 手写轻量分页组件不依赖框架3 个参数搞定教学系统无需 PageHelper自己封装PageUtil类只需currentPage、pageSize、totalCount三个参数// src/main/java/com/example/book/util/PageUtil.java package com.example.book.util; public class PageUtil { private int currentPage; // 当前页码从 1 开始 private int pageSize; // 每页条数 private int totalCount; // 总记录数 private int totalPage; // 总页数 private int startRow; // LIMIT 起始行从 0 开始 public PageUtil(int currentPage, int pageSize, int totalCount) { this.currentPage Math.max(1, currentPage); this.pageSize Math.max(1, pageSize); this.totalCount Math.max(0, totalCount); this.totalPage (int) Math.ceil((double) totalCount / pageSize); this.startRow (currentPage - 1) * pageSize; } // 生成分页 HTML返回字符串JSP 中直接输出 public String generateHtml(String baseUrl) { if (totalPage 1) return ; StringBuilder html new StringBuilder(div classpagination); // 上一页 if (currentPage 1) { html.append(a href\).append(baseUrl).append(?page).append(currentPage - 1).append(\laquo; 上一页/a); } else { html.append(span classdisabledlaquo; 上一页/span); } // 页码数字最多显示 5 个 int start Math.max(1, currentPage - 2); int end Math.min(totalPage, currentPage 2); for (int i start; i end; i) { if (i currentPage) { html.append(span classcurrent).append(i).append(/span); } else { html.append(a href\).append(baseUrl).append(?page).append(i).append(\).append(i).append(/a); } } // 下一页 if (currentPage totalPage) { html.append(a href\).append(baseUrl).append(?page).append(currentPage 1).append(\下一页 raquo;/a); } else { html.append(span classdisabled下一页 raquo;/span); } html.append(/div); return html.toString(); } // Getter 方法 public int getCurrentPage() { return currentPage; } public int getPageSize() { return pageSize; } public int getStartRow() { return startRow; } public int getTotalPage() { return totalPage; } }在BookListServlet.java中使用int currentPage Integer.parseInt(req.getParameter(page) ! null ? req.getParameter(page) : 1); int pageSize 10; int totalCount bookDao.getCount(); // 新增 getCount() 方法 PageUtil pageUtil new PageUtil(currentPage, pageSize, totalCount); ListBook bookList bookDao.listAll(pageUtil.getStartRow(), pageSize); req.setAttribute(bookList, bookList); req.setAttribute(pageUtil, pageUtil); req.setAttribute(baseUrl, req.getContextPath() /book/list); req.getRequestDispatcher(/book/list.jsp).forward(req, resp);list.jsp中输出分页c:out value${pageUtil.generateHtml(pageUtil.baseUrl)} escapeXmlfalse/技巧点escapeXmlfalse允许 HTML 字符串渲染PageUtil构造时对输入参数做Math.max()校验防止负数或零导致 SQL 错误generateHtml()生成的链接带完整上下文路径适配不同部署路径。6.2 图书封面上传用 Apache Commons FileUpload 解析 multipart/form-databook/upload.jsp表单需设enctypemultipart/form-dataform actionupload methodpost enctypemultipart/form-data input typetext nametitle placeholder书名 requiredbr input typefile namecover acceptimage/* requiredbr input typesubmit value上传 /formUploadServlet.java解析文件// src/main/java/com/example/book/servlet/UploadServlet.java package com.example.book.servlet; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.util.List; public class UploadServlet extends HttpServlet { private static final String UPLOAD_DIR uploads; // 相对于 web/ 的路径 Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 检查是否为 multipart 请求 if (!ServletFileUpload.isMultipartContent(req)) { req.setAttribute(errorMsg, 请求不是 multipart 格式); req.getRequestDispatcher(/book/upload.jsp).forward(req, resp); return; } // 2. 配置文件上传工厂 DiskFileItemFactory factory new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); // 1MB 内存缓冲 factory.setRepository(new File(req.getServletContext().getRealPath(/))); // 3. 创建上传处理器 ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(10 * 1024 * 1024); // 单文件最大 10MB upload.setSize p a hrefhttps://download.csdn.net/download/froginwe11/90406205 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p