ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JavaEE图书管理系统实战:Struts2+JDBC+Tomcat7部署指南

JavaEE图书管理系统实战:Struts2+JDBC+Tomcat7部署指南 简介本资源是一套基于JavaEE企业级技术栈开发的完整图书管理系统源码面向Java初学者进阶学习者与高校课程设计学生解决图书信息录入、查询、借阅管理等典型业务场景的工程化实现问题。压缩包共93个文件含21个Java源文件构成核心业务逻辑11个JSP页面实现Web交互视图10个XML配置文件支撑Struts框架路由与数据库连接21个JAR包提供Servlet、JDBC、Struts等运行依赖辅以CSS、JS、PNG等前端资源整体6.62MB结构清晰体现MVC分层与Web应用标准目录组织。已有144人学习下载读者可直接导入Eclipse或IntelliJ IDEA运行调试深入理解ServletJSPStrutsMySQL的整合开发流程掌握DAO层封装、Action控制器调度、JSP表单交互及系统安全登录机制等关键实践细节。1. 这不是又一个“登录增删改查”的Demo而是一套可跑通、可调试、可拆解的JavaEE图书管理实战基线你打开这个java_ee_LibraryManagementSystem-master.zip看到的不是空荡荡的index.jsp和几行硬编码 SQL而是 Eclipse 项目结构下真实存在的.settings/org.eclipse.wst.common.component、struts-sysadmin.xml、lmsDB数据库脚本目录以及src/bean/下带完整 getter/setter 的 Book 类——它默认就跑在 Tomcat 7 上用 JDBC 直连 MySQL不依赖 Spring Boot 自动装配也不靠 Maven 一键拉包。这意味着你能看清 Servlet 如何从request.getParameter(isbn)拿参数能跟踪Action类里execute()方法怎么调BookService.save()更能直接修改web.xml中filter-class看权限拦截器如何生效。它适合两类人刚学完 JSP/Servlet 想验证 MVC 分层是否真能落地的在校生以及需要快速复现 JavaEE 传统分层架构、排查ClassNotFoundException或NoClassDefFoundError的运维/测试工程师。它不炫技但每一步都踩在 JavaEE 规范的边界上——比如struts.xml里package namebook extendsstruts-default的继承链就是 Struts2 框架对 JavaEE Web 层契约的具象实现。2. 从解压到启动Eclipse Tomcat 7.0 环境下的四步可验证部署流程2.1 解压与项目结构识别关键目录与文件的职责定位解压java_ee_LibraryManagementSystem-master.zip后你会看到一个标准 Eclipse Dynamic Web Project 结构。必须立刻确认以下三类路径是否存在且内容完整源码层src/目录下应有bean/实体类、action/Struts2 Action、interceptor/自定义拦截器、service/业务逻辑、dao/数据访问五个包。例如src/bean/Book.java必须包含private String isbn; private String title;等字段及对应 setter这是 JDBC 映射的基础。配置层WebContent/WEB-INF/下必须存在web.xmlServlet 容器入口、struts.xml主配置、struts-bookadmin.xml图书管理模块子配置和lmsDB/目录含create_table.sql。特别注意struts-bookadmin.xml中action nameaddBook classbook.action.BookAction methodadd的写法——class值是全限定名而非简单类名这是 Struts2 查找 Action 的关键依据。资源层WebContent/style/存 CSSWebContent/img/存图标WebContent/uploadResult.jsp是文件上传回调页。这些静态资源路径若在index.jsp中写成link hrefstyle/main.css而非link href${pageContext.request.contextPath}/style/main.css则部署后必然 404。提示不要直接将整个压缩包拖进 Eclipse 的 Workspace。正确做法是解压后在 Eclipse 中选择File → Import → Existing Projects into Workspace勾选Select root directory并指向解压后的文件夹。若出现Project xxx is missing required source folder: src错误说明.project文件中linkedResources节点未正确映射需右键项目 →Properties → Java Build Path → Source手动添加src文件夹。2.2 数据库初始化MySQL 5.7 兼容的建库脚本执行与连接验证系统使用原生 JDBC 连接 MySQL无 Hibernate 或 MyBatis 封装因此数据库初始化必须手动完成。进入lmsDB/目录找到create_table.sql其核心语句如下-- lmsDB/create_table.sql CREATE DATABASE IF NOT EXISTS lmsdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE lmsdb; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(50), publish_date DATE, price DECIMAL(10,2), stock INT DEFAULT 0 ); CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, role ENUM(admin,librarian,user) DEFAULT user );执行前需确认三点MySQL 服务已启动且用户有CREATE DATABASE权限将create_table.sql中CHARACTER SET utf8mb4改为utf8适配 MySQL 5.7 默认配置避免Specified key was too long错误在src/dao/BaseDao.java中检查 JDBC URL 是否匹配本地环境// src/dao/BaseDao.java private static final String URL jdbc:mysql://localhost:3306/lmsdb?useUnicodetruecharacterEncodingUTF-8; private static final String USER root; // 此处需替换为你的MySQL用户名 private static final String PASSWORD 123456; // 此处需替换为你的MySQL密码执行命令Linux/macOSmysql -u root -p /path/to/lmsDB/create_table.sql执行后用mysql -u root -p -e USE lmsdb; SHOW TABLES;验证book和user表是否创建成功。若提示Access denied说明BaseDao.java中的USER/PASSWORD与 MySQL 实际凭证不符必须同步修改。2.3 Eclipse 项目配置JDK 1.8 与 Tomcat 7.0 的精确绑定该项目基于 JavaEE 6 规范强制要求 JDK 1.8 和 Servlet 3.0 容器。在 Eclipse 中配置错误会导致javax.servlet.http.HttpServlet找不到或WebServlet注解失效。操作步骤如下设置项目 JDK右键项目 →Properties → Project Facets确保Java版本为1.8再进入Java Build Path → Libraries移除所有JRE System Library [JavaSE-1.x]点击Add Library → JRE System Library → Workspace default JRE (jdk1.8.0_202)。配置 Target RuntimeProperties → Targeted Runtimes勾选Apache Tomcat v7.0必须是 7.0.x非 8.0并确认该 Tomcat 安装路径下lib/servlet-api.jar存在。验证 Web Module 版本Project Facets中Dynamic Web Module必须为3.0对应 Servlet 3.0若显示2.5则点击Further configuration available...在弹窗中将Content directory设为WebContentGenerate web.xml deployment descriptor勾选。注意若启动时报错java.lang.UnsupportedClassVersionError: org/apache/struts2/dispatcher/Dispatcher has been compiled by a more recent version of the Java Runtime说明 Struts2 JAR 包如struts2-core-2.3.32.jar编译于 JDK 1.8但项目却运行在 JDK 1.7 环境下。此时必须统一 JDK 版本不可降级 JAR 包。2.4 启动与首屏验证从index.jsp到login.jsp的请求链路追踪部署成功后在 Eclipse 中右键项目 →Run As → Run on Server选择已配置的 Tomcat 7.0。启动日志中需出现以下关键行INFO: Starting Servlet Engine: Apache Tomcat/7.0.109 INFO: Initializing Spring root WebApplicationContext INFO: Loading XML bean definitions from class path resource [applicationContext.xml] INFO: Struts2 is ready. Lets rock!打开浏览器访问http://localhost:8080/java_ee_LibraryManagementSystem-master/页面应自动跳转至login.jsp由web.xml中welcome-file-list或index.jsp内response.sendRedirect(login.jsp);控制。此时可抓包验证请求链路F12 打开开发者工具 → Network 标签页刷新页面观察第一个GET /java_ee_LibraryManagementSystem-master/请求的 Response Headers 中Location: login.jsp点击login.jsp请求查看 Response Body 是否包含form actionlogin.action methodpost—— 这证明 Struts2 的FilterDispatcher已生效将.action后缀路由至struts.xml中定义的 Action。若页面空白或 404请立即检查WebContent/WEB-INF/web.xml中 Struts2 过滤器配置!-- web.xml -- filter filter-namestruts2/filter-name filter-classorg.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter/filter-class /filter filter-mapping filter-namestruts2/filter-name url-pattern/*/url-pattern /filter-mappingurl-pattern/*/url-pattern是关键若误写为/或*.action则login.jsp本身无法被容器解析。3. 核心功能拆解以图书借阅流程为例的三层代码穿透分析3.1 表现层JSP 与 Struts2 标签库的协同渲染逻辑sysadmin.jsp是管理员首页其核心借阅操作区使用 Struts2s:form标签生成表单!-- WebContent/sysadmin.jsp -- s:form actionborrowBook methodpost s:textfield nameisbn labelISBN号 / s:textfield namereaderId label读者ID / s:submit value提交借阅 / /s:form此代码生成的 HTML 实际为form idborrowBook nameborrowBook action/java_ee_LibraryManagementSystem-master/borrowBook.action methodpost table classwwFormTable tr td classtdLabellabel forborrowBook_isbnISBN号:/label/td tdinput typetext nameisbn value idborrowBook_isbn//td /tr !-- readerId 同理 -- /table /form关键点在于actionborrowBook被自动补全为borrowBook.action这是 Struts2 的默认后缀规则由struts.properties中struts.action.extensionaction控制。若想关闭此行为需在src/struts.properties中添加struts.action.extension空值但本项目未做此修改故必须保证所有 form action 末尾不带.action。提示s:textfield的name属性值isbn必须与BookAction.java中的成员变量名完全一致区分大小写否则 Struts2 的 OGNL 表达式无法自动注入值。例如private String isbn;若误写为private String ISBN;则request.getParameter(isbn)将始终为 null。3.2 控制层Struts2 Action 的 execute() 方法与业务流转src/action/BookAction.java是借阅流程的中枢其borrowBook()方法定义如下public String borrowBook() { try { // 1. 从ActionContext获取当前用户 Map session ActionContext.getContext().getSession(); User user (User) session.get(user); if (user null || !admin.equals(user.getRole())) { this.addFieldError(error, 无权执行借阅操作); return error; } // 2. 调用Service层处理业务 boolean success bookService.borrowBook(isbn, readerId); if (success) { this.addActionMessage(借阅成功); return success; } else { this.addFieldError(error, 库存不足或读者不存在); return error; } } catch (Exception e) { e.printStackTrace(); this.addFieldError(error, 系统异常 e.getMessage()); return error; } }此处体现三个关键设计权限校验前置通过ActionContext.getContext().getSession()获取 session而非HttpServletRequest.getSession()这是 Struts2 推荐的解耦方式Service 层隔离bookService.borrowBook()封装了全部业务逻辑BookAction不直接操作 DAO符合分层原则返回结果驱动视图return success对应struts-bookadmin.xml中result namesuccess/success.jsp/result实现控制流与视图的松耦合。3.3 业务与数据层Service 事务控制与 DAO 的 JDBC 原生实现src/service/BookService.java的borrowBook()方法开启显式事务public boolean borrowBook(String isbn, String readerId) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn BaseDao.getConnection(); // 从BaseDao获取连接 conn.setAutoCommit(false); // 关闭自动提交 // 步骤1检查图书库存 String checkSql SELECT stock FROM book WHERE isbn ?; ps conn.prepareStatement(checkSql); ps.setString(1, isbn); rs ps.executeQuery(); if (!rs.next() || rs.getInt(stock) 0) { throw new RuntimeException(库存不足); } // 步骤2扣减库存 String updateSql UPDATE book SET stock stock - 1 WHERE isbn ?; ps conn.prepareStatement(updateSql); ps.setString(1, isbn); ps.executeUpdate(); // 步骤3记录借阅日志简化版 String logSql INSERT INTO borrow_log (isbn, reader_id, borrow_time) VALUES (?, ?, NOW()); ps conn.prepareStatement(logSql); ps.setString(1, isbn); ps.setString(2, readerId); ps.executeUpdate(); conn.commit(); // 手动提交事务 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); // 异常时回滚 } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { BaseDao.closeAll(rs, ps, conn); // 统一释放资源 } }此实现暴露 JavaEE 传统事务管理的典型模式conn.setAutoCommit(false)是 JDBC 事务起点conn.commit()与conn.rollback()成对出现且必须在finally块外捕获异常BaseDao.closeAll()封装了rs.close(),ps.close(),conn.close()避免连接泄漏——这是本项目未使用连接池如 DBCP时的必要防护。3.4 数据库交互验证通过日志输出确认 SQL 执行路径为验证上述 JDBC 流程是否真实执行可在BaseDao.java的getConnection()方法中添加日志public static Connection getConnection() { try { Class.forName(com.mysql.jdbc.Driver); // 注意MySQL 5.7 使用此驱动类名 System.out.println([DEBUG] JDBC Driver loaded successfully); Connection conn DriverManager.getConnection(URL, USER, PASSWORD); System.out.println([DEBUG] Database connection established to URL); return conn; } catch (Exception e) { e.printStackTrace(); return null; } }启动 Tomcat 后在控制台搜索[DEBUG]应看到[DEBUG] JDBC Driver loaded successfully [DEBUG] Database connection established to jdbc:mysql://localhost:3306/lmsdb?useUnicodetruecharacterEncodingUTF-8若只看到第一行而无第二行说明数据库连接失败需检查 MySQL 服务状态、端口默认 3306、防火墙设置及URL中的数据库名lmsdb是否存在。4. 常见故障定位ClassNotFoundException 与 NoClassDefFoundError 的根因区分与修复4.1 类加载失败的两种本质差异编译期缺失 vs 运行时缺失当启动报错java.lang.ClassNotFoundException: org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter这属于编译期类缺失——即WEB-INF/lib/下缺少struts2-core-2.3.32.jar。解决方案下载struts-2.3.32.zip与项目匹配的版本解压后将lib/struts2-core-2.3.32.jar、lib/struts2-config-browser-plugin-2.3.32.jar等所有struts2-*.jar复制到WebContent/WEB-INF/lib/在 Eclipse 中右键项目 →Refresh再Properties → Java Build Path → Libraries确认Web App Libraries下已加载这些 JAR。而java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest则是运行时类缺失——即 Tomcat 的servlet-api.jar未被类加载器识别。原因通常是WEB-INF/lib/下误存了servlet-api.jarTomcat 自带禁止重复引入或Targeted Runtimes未正确关联 Tomcat 7.0。修复步骤删除WebContent/WEB-INF/lib/中所有servlet-api*.jar、jsp-api*.jar确认Project Facets → Targeted Runtimes已勾选Apache Tomcat v7.0清理 Tomcat 工作目录Servers → Tomcat v7.0 Server → Clean。4.2 Struts2 配置加载失败struts.xml未被扫描的三种场景若访问login.action报 404且日志无Struts2 is ready提示说明struts.xml未加载。常见原因场景表现修复方法struts.xml位置错误WEB-INF/classes/struts.xml缺失将src/struts.xml复制到WebContent/WEB-INF/classes/Eclipse 默认输出路径web.xml过滤器顺序错误日志出现Filter struts2 not found确保filter声明在filter-mapping之前且StrutsPrepareAndExecuteFilter在CharacterEncodingFilter之后struts.xml语法错误Tomcat 启动卡住日志报Parse error in struts.xml用 XML 验证工具检查struts.xml是否有未闭合标签如package缺/package验证struts.xml是否加载成功在struts.xml的struts根节点内添加一行注释!-- Loaded at startup --启动后查看 Tomcat 日志若出现Loading global messages from xwork-messages则证明加载成功。4.3 数据库连接池缺失导致的连接耗尽手动管理连接的硬伤与规避方案本项目使用BaseDao.getConnection()直连 MySQL无连接池。高并发下易出现java.sql.SQLException: Too many connections。临时规避方案是在BaseDao.closeAll()后强制 GCpublic static void closeAll(ResultSet rs, PreparedStatement ps, Connection conn) { if (rs ! null) try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } if (ps ! null) try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } if (conn ! null) try { conn.close(); System.gc(); // 强制触发垃圾回收释放连接 } catch (SQLException e) { e.printStackTrace(); } }但根本解决需引入 DBCP 连接池。修改BaseDao.java// 添加静态连接池 private static BasicDataSource dataSource new BasicDataSource(); static { dataSource.setDriverClassName(com.mysql.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/lmsdb?useUnicodetruecharacterEncodingUTF-8); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); dataSource.setMaxIdle(10); dataSource.setMaxOpenPreparedStatements(100); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); // 从此处获取连接 }对应需在WEB-INF/lib/中添加commons-dbcp-1.4.jar和commons-pool-1.6.jar。5. 进阶技巧用 Chrome DevTools 快速定位 JSP 页面中的 EL 表达式失效点5.1 EL 表达式${user.username}不渲染的三大排查路径当top.jsp中h3欢迎 ${user.username}/h3显示为纯文本而非用户名说明 EL 表达式未启用。按优先级检查确认web.xml中 Servlet 版本 ≥ 2.4!-- web.xml -- 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 !-- 必须 ≥ 2.4 --检查 JSP 页面是否禁用 EL在top.jsp顶部确认无% page isELIgnoredtrue %。若有改为isELIgnoredfalse或直接删除该指令。验证user对象是否存入正确作用域在LoginAction.java的execute()方法中登录成功后必须将user存入sessionMap session ActionContext.getContext().getSession(); session.put(user, user); // key 必须为 user与 EL 中 ${user} 一致5.2 利用 Chrome Console 快速验证后端数据传递在login.jsp中右键审查元素 → Console 标签页输入// 查看当前页面所有表单的 action 属性 document.querySelector(form).action // 输出应为 /java_ee_LibraryManagementSystem-master/login.action // 查看隐藏域中存储的 contextPath document.querySelector(input[namecontextPath]).value // 若为 undefined说明 index.jsp 中未设置input typehidden namecontextPath value${pageContext.request.contextPath}若document.querySelector(form).action返回绝对路径如http://localhost:8080/login.action说明web.xml中url-pattern配置错误应为/*而非/login.action。5.3 JSP 编译错误的即时反馈查看 Tomcat work 目录生成的 Java 文件Tomcat 将 JSP 编译为 Java 文件存于work/Catalina/localhost/[project-name]/org/apache/jsp/。例如login_jsp.java对应login.jsp。若login.jsp有语法错误如% String name request.getParameter(username; %缺)编译失败后会在该目录生成login_jsp.class的同名.java文件并在其中标注错误行// work/Catalina/localhost/java_ee_LibraryManagementSystem-master/org/apache/jsp/login_jsp.java // 第 42 行报错 String name request.getParameter(username; // 缺少右括号直接打开此.java文件比在 Eclipse 中看模糊的HTTP Status 500更精准定位 JSP 语法问题。提示每次修改 JSP 后务必清空work/目录Servers → Tomcat v7.0 → Clean否则旧编译文件可能缓存导致问题复现。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进