简介:这份资源是基于JavaEE的图书管理系统完整源码包,面向正在学习企业级Java开发的学生与初级开发者,帮助其通过真实项目理解MVC与三层架构的落地方式。压缩包共93个文件、约6.62MB,以java源码、jsp页面、xml配置、jar依赖和class文件为主,涵盖Servlet、JDBC、Struts、Spring、MyBatis等核心技术的实际运用,并包含登录、图书管理、系统管理等模块页面。已有146人学习下载,适合作为课程设计或毕业设计的参考模板。读者可从中获取完整的项目目录结构、分层代码组织方式、数据库连接与事务处理思路,以及前端jQuery与Bootstrap的集成示例,便于导入IDE后对照调试,快速掌握JavaEE项目从表现层到数据访问层的开发流程与常见配置要点。
1. 从一份 Eclipse 时代的 JavaEE 图书管理系统压缩包说起
如果你手上正好有一个java_ee_LibraryManagementSystem-master的压缩包,解压后看到.classpath、.project、.settings这些 Eclipse 工程文件,还有struts.xml、struts-sysadmin.xml、struts-bookadmin.xml、struts-user.xml四个 Struts 配置,外加WebContent下一堆 JSP,那你拿到的是一份典型的「SSH 时代」JavaEE 课设级项目。它不新,但结构完整:src下按bean、interceptor、action分包,lmsDB目录里大概率躺着建库脚本,WebContent里login.jsp、sysadmin.jsp、manager.jsp、history.jsp各司其职。这套东西能解决的核心问题是:让你在一个真实可跑的多角色权限系统里,把 Struts2 的 Action 分发、拦截器栈、JDBC 数据访问和 JSP 视图渲染串成一条线。适合谁?适合正在做课程设计、需要一份能改能跑能讲清楚分层结构的 JavaEE 参考实现的人,也适合想回头补一补「没有 Spring Boot 之前企业应用长什么样」的开发者。下面我按实际拆包、导库、改配置、跑起来的顺序,把这份资源讲透。
2. 拆包先看结构:Struts2 多模块配置与三层架构怎么对应
2.1 四个 struts 配置文件的分工逻辑
拿到包先别急着往 IDE 里导,先把src根目录下的四个 XML 打开对比。struts.xml是主配置,通常只做全局常量、拦截器栈定义和include其他三个文件;struts-sysadmin.xml、struts-bookadmin.xml、struts-user.xml分别对应系统管理员、图书管理员、普通用户三个角色的 Action 映射。这种按角色拆配置文件的做法,在当年是很实用的权限隔离手段——不同角色能访问的 Action 在物理文件层面就分开了,改一个角色的路由不会误伤另一个。
常见做法是主配置里写:
<struts> <constant name="struts.devMode" value="true"/> <constant name="struts.i18n.encoding" value="UTF-8"/> <include file="struts-sysadmin.xml"/> <include file="struts-bookadmin.xml"/> <include file="struts-user.xml"/> </struts>struts.devMode设成true是调试期必须的,改完 XML 不用重启 Tomcat,报错页面也会把完整堆栈打出来。struts.i18n.encoding设 UTF-8 是血泪经验,不设的话 JSP 表单提交中文书名进数据库就是乱码。三个include的顺序不影响功能,但建议按角色权限从高到低排,排查问题时一眼能定位到是哪个角色的映射出了岔子。
2.2 src 分包与三层架构的对应关系
src下能看到bean、interceptor、action三个包,这正好对应三层架构里的模型层、控制层和横切关注点。bean包里是实体类,字段跟lmsDB里表的列一一对应,比如 Book 类有bookId、bookName、author、publisher、stock这些属性。action包是 Struts2 的 Action 类,每个类里一个方法对应一个请求,方法返回字符串决定跳哪个 JSP。interceptor包放自定义拦截器,最典型的就是登录校验拦截器——在 Action 执行前检查session里有没有用户信息,没有就踢回loginError.jsp。
数据访问层没有单独建包,通常混在action里直接写 JDBC,或者放在bean旁边一个dao包。你拿到包后先确认lmsDB目录里有没有.sql文件,有的话直接拿去建库;没有的话就得根据bean里的字段反推表结构。我一般会先看action里PreparedStatement的 SQL 语句,把用到的表名和列名抄出来,再补建表脚本。
2.3 导入 Eclipse 前的环境检查清单
这份工程带.classpath和.project,说明原作者用的是 Eclipse。如果你用 IntelliJ IDEA,直接Open项目根目录,IDEA 会自动识别 Eclipse 工程并转换。但转换前先确认三件事:JDK 版本、Tomcat 版本、数据库驱动。
| 检查项 | 常见值 | 不匹配的后果 |
|---|---|---|
| JDK | 1.7 或 1.8 | 高版本 JDK 编译 Struts2 老版本可能报Unsupported major.minor |
| Tomcat | 7 或 8 | Tomcat 10 的javax.*改成了jakarta.*,老项目直接跑不起来 |
| MySQL 驱动 | mysql-connector-java-5.x | 用 8.x 驱动连 5.x 数据库要改 URL 参数 |
| Struts2 版本 | 2.3.x 或 2.5.x | 2.5 之后struts.xml的 DTD 和包名有变化 |
提示:先看
WEB-INF/lib下有没有 jar 包。如果 lib 是空的,说明依赖没随包带出来,你得自己按struts.xml里的 DTD 版本去补 Struts2 核心包和mysql-connector。
3. 让系统跑起来:建库、改连接、配 Tomcat 的完整链路
3.1 从 lmsDB 目录还原数据库
lmsDB这个目录名基本就是「library management system database」的缩写。进去看,如果有.sql文件,直接用命令行导入:
mysql -u root -p -e "CREATE DATABASE lmsDB DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p lmsDB < lmsDB/lmsDB.sql第一句建库时指定utf8mb4,第二句把脚本灌进去。如果脚本里已经写了CREATE DATABASE,第一句可以跳过。导入完执行SHOW TABLES;确认表都进去了,通常会有user、book、borrow_record这几张核心表。user表里一般有个role字段区分 sysadmin、bookadmin、user 三种角色,登录时靠这个字段决定跳哪个主页。
如果lmsDB里没有.sql文件,只有一堆.frm、.ibd文件,那是 MySQL 的物理数据文件,不能直接导入,得找原作者要逻辑备份。这种情况我一般会根据action里的 SQL 反推建表语句,下面是一个典型的 book 表还原示例:
CREATE TABLE book ( book_id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), stock INT DEFAULT 0, price DECIMAL(10,2) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;book_id设自增主键,stock默认 0 防止借书时库存为负,price用DECIMAL而不是FLOAT避免金额精度丢失。这些细节在还原时补上,后面跑借阅流程才不容易出玄学问题。
3.2 修改 JDBC 连接配置
数据库建好后,去src下找连接配置。Struts2 项目通常有两种写法:一种是在struts.xml里配struts.properties或常量,另一种是直接写个DBUtil类硬编码。搜jdbc:mysql这个关键字,找到后改三处:URL、用户名、密码。
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/lmsDB?useUnicode=true&characterEncoding=utf8&useSSL=false"; private static final String USER = "root"; private static final String PASSWORD = "your_password"; public static Connection getConnection() throws SQLException { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new SQLException("MySQL 驱动未找到", e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }useUnicode=true&characterEncoding=utf8这两个参数是中文不乱码的关键,useSSL=false是避免启动时一堆 SSL 警告。驱动类名com.mysql.jdbc.Driver对应 5.x 版本,如果你用的是 8.x 驱动,要改成com.mysql.cj.jdbc.Driver,URL 里还得加serverTimezone=Asia/Shanghai,否则连库直接抛时区异常。改完这些,写个main方法调一下getConnection(),能打印出连接对象就说明数据库这关过了。
3.3 Tomcat 部署与上下文路径
Eclipse 里配 Tomcat 的步骤是:Window > Preferences > Server > Runtime Environments添加 Tomcat 安装目录,然后把项目Add and Remove到服务器实例上。关键点是WebContent目录会被识别为 Web 根,WEB-INF/web.xml里配的struts2过滤器负责拦截所有请求。
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>StrutsPrepareAndExecuteFilter是 Struts2 2.1 之后的标准过滤器,url-pattern写/*表示所有请求都过 Struts2 的路由。启动 Tomcat 后访问http://localhost:8080/项目名/login.jsp,能出登录页就说明部署成功。如果 404,检查WebContent下index.jsp是否存在、web.xml的welcome-file有没有配错;如果 500 且报ClassNotFoundException,基本是WEB-INF/lib下缺 Struts2 的 jar。
3.4 登录流程与角色跳转验证
系统跑起来后第一件事是验证登录链路。打开login.jsp,看表单action指向哪个 Action,通常是loginAction或userLogin。提交后 Action 里会拿用户名密码去user表查,查到就把用户对象塞进session,然后根据role字段返回不同结果字符串。
public String execute() { User user = userService.login(username, password); if (user == null) { return "loginError"; } ServletActionContext.getRequest().getSession().setAttribute("currentUser", user); if ("sysadmin".equals(user.getRole())) { return "sysadmin"; } else if ("bookadmin".equals(user.getRole())) { return "bookadmin"; } return "user"; }返回的字符串在struts-*.xml里映射到不同 JSP,sysadmin跳sysadmin.jsp,bookadmin跳manager.jsp,user跳index.jsp。验证时三个角色各登一次,确认跳转正确、session里能取到用户信息。如果登录后一直跳回loginError.jsp,先查数据库里密码是不是明文存的,再查 Action 里有没有对密码做 MD5 加密而数据库存的是明文——这种不一致是课设项目最常见的翻车点。
4. 避坑与排查:这份老工程最容易卡住的五个地方
4.1 启动报 ClassNotFoundException: com.mysql.jdbc.Driver
现象是 Tomcat 启动或第一次访问数据库时抛ClassNotFoundException,堆栈指向DBUtil的Class.forName。原因就一个:WEB-INF/lib下没有 MySQL 驱动 jar,或者驱动版本和代码里写的类名对不上。解决方法是确认lib下有mysql-connector-java-5.1.x.jar,如果用的是 8.x 驱动,把Class.forName("com.mysql.jdbc.Driver")改成com.mysql.cj.jdbc.Driver,同时 URL 加serverTimezone参数。改完记得在 Eclipse 里Project > Clean一下,让build/classes重新编译。
4.2 JSP 中文乱码,数据库里存进去是问号
现象是页面上输入中文书名,提交后数据库里变成???。原因有两层:JSP 页面没声明pageEncoding,或者数据库连接没带字符集参数。解决分三步:每个 JSP 头部加<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>;DBUtil的 URL 加useUnicode=true&characterEncoding=utf8;建库时用utf8mb4而不是默认的latin1。三处都改完还乱码,就去 MySQL 命令行执行SHOW VARIABLES LIKE 'character%';看服务端字符集,character_set_server不是utf8mb4的话得改my.ini重启服务。
4.3 Struts2 报 There is no Action mapped for namespace
现象是访问某个功能页时 404,控制台提示There is no Action mapped for namespace [/] and action name [xxx]。原因是struts-*.xml里没配这个 Action 的映射,或者include没生效。排查顺序:先确认struts.xml里include了对应的子配置文件;再确认子配置里package的namespace和action的name跟 JSP 表单的action属性完全一致,大小写敏感;最后确认struts.devMode是true,这样启动时会打印所有已加载的 Action 映射,一眼能看出缺哪个。
4.4 拦截器把登录请求也拦了,导致死循环
现象是访问login.jsp提交后一直跳回登录页,或者直接报重定向次数过多。原因是自定义的登录校验拦截器url-pattern配成了/*,把loginAction本身也拦了。解决是在拦截器里放行登录相关的 Action,或者用 Struts2 的excludeMethods参数排除login方法。常见写法是在struts.xml的拦截器栈里加:
<interceptor-ref name="loginInterceptor"> <param name="excludeMethods">login,execute</param> </interceptor-ref>excludeMethods里列的方法名不会被拦截,多个用逗号隔开。改完重启 Tomcat,确认登录请求能正常进 Action 而不是被踢回来。
4.5 Tomcat 端口占用或项目上下文冲突
现象是启动 Tomcat 报Address already in use: JVM_Bind,或者访问时跳到另一个项目的页面。原因是 8080 端口被别的进程占了,或者 Eclipse 里同时部署了多个项目导致上下文路径冲突。解决:命令行执行netstat -ano | findstr 8080找到占用进程 PID,任务管理器里结束它;或者在 Eclipse 的 Tomcat 配置里把 HTTP 端口改成 8081。上下文冲突的话,双击 Tomcat 实例,在Modules标签页里确认只有当前项目被部署,Path列显示的就是访问路径。
5. 进阶改造:把 JDBC 换成连接池并加一层 DAO
5.1 为什么课设级 JDBC 需要连接池
原工程每个 Action 里DBUtil.getConnection()一次,用完close(),请求量一上来就频繁创建销毁连接,数据库端Threads_connected飙高。常见做法是引入 Druid 或 C3P0 连接池,把连接的创建和回收交给池管理。以 Druid 为例,在src下加druid.properties:
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/lmsDB?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username=root password=your_password initialSize=5 maxActive=20 maxWait=3000initialSize是启动时预建的连接数,maxActive是池上限,maxWait是拿不到连接时的最长等待毫秒数。这三个参数按你机器配置和并发预期调,课设场景initialSize=5、maxActive=20足够。然后在DBUtil里改成从DruidDataSourceFactory拿连接:
public class DBUtil { private static DataSource dataSource; static { try { Properties props = new Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError("连接池初始化失败: " + e.getMessage()); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }静态代码块保证池只初始化一次,getConnection()从池里借连接,close()时 Druid 会自动把连接还回池里而不是真关掉。改完压一下登录接口,看日志里连接创建次数是不是明显下降。
5.2 抽一层 DAO 把 SQL 从 Action 里挪出去
原工程 SQL 散落在各个 Action 里,改一个表结构要翻好几个文件。抽 DAO 的思路是:每个实体一个 DAO 类,Action 只调 DAO 方法,不碰PreparedStatement。以 Book 为例:
public class BookDao { public List<Book> findAll() throws SQLException { String sql = "SELECT book_id, book_name, author, publisher, stock, price FROM book"; List<Book> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Book b = new Book(); b.setBookId(rs.getInt("book_id")); b.setBookName(rs.getString("book_name")); b.setAuthor(rs.getString("author")); b.setPublisher(rs.getString("publisher")); b.setStock(rs.getInt("stock")); b.setPrice(rs.getBigDecimal("price")); list.add(b); } } return list; } }try-with-resources保证Connection、PreparedStatement、ResultSet三个资源自动关闭,不用手写finally块。rs.getBigDecimal("price")对应数据库的DECIMAL类型,避免getDouble的精度问题。Action 里改成new BookDao().findAll(),代码量少一半,后面换 MyBatis 或 JPA 时也只需要改 DAO 层,Action 不动。
5.3 用 JUnit 验证 DAO 层,别每次都靠点页面
改完 DAO 后,写个 JUnit 测试类跑一遍增删查改,比每次启动 Tomcat 点页面快得多:
public class BookDaoTest { private BookDao dao = new BookDao(); @Test public void testFindAll() throws SQLException { List<Book> books = dao.findAll(); assertNotNull(books); System.out.println("查到 " + books.size() + " 本书"); } @Test public void testInsertAndDelete() throws SQLException { Book b = new Book(); b.setBookName("测试书籍"); b.setAuthor("测试作者"); b.setStock(1); int id = dao.insert(b); assertTrue(id > 0); assertEquals(1, dao.deleteById(id)); } }testFindAll验证查询链路通不通,testInsertAndDelete验证写操作和事务。跑测试前确认druid.properties在test资源目录下也能被加载,否则会报NullPointerException。这套测试跑通后,再改 DAO 里的 SQL 就有底气了——改完跑一遍,绿了就说明没破坏原有功能。从那以后我每次动数据库相关的代码,都强制先跑一遍 DAO 测试再启动 Tomcat,省得在页面上反复点。希望这份拆解能帮你把这份老工程顺利跑起来,并在它基础上做出自己的改造。
本文还有配套的精品资源,点击获取