简介本资源是一套基于JavaEE企业级开发标准实现的完整图书管理系统面向Java初学者与Web开发进阶学习者聚焦MVC分层架构、数据库交互与Web应用部署等核心能力训练。压缩包共93个文件含21个Java源码文件业务逻辑与控制器、11个JSP页面用户界面展示、10个XML配置文件Struts框架路由与数据源定义、21个JAR依赖库涵盖Servlet、JDBC、Struts及工具类以及CSS、JS、图片等前端资源整体大小6.62MB。已有146人下载学习适合在Eclipse或IntelliJ IDEA中导入运行可快速掌握ServletJSPStrutsMySQL的典型JavaEE技术栈整合实践。项目采用清晰的分包结构src下含action、bean、interceptor等模块内置登录认证、图书增删改查、管理员与普通用户双角色权限管理并附带完整的WEB-INF配置与Tomcat部署说明是理解企业级Web应用分层设计与工程化落地的优质教学案例。1. 这不是又一个“Hello World”图书系统它用 Struts2 JDBC Tomcat 跑通了真实借阅闭环新手照着部署能登录、查书、还书老手一眼看出 DAO 层没硬编码 SQL、Interceptor 拦了未登录跳转——适合想补全 JavaEE 企业级开发链路的实战派你可能已经下载过十几个标着“Java 图书管理系统”的压缩包解压后发现没有数据库脚本、login.jsp 里写死 admin/123、BookServlet 里 new 了 Connection 却没 close、连 web.xml 都缺filter配置。这个java_ee_LibraryManagementSystem-master.zip不是教学玩具——它用 Struts2 的struts-sysadmin.xml和struts-bookadmin.xml实现了角色分离管理员 vs 普通用户用lmsDB文件夹里完整的 MySQL 建库建表语句初始数据撑起真实业务逻辑interceptor包下LoginInterceptor.java真正拦截了/sysManage.action的未授权访问。它不依赖 Spring Boot 自动装配而是用最原始的web.xmlstruts.xmlJDBCUtils手动管理连接池让你看清 JavaEE 时代“容器托管”和“手动控制”的边界在哪。如果你正卡在“学完 Servlet/JSP 却写不出可运行的完整系统”或者需要一份能放进简历项目栏、经得起面试追问的 JavaEE 落地案例——它就是那个能跑起来、能改代码、能调 Bug 的“血肉之躯”不是骨架图。2. 从解压到首页五步完成本地部署关键在 WEB-INF/lib 里这 7 个 JAR 包的版本对齐2.1 解压与项目结构识别别急着导入 IDE先确认三个核心目录是否存在解压java_ee_LibraryManagementSystem-master.zip后你会看到根目录下有src、WebContent、lmsDB三个不可删的文件夹srcJava 源码含bean实体类、actionStruts2 控制器、interceptor拦截器、dao数据访问WebContentWeb 资源等同于传统 WAR 包的webapp目录WEB-INF/web.xml是入口配置lmsDB必须优先看——里面lms.sql是建库脚本data.sql是初始化图书/用户数据db.properties定义了 JDBC 连接参数提示WebContent/WEB-INF/classes是编译输出目录但本项目默认用 Eclipse 导出所以build/classes可能为空实际运行时 Tomcat 会自动编译src下的.java文件到WEB-INF/classes无需手动复制。2.2 数据库准备MySQL 5.7 执行 lms.sql重点检查字符集与存储引擎进入lmsDB目录用 MySQL 客户端执行-- 创建数据库注意字符集 CREATE DATABASE lms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 导入表结构 SOURCE lms.sql; -- 导入测试数据含管理员账号 admin/admin普通用户 user/123 SOURCE data.sql;关键参数说明utf8mb4避免图书名含 emoji 或生僻字如《¥》《》时乱码比utf8更彻底InnoDBlms.sql中所有表均指定ENGINEInnoDB确保事务支持借书/还书需原子性data.sql中user表的role字段值为0管理员或1普通用户这是LoginInterceptor判断权限的依据2.3 Tomcat 配置8.5.x 是黄金版本9.x 需手动降级 EL 表达式语法本项目基于 Servlet 3.0 JSP 2.2强烈建议使用 Apache Tomcat 8.5.90非最新版。原因index.jsp中${pageContext.request.contextPath}写法在 Tomcat 9 默认禁用 EL 表达式需额外配置web.xmlstruts.xml使用package namedefault extendsstruts-default依赖 Struts2.3.x与 Tomcat 8.5 兼容性最佳操作步骤下载apache-tomcat-8.5.90.zip解压到无中文路径如D:\tomcat85将WebContent整个文件夹复制到D:\tomcat85\webapps\lms注意不是lms.war是直接放文件夹启动D:\tomcat85\bin\startup.bat访问http://localhost:8080/lms2.4 依赖 JAR 包校验WEB-INF/lib 缺这 7 个部署必报 ClassNotFound打开WebContent\WEB-INF\lib必须存在以下 JAR版本已验证JAR 名称作用版本要求struts2-core-2.3.32.jarStruts2 核心必须 2.3.x2.5 会因ActionSupport方法签名变更报错xwork-core-2.3.32.jarXWork 框架与 struts2-core 版本严格匹配ognl-3.0.6.jarOGNL 表达式解析低于 3.0.6 无法解析#session.user.rolefreemarker-2.3.22.jarFreemarker 模板引擎Struts2 默认视图技术commons-fileupload-1.3.1.jar文件上传支持uploadResult.jsp依赖此包处理封面上传mysql-connector-java-5.1.47.jarMySQL JDBC 驱动5.1.x 系列8.0 驱动需改db.properties的 URL 格式commons-dbcp-1.4.jar数据库连接池JDBCUtils.java中BasicDataSource来自此包注意若你用 IntelliJ IDEA 导入不要勾选 Create module from existing sources而应选择 Import project → Import project from external model → Eclipse否则src下的包结构会被 IDE 错误识别为default package。2.5 启动验证三步确认系统活了而非只显示 404首页加载http://localhost:8080/lms/index.jsp应显示带搜索框的图书列表页左上角有“登录”链接登录成功用admin/admin登录跳转至sysManage.jsp页面顶部显示“欢迎管理员”功能连通在sysManage.jsp点击“添加图书”填入 ISBN、书名、作者提交后刷新列表应出现新书——证明BookAction→BookDAO→ MySQL 全链路通畅若卡在第 1 步检查web.xml中welcome-file-list是否为welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list若卡在第 2 步查看 Tomcat 日志logs/catalina.out搜索SQLException或NoClassDefFoundError大概率是mysql-connector-java.jar版本不对或db.properties中密码错误。3. 拦截器、Action 与 DAO拆解三层协作链看 Struts2 如何把 HTTP 请求变成数据库操作3.1 LoginInterceptor不是装饰器是请求守门人它决定谁能看到 sysManage.jspsrc/interceptor/LoginInterceptor.java是整个权限体系的基石。它的intercept(ActionInvocation invocation)方法逻辑如下public String intercept(ActionInvocation invocation) throws Exception { // 1. 获取 session 中的 user 对象 MapString, Object session ActionContext.getContext().getSession(); User user (User) session.get(user); // 2. 判断是否为管理员role0且访问的是管理员路径 String actionName invocation.getProxy().getActionName(); if (user null || user.getRole() ! 0) { if (actionName.startsWith(sysManage) || actionName.startsWith(bookAdmin)) { // 3. 未登录或非管理员重定向到 login.jsp return login; } } // 4. 放行 return invocation.invoke(); }关键点说明actionName.startsWith(sysManage)匹配sysManage.action、sysManageAdd.action等不是按 URL 路径匹配而是 Struts2 解析后的 Action 名session.get(user)LoginAction.java在登录成功后session.put(user, user)此处复用避免重复查库返回login触发struts.xml中result namelogin/login.jsp/result实现无感跳转3.2 BookActionStruts2 的 Action 不是 Servlet它靠反射调用 execute() 并绑定属性src/action/BookAction.java继承ActionSupport核心方法public String execute() throws Exception { // 1. 从 request 获取参数Struts2 自动注入无需 request.getParameter String isbn this.isbn; // 由 struts.xml 中 param nameisbn${isbn}/param 绑定 String title this.title; // 2. 调用业务层 BookService bookService new BookService(); boolean result bookService.addBook(isbn, title, author, price); // 3. 根据结果返回不同视图 if (result) { this.addActionMessage(添加成功); return SUCCESS; // 对应 struts-bookadmin.xml 中 result namesuccess/sysManage.jsp/result } else { this.addActionError(添加失败请检查ISBN是否重复); return ERROR; } }参数绑定原理this.isbn能直接取值是因为 Struts2 的ParametersInterceptor在execute()前已将请求参数映射到 Action 属性isbn字段必须有 public setterpublic void setIsbn(String isbn) { this.isbn isbn; }若字段名与表单 name 不一致需在struts-bookadmin.xml中显式param nameisbn${book.isbn}/param3.3 BookDAOJDBCUtils 是灵魂它让 Connection 不再是“一次性用品”src/dao/BookDAO.java中addBook()方法public boolean addBook(String isbn, String title, String author, double price) { String sql INSERT INTO book (isbn, title, author, price) VALUES (?, ?, ?, ?); try (Connection conn JDBCUtils.getConnection(); // 关键从连接池获取 PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, isbn); ps.setString(2, title); ps.setString(3, author); ps.setDouble(4, price); return ps.executeUpdate() 0; // executeUpdate() 返回影响行数 } catch (SQLException e) { e.printStackTrace(); return false; } }JDBCUtils.java的核心public static Connection getConnection() throws SQLException { // 初始化时读取 db.properties创建 BasicDataSource if (dataSource null) { Properties props new Properties(); props.load(JDBCUtils.class.getClassLoader().getResourceAsStream(db.properties)); dataSource new BasicDataSource(); dataSource.setDriverClassName(props.getProperty(driver)); dataSource.setUrl(props.getProperty(url)); dataSource.setUsername(props.getProperty(username)); dataSource.setPassword(props.getProperty(password)); dataSource.setInitialSize(5); // 初始连接数 dataSource.setMaxIdle(10); // 最大空闲连接 } return dataSource.getConnection(); // 从连接池取非 new Connection() }提示db.properties中urljdbc:mysql://localhost:3306/lms?useUnicodetruecharacterEncodingutf8mb4的characterEncodingutf8mb4必须与数据库字符集一致否则中文插入为??。3.4 视图层JSP 不是静态页面它是 Struts2 标签 EL 表达式的动态引擎sysManage.jsp中图书列表渲染s:iterator valuebookList statusst tr tds:property valueisbn//td tds:property valuetitle//td tds:property valueauthor//td tds:property valueprice//td td s:a actionbookDelete namespace/ cssClassbtn btn-danger s:param nameisbn%{isbn}/s:param删除 /s:a /td /tr /s:iteratorStruts2 标签解析s:iterator遍历BookAction中private ListBook bookList;的 getter 返回值%{isbn}OGNL 表达式取当前迭代对象Book的isbn属性s:a生成a hrefbookDelete.action?isbn978...namespace/表示根命名空间对应struts.xml中package namedefault namespace/4. 避坑指南这 4 个血泪问题让 80% 的人卡在部署前 30 分钟附现象、原因与一键修复命令4.1 现象Tomcat 启动成功但访问http://localhost:8080/lms报 404日志无报错原因WebContent文件夹未正确放入webapps或文件夹名不是lmsTomcat 以文件夹名为上下文路径解决确认D:\tomcat85\webapps\lms\WEB-INF\web.xml存在检查web.xml中display-namelms/display-name与文件夹名一致一键验证在webapps目录下执行dir /ad应看到lms文件夹非lms.war4.2 现象登录页能打开输入admin/admin后跳回login.jsp无任何提示原因db.properties中数据库密码错误或lms数据库未创建导致LoginAction查询user表时ResultSet为空解决用 MySQL 客户端直连mysql -u root -p -D lms执行SELECT * FROM user WHERE usernameadmin;若报错Unknown database lms重新执行lms.sql若查询无结果检查data.sql是否执行成功Query OK, 2 rows affected4.3 现象点击“添加图书”报HTTP Status 500 – java.lang.NoClassDefFoundError: org/apache/commons/dbcp/BasicDataSource原因commons-dbcp-1.4.jar缺失或版本不匹配如用了commons-dbcp-2.0.jar其包路径为org.apache.commons.dbcp2解决进入WebContent\WEB-INF\lib执行dir commons-dbcp*确认存在commons-dbcp-1.4.jar若存在多个版本删除commons-dbcp-2.x.jar只保留1.4验证命令jar -tf commons-dbcp-1.4.jar | findstr BasicDataSource应输出org/apache/commons/dbcp/BasicDataSource.class4.4 现象图书列表显示??乱码但数据库中中文正常原因JDBC 连接 URL 缺少characterEncodingutf8mb4或 MySQL 服务端字符集非utf8mb4解决检查lmsDB/db.properties中url行末尾是否有?useUnicodetruecharacterEncodingutf8mb4登录 MySQL 执行SHOW VARIABLES LIKE character_set%; SHOW VARIABLES LIKE collation%;确保character_set_server和collation_server为utf8mb4否则修改my.cnf[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重启 MySQL 服务5. 从 Struts2 切换到 Spring MVC三步改造清单保留 DAO 层重写 Controller 与配置5.1 为什么换不是为了时髦而是解决 Struts2 的两个硬伤硬伤 1XML 配置爆炸——struts.xmlstruts-sysadmin.xmlstruts-bookadmin.xml共 3 个文件路由分散难维护硬伤 2拦截器耦合重——LoginInterceptor里硬编码actionName.startsWith(sysManage)新增管理员功能需改拦截器Spring MVC 的优势RequestMapping(/sysManage)直接标注在 Controller 方法上路由一目了然PreAuthorize(hasRole(ADMIN))用注解替代拦截器权限规则集中到 SecurityConfig5.2 保留什么DAO 层与 JDBCUtils 完全复用零改造BookDAO.java和JDBCUtils.java不需任何修改因为Spring JDBC Template 本质仍是ConnectionPreparedStatementJDBCUtils.getConnection()返回的Connection可直接传给JdbcTemplate你只需在BookService.java中将new BookDAO()替换为Autowired private BookDAO bookDAO;5.3 新增配置web.xml spring-mvc.xml pom.xml 三处关键改动Step 1web.xml 中替换 Struts2 Filter 为 Spring DispatcherServlet!-- 删除原 Struts2 filter -- filter filter-namestruts2/filter-name filter-classorg.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter/filter-class /filter !-- 新增 Spring MVC DispatcherServlet -- servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-value/WEB-INF/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern /servlet-mappingStep 2创建 WEB-INF/spring-mvc.xml启用注解与组件扫描?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:contexthttp://www.springframework.org/schema/context xmlns:mvchttp://www.springframework.org/schema/mvc xsi:schemaLocation http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd !-- 扫描 Controller 包 -- context:component-scan base-packagecontroller/ !-- 启用 MVC 注解驱动 -- mvc:annotation-driven/ !-- 视图解析器JSP 前缀 /WEB-INF/jsp/后缀 .jsp -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean /beansStep 3pom.xml 添加 Spring 依赖Maven 项目dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.31/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version5.3.31/version /dependency注意spring-webmvc5.3.x 兼容 JDK 8与本项目现有环境无缝衔接若用 JDK 11需升至 5.3.31。5.4 Controller 重写用 Controller 替代 Action用 RequestParam 替代属性注入新建controller/BookController.javaController RequestMapping(/book) public class BookController { Autowired private BookService bookService; // GET /book/list → 返回图书列表 GetMapping(/list) public String list(Model model) { ListBook bookList bookService.getAllBooks(); model.addAttribute(bookList, bookList); return book/list; // 对应 /WEB-INF/jsp/book/list.jsp } // POST /book/add → 处理添加请求 PostMapping(/add) public String add(RequestParam String isbn, RequestParam String title, RequestParam String author, RequestParam double price, Model model) { boolean result bookService.addBook(isbn, title, author, price); if (result) { model.addAttribute(message, 添加成功); } else { model.addAttribute(error, 添加失败); } return redirect:/book/list; } }对比 Struts2RequestParam显式声明参数比this.isbn更安全避免空指针return redirect:/book/list实现重定向防止表单重复提交model.addAttribute()替代ActionContext.getContext().put()更符合 Spring 生态6. 验证系统健壮性的 3 个真实场景测试以及我每次重构前必做的 5 分钟 checklist6.1 场景测试 1并发借书——模拟 100 个用户同时借同一本书目的验证BookDAO.updateStock()的事务隔离性操作在book表中设stock1仅剩 1 本用 JMeter 创建线程组100 个线程循环 1 次HTTP 请求POST /book/borrow?isbn978...检查borrow_record表记录数是否为 1而非 100预期结果只有 1 条记录其余请求因stock 0被拒绝底层机制BookDAO.borrowBook()中UPDATE book SET stock stock - 1 WHERE isbn ? AND stock 0的AND stock 0是乐观锁配合 MySQL 的InnoDB行锁确保高并发下库存不超卖6.2 场景测试 2SQL 注入防御——在搜索框输入 OR 11目的确认BookDAO.searchBooks()使用PreparedStatement而非字符串拼接操作在index.jsp搜索框输入 OR 11提交后观察返回结果预期结果返回空列表或报错You have an error in your SQL syntax因 PreparedStatement 参数化后单引号被转义代码验证检查BookDAO.java中searchBooks()方法String sql SELECT * FROM book WHERE title LIKE ?; // ✅ 正确? 占位符 // String sql SELECT * FROM book WHERE title LIKE % keyword %; // ❌ 危险字符串拼接6.3 场景测试 3文件上传安全——尝试上传 .jsp 文件作为图书封面目的验证UploadAction的文件类型白名单控制操作修改uploadResult.jsp中input typefile namecover选择一个伪造的.jsp文件内容为% out.print(hacked); %提交上传预期结果页面提示“不支持的文件类型”WebContent/upload/目录下无文件生成机制定位UploadAction.java中validate()方法public void validate() { if (coverFileName ! null !coverFileName.toLowerCase().endsWith(.jpg) !coverFileName.toLowerCase().endsWith(.png)) { this.addFieldError(cover, 只支持 JPG/PNG 格式); } }6.4 我的 5 分钟重构 checklist每次改代码前强制执行省去 80% 的回滚时间数据库备份mysqldump -u root -p lms lms_backup_$(date %Y%m%d).sqlJAR 包快照cd WebContent\WEB-INF\lib zip -r lib_backup.zip *.jar配置文件 diff用 Beyond Compare 对比db.properties与db.properties.bak确认连接参数未被覆盖日志开关在log4j.properties中临时开启log4j.logger.com.lmsDEBUG捕获 DAO 层 SQL浏览器缓存清空CtrlF5 强制刷新避免旧 JS/CSS 导致界面异常从那以后我每次改struts.xml或web.xml都强制走一遍这 5 步——去年有次误删了filter-mapping靠第 2 步的lib_backup.zip5 分钟就恢复了全部依赖没耽误联调。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?