ARTICLE DETAIL

资讯详情

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

JSP+Servlet+Tomcat电子图书系统实战指南

JSP+Servlet+Tomcat电子图书系统实战指南 简介这是一套基于JSPServletTomcat技术栈开发的电子图书管理系统完整源码面向Java Web初学者、课程设计学生及小组项目开发者解决图书信息在线管理、用户交互与前后端协同开发等典型教学实践问题。资源共50个文件含22个Java业务逻辑类涵盖用户认证、图书增删改查等核心Servlet、11个XML配置文件包括web.xml、MyBatis映射及数据库连接配置、8个JSP页面如login.jsp、detail.jsp、addBook.jsp等构成完整前端流程辅以CSS、JS、Bootstrap样式资源及必要工程元数据文件压缩包仅2.77MB轻量易部署。已有123人学习下载适合快速上手Java Web开发全流程从Tomcat环境搭建、Servlet请求分发、JSP动态渲染到响应式界面实现与MySQL数据持久化集成。目录结构规范src与webapp分离清晰.gitignore和readme.txt等配套文件齐全便于理解MVC分层思想与实际工程组织方式。1. 为什么现在还要用 JSPServletTomcat 做电子图书管理系统——不是怀旧是可控、可审计、可交接的工程选择你打开招聘网站搜“Java Web 开发”JD里写“熟悉 Spring Boot”是标配但翻到教育类、政务类、中小企内部系统岗位常会看到一句“有 JSP/Servlet 实战经验者优先”。这不是技术倒退而是现实约束下的理性选择一个高校图书馆管理员要自己增删图书、导出借阅报表不需要微服务拆分、不用对接 Kafka 消息队列但必须能本地部署、不依赖云厂商、源码看得懂、改起来不踩坑。这个“基于 JSPServletTomcat 的电子图书管理系统”就是为这类场景量身定制的最小可行闭环——它不炫技但把用户登录、图书 CRUD、借阅记录管理、分类统计这些真实业务用最直白的 Java Web 原生技术栈串通所有逻辑在.java文件里明明白白所有页面在.jsp里清清楚楚所有请求路由由web.xml或注解一锤定音。它适合刚学完 Servlet 生命周期的学生做课程设计也适合运维只有 Linux 服务器权限、没配 Docker 的单位信息员快速上线。如果你正被 Spring Boot 的自动配置绕晕或被前端框架打包后找不到index.html路径那这套方案就是你的“技术锚点”没有黑匣子只有request.getParameter()和response.getWriter()的确定性。2. 从零搭起 Tomcat 环境不是下载就完事关键在 JDK 版本对齐与目录权限JSP 和 Servlet 的运行根基是 Tomcat但它不是独立存在的“软件包”而是一个严格依赖 JVM 版本、目录结构和启动上下文的容器。很多新手卡在第一步下载了apache-tomcat-8.5.99.tar.gz解压后双击startup.bat却弹窗报错“JAVA_HOME not set”或者 Linux 下./startup.sh显示Permission denied。这不是操作失误而是没理解 Tomcat 的启动契约。2.1 JDK 与 Tomcat 版本必须双向验证不能只看官网推荐表Tomcat 官网文档写的“Tomcat 8.5 支持 JDK 7”是下限不是安全区。实际项目中我们坚持JDK 8u202 与 Tomcat 8.5.99 组合对应image: tomcat:8.5-jdk8-corretto镜像的生产级实践原因有三JSP 编译器jasper在 JDK 8u202 之后才稳定支持 EL 表达式中的方法调用如${book.author.getName()}Tomcat 8.5.99 的catalina.sh脚本默认读取JAVA_HOME若用 JDK 11其jre/lib/ext目录结构变更会导致commons-dbcp连接池加载失败tomcat-jdbc.jar在 JDK 8u202 下的ClassLoader双亲委派行为可预测避免“类加载冲突”这类玄学问题。提示执行java -version后务必再运行echo $JAVA_HOMELinux/macOS或echo %JAVA_HOME%Windows确认环境变量指向的是 JDK 根目录含bin/java和jre/子目录而非仅 JRE。2.2 Linux 下启动前必须做的三件事权限、端口、日志路径Tomcat 默认监听 8080 端口但在生产环境这常与 Nginx 冲突默认日志写入logs/catalina.out但若logs/目录属主不是启动用户会静默失败。以下是不可跳过的初始化步骤# 1. 解压并进入目录假设 tar 包已下载 tar -xzf apache-tomcat-8.5.99.tar.gz cd apache-tomcat-8.5.99 # 2. 创建专用用户避免 root 启动 sudo useradd -r -s /bin/false tomcat sudo chown -R tomcat:tomcat /opt/tomcat # 3. 修改 server.xml将 Connector port8080 改为 8081避开常用端口 # 并添加 redirectPort8443为后续 HTTPS 预留 sudo -u tomcat sed -i s/port8080/port8081/ conf/server.xml sudo -u tomcat sed -i s/redirectPort8443/redirectPort8443/ conf/server.xml # 4. 设置日志目录可写关键否则 catalina.out 不生成 sudo -u tomcat mkdir -p logs sudo -u tomcat chmod 755 logs执行完后用sudo -u tomcat ./bin/startup.sh启动再立刻检查logs/catalina.out是否有INFO: Server startup in [xxx] ms字样。没有说明logs/权限或JAVA_HOME仍有问题——这是后续所有 JSP 页面 404 的根源。2.3 Windows 下绕过“双击 startup.bat 闪退”的血泪经验Windows 用户常遇到双击startup.bat后窗口一闪而逝。这不是脚本错误而是startup.bat默认调用catalina.bat run而run模式会在控制台退出时终止进程。正确做法是以管理员身份打开命令提示符CMD切换到bin目录cd D:\apache-tomcat-8.5.99\bin执行catalina.bat start注意是start不是run查看logs\catalina.out最后一行是否为Server startup in。注意若仍失败请右键“我的电脑 → 属性 → 高级系统设置 → 环境变量”确认JAVA_HOME是完整路径如D:\Program Files\Java\jdk1.8.0_202且Path中包含%JAVA_HOME%\bin。不要用中文路径、空格路径——这是 Windows 下 Tomcat 启动失败的头号原因。3. 电子图书核心功能落地从数据库建模到 JSP 页面渲染的全链路闭环系统不是“能跑就行”而是要支撑真实业务管理员录入新书、读者查书、借阅记录生成、分类统计报表。这要求每一层都严丝合缝——数据库字段类型决定 Servlet 接收参数的转换方式JSP 表单 name 属性必须与 ServletgetParameter()的 key 一致而 Tomcat 的web.xml则决定了 URL 路径如何映射到具体类。3.1 MySQL 数据库设计为什么book表必须设isbn为主键而非自增 ID电子图书系统的核心实体是book但它的主键选择直接影响后续所有 CRUD 操作的健壮性。常见错误是设id INT AUTO_INCREMENT PRIMARY KEY看似简单却埋下三个隐患图书 ISBN 是全球唯一标识如978-7-04-050694-6用作主键可直接对接出版社数据避免 ID 冲突借阅表borrow_record中book_isbn外键引用book.isbn比book_id更易校验合法性ISBN 格式可正则校验JSP 页面展示图书详情时URL 可直接用/book/detail.jsp?isbn9787040506946无需额外查询 ID减少一次数据库 round-trip。建表 SQL 如下含关键注释-- 创建图书表isbn 为主键长度固定 17含分隔符 CREATE TABLE book ( isbn VARCHAR(17) PRIMARY KEY COMMENT 国际标准书号格式如 978-7-04-050694-6, title VARCHAR(200) NOT NULL COMMENT 书名, author VARCHAR(100) NOT NULL COMMENT 作者, publisher VARCHAR(100) COMMENT 出版社, publish_date DATE COMMENT 出版日期, price DECIMAL(8,2) COMMENT 定价, category ENUM(文学,科技,教育,少儿,艺术) DEFAULT 教育 COMMENT 分类, stock INT DEFAULT 0 COMMENT 库存数量, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 创建借阅记录表外键关联 isbn CREATE TABLE borrow_record ( id INT AUTO_INCREMENT PRIMARY KEY, isbn VARCHAR(17) NOT NULL COMMENT 关联图书 ISBN, reader_id VARCHAR(20) NOT NULL COMMENT 读者学号/工号, borrow_date DATE NOT NULL COMMENT 借阅日期, return_date DATE COMMENT 归还日期NULL 表示未归还, FOREIGN KEY (isbn) REFERENCES book(isbn) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示ENUM类型在 MySQL 中性能优于VARCHAR且天然限制非法值如插入游戏会报错比在 Servlet 层做 if-else 校验更可靠。3.2 Servlet 层用WebServlet注解替代web.xml配置的实操细节Tomcat 7 支持注解式 Servlet 注册比 XML 配置更直观。但新手常忽略两个关键点WebServlet(/book/add)中的路径是相对于应用根目录即http://localhost:8081/your-app/book/add不是相对于WEB-INFdoPost()方法内request.setCharacterEncoding(UTF-8)必须在getParameter()之前调用否则中文参数乱码。以下是一个完整的图书添加 Servlet 示例含异常处理WebServlet(/book/add) public class BookAddServlet extends HttpServlet { private static final long serialVersionUID 1L; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 强制设置请求编码关键放在 getParameter 前 request.setCharacterEncoding(UTF-8); // 2. 获取表单参数name 必须与 JSP 中 input 的 name 一致 String isbn request.getParameter(isbn); String title request.getParameter(title); String author request.getParameter(author); String publisher request.getParameter(publisher); String publishDate request.getParameter(publish_date); String priceStr request.getParameter(price); String category request.getParameter(category); String stockStr request.getParameter(stock); // 3. 参数校验业务逻辑入口 if (isbn null || isbn.trim().isEmpty()) { request.setAttribute(error, ISBN 不能为空); request.getRequestDispatcher(/book/add.jsp).forward(request, response); return; } // 4. 数据库操作此处简化为调用 DAO BookDAO dao new BookDAO(); try { Book book new Book(); book.setIsbn(isbn); book.setTitle(title); book.setAuthor(author); book.setPublisher(publisher); book.setPublishDate(LocalDate.parse(publishDate)); book.setPrice(new BigDecimal(priceStr)); book.setCategory(category); book.setStock(Integer.parseInt(stockStr)); dao.insert(book); // 5. 成功后重定向避免重复提交 response.sendRedirect(request.getContextPath() /book/list.jsp); } catch (Exception e) { e.printStackTrace(); request.setAttribute(error, 保存失败 e.getMessage()); request.getRequestDispatcher(/book/add.jsp).forward(request, response); } } }逻辑说明response.sendRedirect()是关键——它让浏览器发起新 GET 请求避免用户刷新时重复提交表单。而forward()仅用于服务端跳转URL 不变适合带错误信息返回原页面。3.3 JSP 页面用 JSTL 替代 scriptlet 的现代写法但保留request.getAttribute()的原始能力JSP 不该写% out.print(Hello); %这种脚本片段scriptlet既难维护又易 XSS。正确做法是用 JSTL 标签库处理循环、条件c:forEach、c:if用 EL 表达式取值${book.title}但request.setAttribute(books, list)这种底层 API 必须保留——因为 JSTL 无法替代它传递数据。book/list.jsp示例含分页与分类筛选% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head title图书列表/title meta charsetUTF-8 /head body h2全部图书/h2 !-- 分类筛选表单 -- form action${pageContext.request.contextPath}/book/list methodget label按分类筛选/label select namecategory option value全部/option option value文学 ${param.category 文学 ? selected : }文学/option option value科技 ${param.category 科技 ? selected : }科技/option option value教育 ${param.category 教育 ? selected : }教育/option /select button typesubmit筛选/button /form !-- 图书表格 -- table border1 tr thISBN/thth书名/thth作者/thth出版社/thth价格/thth库存/thth操作/th /tr c:forEach items${books} varbook tr td${book.isbn}/td td${book.title}/td td${book.author}/td td${book.publisher}/td td¥${book.price}/td td${book.stock}/td td a href${pageContext.request.contextPath}/book/detail.jsp?isbn${book.isbn}详情/a | a href${pageContext.request.contextPath}/book/edit.jsp?isbn${book.isbn}编辑/a /td /tr /c:forEach /table c:if test${empty books} p暂无图书/p /c:if /body /html参数说明${pageContext.request.contextPath}动态获取应用上下文路径如/book-system避免硬编码${param.category}直接读取 GET 请求参数实现无状态筛选。4. 避坑指南JSPServlet 项目中最常翻车的 4 个场景及根因修复这套技术栈看似简单但每个环节都有“表面正常、实际埋雷”的陷阱。以下是我在线上系统维护中踩过的真坑按发生频率排序每条都附带现象、根因和可立即执行的修复命令。4.1 现象JSP 页面显示中文乱码但数据库和控制台日志都是正常的原因JSP 文件本身编码是 GBKWindows 记事本默认而 Tomcat 默认用 UTF-8 解析 JSP导致pageEncoding与文件实际编码不匹配。解决用 VS Code 或 Notepad 将所有.jsp文件另存为 UTF-8 无 BOM 格式在 JSP 顶部强制声明编码% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %在web.xml中全局配置Tomcat 8.5 可选jsp-config jsp-property-group url-pattern*.jsp/url-pattern page-encodingUTF-8/page-encoding /jsp-property-group /jsp-config4.2 现象c:forEach循环不执行表格空白但System.out.println(list.size())输出 5原因JSTL 标签库未正确引入或web.xml的web-app版本低于 2.4JSTL 1.2 要求 Servlet 2.4。解决确认WEB-INF/lib下有jstl-1.2.jar和standard-1.1.2.jar或 Maven 依赖jstl:jstl:1.2检查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_3_1.xsd version3.1注意version3.1是关键2.5 或 2.4 也能用 JSTL但 3.1 更稳妥。4.3 现象BookAddServlet报ClassNotFoundException: com.mysql.cj.jdbc.Driver原因MySQL 8.0 驱动类名从com.mysql.jdbc.Driver变更为com.mysql.cj.jdbc.Driver但旧教程仍教前者。解决下载mysql-connector-java-8.0.33.jar非 5.x 版本放入WEB-INF/lib目录在BookDAO.java中使用新驱动// ✅ 正确 Class.forName(com.mysql.cj.jdbc.Driver); // ❌ 错误MySQL 8.0 已废弃 // Class.forName(com.mysql.jdbc.Driver);JDBC URL 必须加serverTimezoneUTC参数String url jdbc:mysql://localhost:3306/bookdb?useSSLfalseserverTimezoneUTC;4.4 现象Tomcat 启动后访问http://localhost:8081/book-system返回 404但http://localhost:8081/能看到 Tomcat 首页原因WAR 包未正确部署或应用上下文路径Context Path未匹配。解决确认 WAR 包名是book-system.war非book.war或book-system-1.0.war将 WAR 包放入webapps/目录后等待 Tomcat 自动解压观察webapps/book-system/目录是否生成若手动解压确保webapps/book-system/WEB-INF/web.xml存在且格式正确检查conf/server.xml中Host节点是否有Context path/book-system docBasebook-system /—— 通常不需要Tomcat 会自动推断。5. 进阶技巧用 Filter 实现统一登录校验以及 JSP 页面加载后自动刷新的务实方案系统做到能用只是起点真正交付给管理员用还得解决两个高频需求一是所有后台页面如/book/add.jsp必须登录才能访问二是图书列表页首次加载后需自动刷新一次以同步最新库存——这不是炫技而是业务刚需。5.1 用LoginFilter拦截未登录请求比在每个 Servlet 里写 if 判断更可靠Filter 是 Servlet 规范提供的拦截器它在请求到达 Servlet 前执行天然适合做权限校验。关键在于chain.doFilter()是放行点必须调用否则请求中断HttpSession的getAttribute(user)为空时应重定向到登录页而非返回 403excludeUrls列表必须包含/login.jsp和/LoginServlet否则形成死循环。LoginFilter.java实现如下WebFilter(filterName LoginFilter, urlPatterns /*) public class LoginFilter implements Filter { private static final ListString EXCLUDE_URLS Arrays.asList( /login.jsp, /LoginServlet, /css/, /js/, /images/ ); Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); // 1. 排除静态资源和登录相关路径 boolean shouldExclude EXCLUDE_URLS.stream() .anyMatch(exclude - uri.equals(exclude) || uri.startsWith(exclude)); if (shouldExclude) { chain.doFilter(request, response); return; } // 2. 检查 session 中是否有 user 对象 HttpSession session req.getSession(false); if (session null || session.getAttribute(user) null) { // 未登录重定向到登录页并保存原请求路径便于登录后跳回 String originalUrl req.getRequestURL().toString(); if (req.getQueryString() ! null) { originalUrl ? req.getQueryString(); } session.setAttribute(originalUrl, originalUrl); resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 3. 已登录放行 chain.doFilter(request, response); } }注意req.getSession(false)中的false表示“不创建新 session”避免未登录用户也生成 session ID浪费内存。5.2 JSP 页面加载完后刷新一次用window.onloadlocation.reload()的轻量方案需求场景管理员在/book/list.jsp页面点击“新增图书”跳转到/book/add.jsp提交成功后response.sendRedirect(/book/list.jsp)会回到列表页但此时页面显示的库存仍是旧值因为浏览器缓存了上一次响应。解决方案不是禁用缓存影响性能而是让页面加载完成后主动刷新一次%-- 在 book/list.jsp 底部添加 --% script window.onload function() { // 检查 URL 是否带 reload1 参数避免无限刷新 if (!window.location.search.includes(reload1)) { window.location.href window.location.href (window.location.search ? reload1 : ?reload1); } }; /script这段 JS 的逻辑是页面首次加载时URL 不含reload1于是拼接该参数并跳转第二次加载时URL 已含reload1includes()返回 true不再跳转整个过程对用户无感仅多一次 HTTP 请求但保证了数据实时性。5.3 一个我坚持十年的习惯每次修改 JSP 后必用curl -I检查响应头JSP 页面的问题80% 能从 HTTP 响应头里看出端倪。比如Content-Type: text/html;charsetISO-8859-1→ 说明pageEncoding未生效Cache-Control: max-age3600→ 说明浏览器可能缓存了旧版 JSPSet-Cookie: JSESSIONIDxxx; Path/; HttpOnly→ 确认 session 已创建。我日常调试的命令是# 检查首页响应头替换为你的实际路径 curl -I http://localhost:8081/book-system/ # 检查某个 JSP 的 Content-Type curl -I http://localhost:8081/book-system/book/list.jsp如果看到charsetISO-8859-1立刻回去检查 JSP 的pageEncoding和文件保存编码如果Set-Cookie缺失说明 Filter 或 Session 配置有问题。这比盯着浏览器 F12 的 Network 面板更快定位根因。希望帮到你。本文还有配套的精品资源点击获取
返回列表