尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

JavaEE图书管理系统实战:从环境搭建到借阅闭环的避坑指南

发布时间:2026/9/24 18:18:01

资讯中心
01
ARTICLE

JavaEE图书管理系统实战:从环境搭建到借阅闭环的避坑指南

JavaEE图书管理系统实战:从环境搭建到借阅闭环的避坑指南
简介这是一套面向JavaEE初学者与课程设计者的图书管理系统完整源码基于MVC与三层架构实现适合用于毕业设计、课程作业或企业级开发入门练习。压缩包共93个文件约6.62MB包含21个java源文件、11个jsp页面、10个xml配置以及21个jar依赖另有class编译文件、properties配置、css样式与图片资源覆盖表现层、业务层与数据访问层的完整结构。系统集成Struts、Spring、MyBatis等主流框架涉及Servlet、JSP、JDBC、JPA与Hibernate等核心技术并包含登录、图书管理、系统管理等模块前端采用HTML、CSS、JavaScript与jQuery实现交互。已有145人学习下载读者可借此理解JavaEE项目从配置到部署的全流程掌握框架整合、数据库操作与权限控制的实现思路快速搭建可运行的图书管理应用。1. 基于 JavaEE 的图书管理系统从课程设计到能跑通的最小闭环很多同学拿到「基于 JavaEE 的图书管理系统」这个题目时第一反应是去搜一套现成源码解压、导入、点运行然后发现数据库连不上、Tomcat 起不来、页面 404。这套系统本身不复杂难的是把 JavaEE 那套分层结构、容器部署、数据库连接串成一条能跑通的链路。它解决的核心问题是用 Servlet JSP JDBC 这套经典组合实现图书的增删改查、借阅归还和用户权限控制。适合正在做课程设计、毕业设计或者想补上 JavaWeb 基础实操的开发者。下面我按「环境怎么配 → 分层怎么写 → 坑在哪 → 怎么验证」的顺序把这条链路拆开讲清楚。2. 环境与工程结构把 JavaEE 运行环境先立住2.1 JDK、Tomcat、MySQL 的版本搭配与安装顺序JavaEE 项目跑不起来八成是环境版本打架。我一般固定一套组合JDK 8 或 JDK 11、Tomcat 8.5 或 9.0、MySQL 5.7 或 8.0。JDK 17 配 Tomcat 8 会出现模块化相关的类加载报错新手很难排查所以不建议一上来就用最新版。安装顺序也有讲究先装 JDK配好JAVA_HOME再装 Tomcat最后装 MySQL。Tomcat 启动时会读JAVA_HOME如果 JDK 没配好startup.bat一闪而过日志里只有一行Neither the JAVA_HOME nor the JRE_HOME environment variable is defined。# 验证 JDK 是否配好 java -version javac -version # 验证 Tomcat 能否启动Windows cd %CATALINA_HOME%\bin startup.bat # 验证 MySQL 服务 mysql -u root -p -e SELECT VERSION();这三条命令分别验证 Java 编译环境、Servlet 容器和数据库服务。java -version输出 1.8 或 11 都行关键是和 Tomcat 匹配。Tomcat 启动后浏览器访问http://localhost:8080能看到默认页说明容器没问题。MySQL 能返回版本号说明服务在跑。提示Tomcat 默认端口 8080如果被占用改conf/server.xml里的Connector port别去改系统环境变量。2.2 用 Maven 搭出标准 Web 工程目录不要手动建WEB-INF和web.xml容易漏。用 Maven 的webapp骨架生成目录结构天然符合 Servlet 规范。mvn archetype:generate -DgroupIdcom.library \ -DartifactIdlibrary-system \ -DarchetypeArtifactIdmaven-archetype-webapp \ -DinteractiveModefalse生成的目录里src/main/webapp放 JSP 和静态资源src/main/java放 Servlet 和 Servicesrc/main/resources放数据库配置。pom.xml里要加三个依赖javax.servlet-apiscope 设 provided因为 Tomcat 自带、mysql-connector-java、jstl。provided这个 scope 很关键打成 war 包时不带 Servlet API否则和容器里的类冲突启动就报ClassCastException。dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency参数说明javax.servlet-api的provided表示编译时需要、打包时排除mysql-connector-java8.x 对应 MySQL 8.0驱动类名是com.mysql.cj.jdbc.Driver5.x 是com.mysql.jdbc.Driver写错就报ClassNotFoundException。JSTL 用于 JSP 页面里做循环和判断不加的话c:forEach标签直接不解析。3. 分层实现Servlet 接请求、Service 管逻辑、JDBC 落数据3.1 数据库表设计与连接池配置图书管理系统的表不多但字段类型和索引要一次定对不然后面改表很痛苦。核心四张表user用户、book图书、borrow借阅记录、category分类。CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE, title VARCHAR(100) NOT NULL, author VARCHAR(50), category_id INT, total INT DEFAULT 1, available INT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE borrow ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATE NOT NULL, return_date DATE, status TINYINT DEFAULT 0 COMMENT 0借出 1已还 );isbn加唯一索引防止同一本书重复录入。available字段单独维护可借数量比每次去borrow表 count 快得多。status用 TINYINT 而不是字符串查询和索引效率更高。连接池不要用DriverManager每次新建连接课程设计里并发虽然低但养成用连接池的习惯。常见做法是配 Druid 或 HikariCP在src/main/resources/druid.properties里写driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/library?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 usernameroot password你的密码 initialSize5 maxActive20serverTimezone不写MySQL 8 会报时区错误characterEncodingutf8不写中文书名存进去变问号。这两个参数是血泪经验少一个都翻车。3.2 图书查询与借阅的 Servlet 实现Servlet 层只做三件事收参数、调 Service、转发页面。业务逻辑不要写在 Servlet 里否则后面加个「借阅超期提醒」就得改一堆地方。WebServlet(/book/list) public class BookListServlet extends HttpServlet { private BookService bookService new BookServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String keyword req.getParameter(keyword); int page req.getParameter(page) null ? 1 : Integer.parseInt(req.getParameter(page)); // 每页 10 条keyword 为空则查全部 PageResultBook result bookService.findByPage(keyword, page, 10); req.setAttribute(pageResult, result); req.getRequestDispatcher(/WEB-INF/views/book/list.jsp) .forward(req, resp); } }WebServlet注解替代了web.xml里的servlet-mappingServlet 3.0 以后都这么写。forward是服务器内部跳转浏览器地址栏不变如果用sendRedirectrequest 里的属性会丢页面拿不到数据。/WEB-INF/views/下的 JSP 不能直接被浏览器访问必须走 Servlet 转发这是防止用户绕过权限校验直接看页面的基本手段。借阅操作涉及事务先查available是否大于 0再减库存、插借阅记录两步必须在一个事务里。public boolean borrow(int userId, int bookId) { Connection conn null; try { conn DruidUtils.getConnection(); conn.setAutoCommit(false); BookDao bookDao new BookDaoImpl(conn); BorrowDao borrowDao new BorrowDaoImpl(conn); int available bookDao.getAvailable(bookId); if (available 0) { conn.rollback(); return false; } bookDao.decreaseAvailable(bookId); borrowDao.insert(userId, bookId); conn.commit(); return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }这里 DAO 接收同一个Connection保证两个操作在同一个事务里。如果各自从连接池拿连接事务就失效了会出现库存减了但借阅记录没插进去的情况。setAutoCommit(false)之后必须显式commit或rollback最后在finally里恢复自动提交再关连接否则连接池回收的连接状态是脏的。3.3 JSP 页面用 JSTL 渲染数据列表JSP 里不要写 Java 脚本片段% %用 JSTL 和 EL 表达式页面干净也好维护。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table trthISBN/thth书名/thth作者/thth可借/thth操作/th/tr c:forEach items${pageResult.list} varbook tr td${book.isbn}/td td${book.title}/td td${book.author}/td td${book.available}/td td c:if test${book.available 0} a href${pageContext.request.contextPath}/borrow?bookId${book.id}借阅/a /c:if c:if test${book.available 0}已借完/c:if /td /tr /c:forEach /table${pageContext.request.contextPath}拿到项目部署路径避免硬编码/library-system换部署名时不用改页面。c:if控制借阅按钮的显示库存为 0 时不给点减少无效请求。EL 表达式${book.title}会自动调用 getter前提是实体类字段名和属性名一致。4. 避坑与排查这套系统最容易翻车的五个地方4.1 中文乱码POST 和 GET 处理方式不一样现象表单提交的书名在数据库里变成???或者页面显示乱码。原因Tomcat 8 以后 GET 请求默认 URI 编码是 UTF-8但 POST 请求体默认还是 ISO-8859-1。很多人只加了request.setCharacterEncoding(UTF-8)对 GET 无效。解决POST 在 Servlet 第一行设request.setCharacterEncoding(UTF-8)GET 要么在server.xml的 Connector 上加URIEncodingUTF-8要么统一用 POST 提交。数据库连接串里characterEncodingutf8也必须加三层编码一致才不会乱。4.2 数据库连接报 Public Key Retrieval 错误现象MySQL 8.0 下启动项目报Public Key Retrieval is not allowed。原因MySQL 8 默认用caching_sha2_password认证插件JDBC 连接时如果不允许公钥检索就握手失败。解决连接串加allowPublicKeyRetrievaltrue或者把用户认证插件改成mysql_native_password。前者改配置就行后者要执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;。我一般用前者不动数据库用户设置。4.3 404Servlet 映射路径和 JSP 存放位置对不上现象访问/book/list报 404Tomcat 日志没有异常。原因三种可能——WebServlet的路径写错、web.xml的metadata-completetrue导致注解不扫描、JSP 放在WEB-INF下却直接用浏览器访问。解决先看 Tomcat 启动日志里有没有Servlet [BookListServlet] mapped to [/book/list]没有就是注解没生效检查web.xml的version是不是 3.0 以上。JSP 在WEB-INF下只能通过 Servlet 转发访问地址栏直接敲路径必然 404这是设计如此。4.4 连接池耗尽Connection 没关导致后续请求卡死现象系统用一会儿就卡住所有请求转圈重启 Tomcat 又好了。原因某个 DAO 方法里Connection在异常分支没关闭连接池的maxActive20被占满新请求拿不到连接就一直等。解决所有数据库操作放在try-finally里finally中关闭Connection、PreparedStatement、ResultSet。更稳妥的做法是用 try-with-resources让编译器保证关闭。排查时可以在 Druid 监控页面看活跃连接数或者临时把maxActive调大看是否缓解缓解了就说明是泄漏。4.5 事务不生效Service 里自己 new 了 DAO 的连接现象借阅时库存减了但借阅记录没插入或者反过来。原因Service 方法上加了Transactional但没配 Spring或者 DAO 内部各自从连接池拿连接两个操作不在同一事务。解决不用 Spring 的话就手动传同一个Connection给 DAO像 3.2 节那样显式commit/rollback。用了 Spring 的话确认DataSourceTransactionManager配了、Transactional注解的类被 Spring 管理、方法不是private。事务这个问题不报错只表现为数据不一致最难查建议借阅逻辑写完立刻测「库存为 0 时借阅」这个边界。5. 进阶技巧用过滤器统一权限校验和字符编码系统做到后面每个 Servlet 都写一遍编码设置和登录校验太啰嗦用 Filter 统一处理。这是 JavaEE 里最实用的一个进阶点也是课程设计加分项。WebFilter(/*) public class EncodingAndAuthFilter implements Filter { // 不需要登录就能访问的路径 private static final String[] WHITE_LIST {/login, /register, /css/, /js/}; Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String uri request.getRequestURI(); String path uri.substring(request.getContextPath().length()); boolean white false; for (String w : WHITE_LIST) { if (path.startsWith(w)) { white true; break; } } if (white || request.getSession().getAttribute(user) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login); } } }WebFilter(/*)拦截所有请求白名单里的路径直接放行其余检查 session 里有没有登录用户。chain.doFilter是放行到下一个过滤器或目标 Servlet不调用的话请求就断在这里。这个过滤器同时解决了编码和权限两件事比在每个 Servlet 里重复写要干净得多。验证过滤器是否生效可以打开浏览器开发者工具看登录后请求头里的Content-Type是不是text/html;charsetUTF-8再退出登录访问/book/list应该被重定向到登录页而不是直接看到列表。如果重定向后死循环检查白名单里有没有把登录页本身加进去。参数调优上WHITE_LIST用数组硬编码适合小系统路径多了建议改成读配置文件。另外过滤器里不要做耗时操作它拦截所有请求一个慢查询会拖垮整个应用。我自己做这类系统时习惯先把「登录 → 查书 → 借书 → 还书」这条主链路跑通再去加分类管理、超期提醒这些边角功能。主链路不通就堆功能最后排查起来是灾难。数据库连接串里的时区和编码参数我每次都会先写好再动代码省得后面中文乱码来回折腾。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。