ARTICLE DETAIL

资讯详情

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

从零实现图书管理系统:Servlet+JSP+JDBC+MySQL完整实战指南

从零实现图书管理系统:Servlet+JSP+JDBC+MySQL完整实战指南 简介这是一份完整的 JavaWeb 图书管理系统项目材料面向正在学习 JSP/Servlet、JDBC、DAO 与 MVC 分层开发的入门开发者尤其适合课程设计与实训参考。项目通过 HTML、CSS、JavaScript 搭建设计页面JSP 负责动态处理请求Servlet 作为控制器调用服务JavaBean 封装图书字段并用 DAO 模式统一管理数据库操作完整展现了 JavaWeb 经典分层开发流程。压缩包共 3 个文件包含 SQL 数据库脚本、Word 设计报告和源码压缩包整体约 4.52MBSQL 脚本可快速还原图书信息表设计报告覆盖需求分析、系统架构、数据库设计及功能模块实现等内容可辅助理解项目全貌。目前已有 1.9 万余人学习下载是 JavaWeb 学习中认可度较高的综合案例。通过这份材料读者既能掌握项目结构搭建也能对照源码理解前后端交互、数据库访问与分层解耦的完整实现。1. 为什么图书管理系统是 JavaWeb 入门的“黄金项目”如果你在技术社区搜过“JavaWeb 项目完整案例”十个结果里至少有七八个是图书管理系统。我刚入行那会儿也觉得这题目太老套直到自己带过几个新人之后才明白这个项目能一直霸榜是有道理的它几乎覆盖了 JavaWeb 开发的所有核心环节——前端页面交互、Servlet 控制层编写、JSP 模板渲染、JDBC 操作 MySQL、Session 会话管理、增删改查业务逻辑一个不落。更重要的是图书管理系统的业务模型足够简单没有电商那种复杂的订单状态机也不需要像社交产品那样考虑高并发但它又不是那种“一看就会、一写就废”的纯 Demo。借书、还书、超期计算、库存扣减这些场景天然就带着事务和状态变更的味道能让你认真思考“一行数据从页面到数据库再回到页面中间到底经历了什么”。这篇文章我打算从一个完整的项目搭建视角来写从技术选型、数据库设计、核心功能实现到前端页面、常见部署坑尽量把每一步的设计理由和踩坑记录都交代清楚。你如果是正在做课程设计的学生或者想巩固 Servlet/JSP/JDBC 基础的自学者这篇文章可以直接当作一份手把手实现指南来参考。2. 技术选型与整体设计思路2.1 为什么选 Servlet JSP MySQL 而不是 Spring 全家桶现在搜索 JavaWeb 项目很多人上来就推荐 Spring Boot MyBatis Plus这当然没错但我不建议零基础的人从框架入手。Servlet 和 JSP 是 JavaWeb 的底层规范你理解了 HttpServletRequest 是怎么把表单数据带进来的理解了为什么 JSP 最终会被编译成 Servlet 执行再去看 Spring MVC 里的 DispatcherServlet 和 RequestMapping 才有代入感否则你永远只是在“调 API”而不是在“写代码”。图书管理系统用原生 Servlet JSP 来实现正好踩中 JavaWeb 学习的黄金区间能感受到 HTTP 请求响应的完整生命周期又不会被繁琐的配置淹没。组件版本上我建议统一使用 Servlet 4.0 JSP 2.3 JSTL 1.2 MySQL 8.0对应 Tomcat 9.x这一套是兼容性最稳的组合。Tomcat 10 以后 Jakarta EE 改名导致包名从 javax.* 变成 jakarta.*网上大量老教程的代码直接跑不起来新手踩这个坑非常打击信心。数据访问层用原生 JDBC不要一上来就上 MyBatis。JDBC 那套“注册驱动 - 获取连接 - 预编译语句 - 执行查询 - 处理结果集 - 关闭资源”的流程虽然啰嗦但它把数据库操作的本质全暴露给你了后面你用任何 ORM 框架都是在封装这套流程。2.2 三层架构与包结构规划项目不能把代码全塞在一个包里我习惯用经典的三层架构来组织Controller 层负责接收请求和跳转页面Service 层处理业务逻辑Dao 层数据访问层只做数据库的增删改查。好处是后面如果要换数据库、改业务规则不用推倒重来。实际的包结构参考如下com.library ├── controller // Servlet 类 ├── service // 业务逻辑接口 实现类 ├── dao // 数据访问接口 实现类 ├── entity // 实体类Book, User, BorrowRecord ├── util // 工具类DBUtil, StringUtil └── filter // 过滤器编码过滤、登录校验过滤WebContent 目录下按功能分文件夹admin 放后台管理页面user 放读者端页面static 放 css/js/imagesWEB-INF 下放 JSP 模板。强调一下WEB-INF 目录下的页面不能通过浏览器地址栏直接访问只能通过 Servlet 服务端转发进入这个特性可以用来做登录拦截防止用户绕过登录直接访问后台页面。三层结构写起来会显得“类很多”但这是好事。图书管理系统的业务复杂度不高如果你在这个阶段就习惯按职责拆分代码后面遇到订单、支付、库存这类复杂业务时才不会把数据库操作混在页面跳转逻辑里搞得一团糟。2.3 开发工具与环境准备开发工具我推荐 IntelliJ IDEA社区版就够用不需要破解旗舰版。如果电脑配置一般也可以用 Eclipse IDE for Enterprise Java and Web Developers注意别下成普通 Java SE 版本否则没有 Dynamic Web Project 选项。这里针对热词里的“vscode 创建 javaweb 项目”多说一句VSCode 可以通过安装 Extension Pack for Java Tomcat for Java 插件来开发 JavaWeb 项目但不建议新手用因为 VSCode 对 Web 项目结构、部署路径、依赖管理的支持都是“能用但不顺手”出了问题排查成本很高。环境版本对照表我整理了一下组件推荐版本说明JDK1.8 或 11不要用 17部分老依赖和 Tomcat 9 不兼容Tomcat9.0.x对应 javax.* 命名空间MySQL8.0.x5.7 也可以注意驱动和连接 URL 差异mysql-connector-java8.0.x驱动类名是 com.mysql.cj.jdbc.DriverIDEA2023 社区版或 Eclipse for JavaEEMaven3.8强烈建议用 Maven 管理 jar 包为什么强烈建议 Maven因为传统方式是把 jar 包手动复制到 WebContent/WEB-INF/lib 目录里一旦依赖多了版本冲突、漏包问题能把人折磨疯。Maven 在 pom.xml 里声明依赖后自动下载统一管理这不仅是工程化习惯也是后面入职项目的基本要求。3. 数据库设计与核心表结构3.1 四张核心表的关系梳理图书管理系统的数据库设计核心在于搞清楚“书”和“读者”之间是多对多的借阅关系。至少需要四张表用户表包含管理员和普通读者、图书表、图书分类表、借阅记录表。下面把每张表的字段设计关键点讲透。用户表设计CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), role TINYINT DEFAULT 0 COMMENT 0-普通用户 1-管理员, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个设计要点我逐个说。username 一定要加唯一约束这是业务上的硬要求同时唯一索引也能加速登录查询。password 字段长度至少 100因为你大概率会存 MD5 加密后的 32 位字符串如果未来想升级成 BCrypt长度不够就得改表结构。role 字段用 TINYINT 而不是字符串省空间且判断效率高。图书表的核心字段要特别注意一个容易忽略的点总库存 total_stock 和当前可借数量 available_stock 要分开设计不能只用一个库存字段。每次借书扣减 available_stock还书时加回来total_stock 保持不变用于统计总馆藏。如果只用一个字段你得在借阅记录里去倒推“这本书到底有多少本”完全没法做。3.2 借阅记录表的状态设计与外键策略借阅记录是系统里业务逻辑最复杂的表因为它关联用户和图书两张表同时还要记录借书时间、应还时间、实际归还时间、状态等关键信息。CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, due_time DATETIME NOT NULL, return_time DATETIME DEFAULT NULL, status TINYINT DEFAULT 0 COMMENT 0-借出 1-已还 2-超期未还, CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES user(id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;status 字段我用了 0/1/2 三种状态0 表示借出未还1 表示已归还2 表示超期未还。这里有个很容易犯的错误很多人把“超期”设计成一个单独的表或者单独的字段其实超期完全可以通过计算 due_time 和当前时间的差值得到不需要真实存储。但保留一个状态值可以简化查询比如首页统计“有多少本超期未还”直接WHERE status 2就能查出来不用每次都在代码里做日期比对。外键到底加不加网上争议很大。图书管理系统这种规模我建议加外键约束因为它能保证数据完整性——比如你不能删除一本仍有借阅记录的图书。但要注意加了外键之后删除顺序有讲究先删借阅记录再删图书否则会报外键约束错误。生产环境大表不加外键是因为性能和高并发下锁竞争的问题小项目根本遇不到这个瓶颈别被“高并发经验”误导。3.3 分类表与初始化数据图书分类表结构简单id、name、description 三个字段就够。但要注意插入初始化数据时必须把默认管理员账号一起加进去否则系统启动后你连后台都进不去。INSERT INTO user (username, password, real_name, role) VALUES (admin, MD5(admin123), 系统管理员, 1);一个小提示JDBC 连接 MySQL 8.0 时URL 需要加上 useSSLfalse 和 serverTimezoneAsia/Shanghai否则会报 SSL 连接警告和时区错误。这个不是代码问题是 MySQL 8.0 的配置变更遇到别慌。4. 核心功能模块的设计与实现4.1 登录与会话管理Cookie/Session 的实战理解登录模块是所有 JavaWeb 项目都躲不开的第一关但别看它简单里面值得讲的细节不少。登录流程是前端表单提交用户名密码 - 后端接收并做非空校验 - 查库匹配 - 成功后把用户信息放进 Session - 跳转首页。密码加密存储是必须的明文存储一旦数据库泄露所有用户账号全裸奔。图书管理系统我用 MD5 加密就够因为它的安全级别不像支付系统那么高MD5 虽然理论上可碰撞但在这个场景下足够了。如果以后做商业项目至少要用 SHA-256 加盐或者 BCrypt。登录成功后不要把密码放进 Session只存用户 id、用户名、角色这几个字段。Session 默认 30 分钟过期这个时间可以在 web.xml 里通过 session-config 配置。如果用户选了“记住我”可以用 Cookie 存一个加密后的用户名标识但不要存明文密码这是底线。登录拦截用 Filter 实现在一个自定义的 LoginFilter 里检查 Session 中是否包含登录标记没有就重定向到登录页有就放行。注意要放行登录页本身和静态资源否则会出现“明明点了登录却一直被弹回登录页”的诡异问题。4.2 图书管理列表分页与条件检索的组合查询图书列表页是系统的门面功能诉求是“能看、能搜、能翻页”。分页查询是这里的技术难点很多新手一开始是把所有图书查出来然后用 Java 代码在内存里 slice图书量小的时候感觉不到问题量一上来页面会越翻越卡。正确的做法是在 SQL 层分页MySQL 的 LIMIT 语法String sql SELECT * FROM book WHERE name LIKE ? LIMIT ?, ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, % keyword %); ps.setInt(2, (currentPage - 1) * pageSize); ps.setInt(3, pageSize);分页的核心参数是 currentPage当前页码和 pageSize每页条数数据库偏移量 (currentPage - 1) * pageSize。举个例子第 1 页每页 5 条偏移量是 0查询前 5 条第 2 页偏移量是 5从第 6 条开始取 5 条。这套计算公式所有分页组件都绕不开。模糊查询用 LIKE 配合 %keyword%注意两个细节一是 SQL 注入问题必须用 PreparedStatement 预编译绑定参数禁止字符串拼接 SQL二是 LIKE 查询性能问题%keyword% 这种写法不会走索引但图书表几千条数据无需担心等到了几十万条再考虑全文索引。分页查询还隐含两个必要的统计查询总记录数 totalCount 和总页数 totalPages。totalCount 用 COUNT(*) 获取totalPages 的计算公式是int totalPages (totalCount % pageSize 0) ? (totalCount / pageSize) : (totalCount / pageSize 1);这个公式用“是否能整除”来做向上取整比 Math.ceil 更直观也避免浮点数精度问题。4.3 借书还书事务处理的状态流转借书和还书是系统性最强的一块因为涉及两张表的数据变更借书要更新借阅记录表同时扣减图书表的 available_stock还书要更新借阅记录的 status/return_time同时加回库存。这两步操作必须在一个数据库事务里完成否则借书成功但库存没减会造成数据不一致。JDBC 事务的标准写法是Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 // 1. 插入借阅记录 // 2. 扣减图书库存 // 3. 更新图书状态 conn.commit(); // 全部成功提交事务 } catch (SQLException e) { conn.rollback(); // 任何一步失败回滚所有操作 throw e; } finally { conn.setAutoCommit(true); conn.close(); }这段代码每次看起来都很“例行公事”但它是事务的完整骨架关闭自动提交 - 执行多条 SQL - 全部成功 commit - 任一失败 rollback - 恢复自动提交并关闭连接。借书时还要做几个前置校验这本书是否存在、当前可借库存是否大于 0、该读者是否已经借了同一本书且未归还。第三个校验很多人漏掉结果就是读者能无限次借同一本书库存被疯狂扣减。校验的 SQL 就是在 borrow_record 里查是否存在 user_id book_id status0 的记录。还书操作比较简单根据借阅记录 id 把 status 更新为 1set return_time NOW()同时把图书的 available_stock 1。超期判断不用单独处理JSP 页面展示时比较 due_time 和当前时间超了就显示红色“已超期”管理员可以按状态 2 筛选超期记录。4.4 JSP 页面渲染与 JSTL 标签的使用JSP 页面不建议直接在 HTML 里写 % % 脚本片段那样一方面页面代码臃肿另一方面项目大了很难维护。正确做法是 JSP EL 表达式 JSTL 标签三件套EL 负责取值JSTL 负责逻辑控制HTML 负责布局展示。举个例子图书列表页的核心渲染部分c:forEach items${pageBean.list} varbook tr td${book.id}/td td${book.name}/td td${book.author}/td td${book.availableStock}/td td c:if test${book.availableStock 0} a hrefborrowBook?bookId${book.id}借阅/a /c:if c:if test${book.availableStock 0} span classtext-muted已借完/span /c:if /td /tr /c:forEach注意 c:if 标签没有 else 分支需要反向条件用第二个 c:if 实现。这是 JSTL 和普通编程语言的差异点习惯了 else 的开发者容易踩这个坑。另外EL 表达式${book.name}实际上调用的是 Book 对象的 getName() 方法所以实体类的属性必须有规范 getter/setter。5. 前端交互与页面细节5.1 用 AJAX 实现借阅操作局部刷新图书列表页如果每次点击“借阅”都整页刷新体验非常生硬而且翻页后当前位置还会丢失。我在这个项目里给借阅操作加了 AJAX 异步请求返回结果是 JSON 格式前端根据返回的 code 判断成功失败弹出提示框后用 JS 局部更新那一行的库存数据。Servlet 返回 JSON 的简化写法response.setContentType(application/json;charsetutf-8); PrintWriter out response.getWriter(); out.print({\code\:0,\msg\:\借阅成功\});前端用原生 JS 的 fetch 就够了不需要引入 axiosfetch(borrowBook?bookId bookId) .then(resp resp.json()) .then(data { if (data.code 0) { alert(data.msg); location.reload(); } else { alert(data.msg); } });这里有个小坑Servlet 输出 JSON 必须设置 application/json 的 ContentType并指定 UTF-8 编码。如果漏了 charset浏览器解析中文返回信息会乱码。另外AJAX 请求同样会被 LoginFilter 拦截未登录时返回的是重定向的 HTML 页面而不是 JSON前端解析会报错。解决方法是前端先判断响应头或内容类型或者在后端 Filter 里针对 AJAX 请求返回 JSON 的未登录提示。5.2 Bootstrap 快速构建后台界面图书管理系统不需要你从零写 CSS。我推荐用 Bootstrap 4.x直接用它现成的表格、按钮、表单、导航栏组件半小时就能拼出一个干净的后台界面。虽然 Bootstrap 已经出到 5.x但 4.x 的中文教程最多遇到问题排查方便。界面布局上后台管理页面用“左侧导航栏 右侧内容区”的经典结构左侧放“图书管理、借阅管理、分类管理、用户管理”等入口右侧根据菜单动态展示对应内容。顶部导航栏放系统名称、当前登录用户、退出登录按钮。注意 JSP 页面里引入 Bootstrap 有两种方式本地下载到 static 目录或者用 CDN 链接。课程设计交作业场景建议本地引入因为评委老师现场演示时可能没网CDN 加载不出来整个页面就“裸奔”了影响观感。5.3 表单校验前端体验与后端安全的配合表单校验是前后端各做一层的前端校验可以极端一点字段必填、邮箱格式、手机号位数这些用 HTML5 的 required 和 pattern 属性就能实现错误信息即时反馈提升用户体验。但前端校验随时可以被绕过真正承重的校验必须在后端做。以新增图书为例后端 Service 层至少要校验书名不能为空、ISBN 不能重复、出版日期格式必须正确。在 Servlet 里把校验结果放进 request 域然后转发回新增页面回显错误信息if (bookName null || bookName.trim().isEmpty()) { request.setAttribute(error, 书名不能为空); request.getRequestDispatcher(/admin/bookAdd.jsp).forward(request, response); return; }前端校验更像是“防呆”后端校验才是“保安”。两者职责不同不能互相替代。6. 部署调试与常见问题排查6.1 用 vscode/IDEA 配置 Tomcat 的完整步骤IDEA 社区版不像旗舰版自带 Tomcat 集成但完全可以通过 Smart Tomcat 插件来运行项目。装好插件后配置一次把 Tomcat 安装路径指到你的 Tomcat 9 目录设置 Deployment 指向当前 Web 项目Context Path 填 /library就能在 IDEA 里一键启动 Tomcat 了。不用 Maven 的 Web 项目Community 版还需要把依赖 jar 放进 WEB-INF/lib这个和 Eclipse 里的做法一样。如果你坚持用 VSCode流程是安装 Tomcat for Java 插件 - 在 VSCode 侧边栏找到 Tomcat Servers 面板 - 添加本地 Tomcat 路径 - 右键项目选择 Run on Tomcat。但这套方案在项目结构不对时会频繁报 404 或类找不到排查起来比 IDEA 麻烦不少还是建议优先用 IDEA。启动成功后在浏览器访问http://localhost:8080/library/默认端口 8080 如果被占用去 Tomcat 的 conf/server.xml 里改 Connector 的 port 属性改成 8081 或 9090 都可以。6.2 高频问题排查速查表根据我多年的经验JavaWeb 项目跑不起来的坑90% 集中在那几个点上。我整理成速查表直接对着排查比自己瞎猜快得多问题现象可能原因解决方案启动 Tomcat 报端口被占用8080 被其他程序占用修改 server.xml 端口或杀掉占用进程JDBC 连接报 ClassNotFoundExceptionmysql 驱动 jar 没放到 WEB-INF/lib检查 jar 包是否编译进项目用 Maven 的检查 dependency中文乱码请求/响应编码不一致统一设置 request.setCharacterEncoding(utf-8) 和 response.setContentType(text/html;charsetutf-8)页面 404Servlet 没配置 WebServlet 或没配置 web.xml 映射检查注解路径和表单 action 路径是否一致列表页不显示数据数据库连接 URL 写错或表名大小写问题检查 URL 参数MySQL 在 Linux 下表名区分大小写数据库中文存进去乱码数据库连接 URL 没指定编码URL 加 useUnicodetruecharacterEncodingutf8修改代码后不生效JSP 文件被服务器缓存Clean Tomcat 缓存或重启服务6.3 我踩过印象最深的三个坑第一个坑是 MySQL 8.0 驱动类名变化。5.x 版本用 com.mysql.jdbc.Driver8.0 改成了 com.mysql.cj.jdbc.Driver如果沿用老驱动类名会直接 ClassNotFoundException。这个错误信息很直接但新手可能懵很久因为代码看着没问题。第二个坑是 filter 拦截了静态资源。我一开始写 LoginFilter 时没排除 css/js/image 路径结果登录页面加载出来纯文本样式全丢了排查半天发现是 Filter 的 url-pattern 写了 /*把静态资源的请求也拦截重定向了。Filter 里要对静态资源路径做白名单判断或者把静态资源放在单独路径下针对性排除。第三个坑是 JSP 修改不生效。Tomcat 默认对 JSP 有热部署机制但有时因为本地缓存或者编译目录残留改动就是不刷新。遇到这种情况把 Tomcat 的 work/Catalina 目录删掉重启一切就正常了。这个操作等同于“重启大法”但胜过花半小时怀疑人生。7. 项目扩展方向与个人实操感受图书管理系统完成基础功能之后我建议你别急着“毕业”顺着下面几个方向扩展一下项目含金量会明显提升借阅超期自动计算用 Quartz 定时任务每天刷新超期状态这是从“手动操作”到“系统自动化”的质变。借阅排行榜写一条带 GROUP BY 和 COUNT 的 SQL统计热门书籍 Top10这是 SQL 进阶训练。验证码登录用 Kaptcha 组件生成图片验证码理解 Session 在“防止机器操作”场景的另一个用途。读者借阅历史详情页把列表和详情联动起来练一下主从表查询和页面数据回显。接口化改造把 Servlet 改造成返回 JSON 的 REST 风格接口配合 Vue 前端重写一遍那基本就是企业级前后端分离的思路了。我在带新人做这类项目时的个人体会是图书管理系统最大的价值不在于它本身有多新潮而在于它把 JavaWeb 开发的核心知识编织成了一个完整的闭环。你写登录模块时会理解会话保持写借阅模块时会理解事务边界写列表页时会理解分页和条件查询这些东西在框架和微服务大行其道的今天依然是底层的地基。地基打得越扎实后面学 Spring Boot、MyBatis Plus 甚至微服务都只是在这个闭环上做工程化封装而已。最后分享一个小建议做这个项目时把每一段代码都当“作品”而不是“作业”来写每一步都问一句为什么这么写。做完之后把心得总结成博客发出来你会发现自己对这个项目的理解会再上一个台阶。本文还有配套的精品资源点击获取
返回列表