ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

JSP+Servlet+MySQL图书馆借阅系统源码解析与二次开发实战

JSP+Servlet+MySQL图书馆借阅系统源码解析与二次开发实战 简介这是一套面向计算机相关专业学生与Java Web初学者的图书馆图书借阅管理系统完整源码采用jspservletdaomysql技术栈实现可作为毕业设计、课程设计或作业参考。系统区分管理员、学生与游客三种角色管理员可登录、管理用户与图书、处理罚款缴纳学生可登录、借阅归还图书、查看借阅记录并修改密码游客可浏览图书列表与详情。压缩包共247个文件约6.7MB以45个java源文件、45个class编译文件、20个jsp页面、18个js脚本、13个css样式及10个jar依赖为主另含sql建库脚本、xml配置与图片资源结构完整便于二次开发。项目已在jdk1.8/1.9与Tomcat8.x/9.x下测试运行成功支持Eclipse、MyEclipse与IDEA导入附管理员与学生测试账号。目前已有144人学习适合需要完整赛题方案与排错思路的读者参考借鉴。1. 从一份 jspservletmysql 图书馆借阅系统源码说起它到底能帮你解决什么如果你手头正好有一份「jspservletmysql 图书馆图书借阅管理系统」的源码压缩包里面还带着文档说明和数据库脚本那它大概率不是拿来直接上线运营的而是拿来吃透 JavaWeb 全链路的一次完整演练。图书馆借阅这个业务场景足够经典读者、图书、借阅记录三张核心表就能撑起借书、还书、续借、查询、逾期提醒这一整套动作而 jspservletmysql 这套组合恰好把「页面渲染、请求分发、数据持久化」三层拆得清清楚楚。它适合两类人一类是刚学完 Servlet 想找个完整项目练手的在校生另一类是工作几年后想回头把 JavaWeb 底层补扎实的开发者。你真正要拿到的不是能跑的 war 包而是能读懂、能改、能自己重搭一遍的掌控感。这份源码的价值在于它把 MVC 的边界、JDBC 的写法、会话管理和分页查询这些散点知识塞进了一个有真实业务约束的壳子里。2. 拆开压缩包先看什么目录结构、数据库脚本与运行环境拿到源码别急着往 IDE 里拖先花十分钟把包里的东西分类。常见的目录结构是src放 Java 源码、WebContent或webapp放 jsp 和静态资源、sql或db放数据库脚本、根目录放一份README或文档说明。这一步的目的是判断这个项目是 Eclipse 动态 Web 项目还是 Maven 项目两者的导入方式完全不同搞错了后面全是玄学报错。2.1 目录结构与技术栈的对应关系一个典型的 jspservlet 项目src下通常按包名分层com.xxx.entity放实体类com.xxx.dao放数据库访问com.xxx.service放业务逻辑com.xxx.servlet或com.xxx.controller放 Servlet。WebContent/WEB-INF下的web.xml是部署描述符Servlet 3.0 之后很多映射改用注解WebServlet但老项目仍然靠web.xml配置。WebContent根目录下的 jsp 文件就是视图层css、js、images是静态资源。先把这个映射关系在脑子里建好后面读代码时才知道一个请求从哪进、从哪出。数据库脚本一般是一个.sql文件里面包含建库、建表、插入初始数据三部分。图书馆系统的核心表通常有book图书、reader读者、borrow借阅记录可能还有admin管理员和category分类。看脚本时重点确认三件事字符集是不是utf8mb4、引擎是不是InnoDB、外键和索引有没有建。字符集不对中文书名会变问号引擎不对事务和行锁用不了索引缺失借阅记录一多查询就慢。2.2 数据库导入与连接配置导入脚本用命令行最稳避免图形化工具的各种编码坑# 登录 MySQL注意 -u 和 -p 之间不要加空格 mysql -u root -p # 在 MySQL 交互界面里创建库并指定字符集 CREATE DATABASE library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后用 source 导入脚本路径用绝对路径更保险 exit; mysql -u root -p library_db /path/to/library.sql导入完成后进库核对表和数据USE library_db; SHOW TABLES; SELECT COUNT(*) FROM book; DESC borrow;SHOW TABLES确认表都建出来了SELECT COUNT(*)确认初始数据进去了DESC borrow看借阅表的字段类型尤其是借出日期和归还日期是DATE还是DATETIME这直接影响后面逾期天数的计算逻辑。连接配置一般在src下的db.properties或直接硬编码在DBUtil类里。常见配置项是驱动类com.mysql.cj.jdbc.DriverMySQL 8.x或com.mysql.jdbc.Driver5.xURL 里要带useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8。时区不设插入时间会差 8 小时编码不设中文乱码。这两个参数是血泪经验别省。2.3 运行环境与容器选择jspservlet 项目离不开 Servlet 容器Tomcat 是最常见的选择。版本对应关系要清楚Tomcat 8.5 对应 Servlet 3.1Tomcat 9 对应 Servlet 4.0Tomcat 10 之后包名从javax.servlet变成jakarta.servlet老项目直接扔进 Tomcat 10 会报ClassNotFoundException。所以先看源码里 import 的是javax还是jakarta再决定用哪个版本的 Tomcat。JDK 版本同理老项目用 JDK 8 最稳新项目可以上 JDK 11 或 17但要注意编译级别和依赖兼容。提示导入项目后如果报「The superclass javax.servlet.http.HttpServlet was not found on the Java Build Path」说明 Tomcat 库没加到构建路径在 IDE 里把 Server Runtime 加进去即可。3. 借阅业务的核心链路从 jsp 表单到 servlet 再到 mysql图书馆借阅系统的业务不复杂但链路完整。以「借书」这个动作为例用户在 jsp 页面输入读者编号和图书编号提交到 ServletServlet 调用 ServiceService 调 DAODAO 执行 SQL 更新借阅表和图书库存最后返回结果页面。把这一条链路走通整个系统的骨架就清楚了。3.1 借书流程的代码实现与参数说明先看 jsp 表单重点是表单的action和method!-- borrow.jsp 借书表单提交到 BorrowServlet -- form actionBorrowServlet?actionborrow methodpost 读者编号input typetext namereaderId required 图书编号input typetext namebookId required input typesubmit value确认借阅 /formaction里的?actionborrow是一种常见的分发写法一个 Servlet 处理多种操作靠action参数区分。methodpost是因为要写数据用 get 会把参数暴露在 URL 里。required是前端校验但不能替代后端校验。Servlet 端接收参数并调用业务// BorrowServlet.java 处理借书请求 WebServlet(/BorrowServlet) public class BorrowServlet extends HttpServlet { private BorrowService borrowService new BorrowService(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String action request.getParameter(action); if (borrow.equals(action)) { int readerId Integer.parseInt(request.getParameter(readerId)); int bookId Integer.parseInt(request.getParameter(bookId)); // 调用业务层返回结果消息 String msg borrowService.borrowBook(readerId, bookId); request.setAttribute(msg, msg); request.getRequestDispatcher(result.jsp).forward(request, response); } } }setCharacterEncoding(UTF-8)必须在取参数之前调用否则中文参数乱码。Integer.parseInt有风险如果前端传空或非数字会抛NumberFormatException生产代码要加 try-catch。forward是服务器内部跳转URL 不变适合携带 request 属性。Service 层做业务判断核心是「库存够不够、读者有没有超借、这本书有没有被借走」// BorrowService.java 借书业务逻辑 public String borrowBook(int readerId, int bookId) { // 先查图书库存 Book book bookDao.findById(bookId); if (book null) return 图书不存在; if (book.getStock() 0) return 库存不足借阅失败; // 查读者当前借阅数量 int borrowing borrowDao.countByReader(readerId); if (borrowing 5) return 已达最大借阅数量; // 开启事务扣库存 插借阅记录 try { bookDao.decreaseStock(bookId); borrowDao.insert(readerId, bookId, new Date()); return 借阅成功; } catch (Exception e) { return 系统异常借阅失败; } }这里的5是最大借阅数属于业务参数实际项目里应该配置化而不是硬编码。事务是关键扣库存和插记录必须同时成功或同时失败否则会出现库存扣了但没借阅记录的脏数据。用 JDBC 手动管理事务时记得setAutoCommit(false)、commit()、rollback()三件套。DAO 层的 SQL 用预编译防注入// BookDao.java 扣减库存 public void decreaseStock(int bookId) throws SQLException { String sql UPDATE book SET stock stock - 1 WHERE id ? AND stock 0; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, bookId); ps.executeUpdate(); } }WHERE id ? AND stock 0这个条件很重要它把库存判断和扣减放在一条原子 SQL 里避免并发下超卖。try-with-resources自动关闭连接比手动finally里 close 更简洁。3.2 还书与逾期计算的实现细节还书逻辑比借书多一步计算逾期天数并可能产生罚金。核心是日期差// BorrowService.java 还书并计算逾期 public String returnBook(int borrowId) { Borrow borrow borrowDao.findById(borrowId); if (borrow null) return 借阅记录不存在; if (borrow.getReturnDate() ! null) return 该书已归还; Date now new Date(); long days (now.getTime() - borrow.getBorrowDate().getTime()) / (1000 * 60 * 60 * 24); int overdue days 30 ? (int)(days - 30) : 0; borrowDao.updateReturn(borrowId, now, overdue); bookDao.increaseStock(borrow.getBookId()); return overdue 0 ? 归还成功逾期 overdue 天 : 归还成功; }1000 * 60 * 60 * 24是毫秒转天数的固定系数。30是借阅期限同样是业务参数。逾期天数用int存罚金规则如果复杂建议单独建表而不是塞在借阅表里。3.3 分页查询与 mysql limit 的配合图书列表和借阅记录列表都要分页mysql 用LIMIT offset, size-- 查询第 page 页、每页 size 条图书 SELECT * FROM book ORDER BY id LIMIT ?, ?;// BookDao.java 分页查询 public ListBook findByPage(int page, int size) { ListBook list new ArrayList(); String sql SELECT * FROM book ORDER BY id LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (page - 1) * size); ps.setInt(2, size); ResultSet rs ps.executeQuery(); while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setName(rs.getString(name)); b.setStock(rs.getInt(stock)); list.add(b); } } catch (SQLException e) { e.printStackTrace(); } return list; }(page - 1) * size是 offset 的标准算法page 从 1 开始。ORDER BY id保证分页结果稳定没有 order by 的分页在数据变动时会出现重复或遗漏。总页数需要单独一条SELECT COUNT(*)查询别用list.size()当总数。4. 避坑与排查jspservletmysql 项目最容易翻车的五个地方4.1 中文乱码现象是页面显示问号原因是编码链没统一现象jsp 页面输入中文书名存进数据库变成???或者从数据库读出来显示乱码。原因编码涉及 jsp 页面、request、response、数据库连接、数据库表五个环节任何一环不是 utf8 都会断链。解决jsp 顶部加% page contentTypetext/html;charsetUTF-8 %Servlet 里request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)JDBC URL 加characterEncodingutf8建库建表用utf8mb4。五个地方对齐乱码必消。4.2 数据库连接泄漏现象是跑一会儿就报 Too many connections现象系统用一段时间后报Too many connections重启 Tomcat 能好一阵。原因DAO 里Connection、PreparedStatement、ResultSet没关或者异常路径下没关。解决统一用 try-with-resources或者用连接池Druid、HikariCP替代裸 JDBC。裸 JDBC 每次DriverManager.getConnection都新建物理连接性能差且容易泄漏。连接池的maxActive和maxWait要按并发量调别用默认值。4.3 Servlet 映射冲突现象是 404 或 500原因是 url-pattern 写重了现象访问某个功能报 404或者启动时报Servlet mapping conflict。原因两个 Servlet 配了相同的url-pattern或者注解和 web.xml 同时配了同一个 Servlet。解决检查WebServlet的 value 和web.xml的url-pattern确保全局唯一。用?actionxxx分发时一个 Servlet 对应一个 patternaction 在代码里判断不要给每个 action 配一个 pattern。4.4 事务失效现象是库存扣了但借阅记录没插进去现象并发借书时出现库存为负或者库存扣了但借阅记录缺失。原因Service 层没开事务或者开了但异常被 catch 后没 rollback。解决在 Service 方法入口conn.setAutoCommit(false)正常结束commit()catch 块里rollback()。如果用 SpringTransactional要加在 public 方法上且同类内部调用不生效。图书馆系统并发不高但事务边界必须清楚。4.5 Tomcat 版本与 javax/jakarta 不匹配现象是启动就报 ClassNotFoundException现象项目在 Tomcat 9 能跑换 Tomcat 10 启动就报javax.servlet.http.HttpServlet找不到。原因Tomcat 10 把 Servlet API 的包名从javax.servlet迁到了jakarta.servlet老代码 import 的是 javax。解决要么降回 Tomcat 9要么把源码里所有javax.servlet改成jakarta.servlet并升级依赖。改包名不是全局替换就完事还要确认第三方库是否兼容 jakarta。最稳的做法是看源码 import选对应版本的容器。5. 把这套源码用出价值二次开发与验证的几个具体技巧拿到源码跑通只是起点真正让它产生价值的是二次开发。我一般会先做三件事来验证自己是否真的读懂了这套代码第一把硬编码的业务参数抽出来。比如最大借阅数5、借阅期限30天、罚金单价这些散落在 Service 里的魔法数字抽到一个config.properties里用Properties类加载。这一步能逼你把业务逻辑的边界摸清楚。第二给核心查询加索引并验证效果。借阅表上reader_id和book_id是高频查询字段建两个普通索引ALTER TABLE borrow ADD INDEX idx_reader (reader_id); ALTER TABLE borrow ADD INDEX idx_book (book_id);建完用EXPLAIN看执行计划EXPLAIN SELECT * FROM borrow WHERE reader_id 1001;看type列从ALL变成refrows列明显下降说明索引生效。数据量小的时候差别不明显可以自己造几万条测试数据再对比。第三把分页查询的 count 和 list 合并优化。常见写法是两条 SQL一条查总数一条查数据。数据量大时 count 也慢可以考虑缓存总数或者用SQL_CALC_FOUND_ROWS注意这个特性在 MySQL 8.0.17 后已废弃新项目别用。更现代的做法是前端滚动加载不查总数只查下一页有没有数据。还有一个容易被忽略的点会话管理。图书馆系统里读者登录后session里存readerId借书时从 session 取而不是从表单取防止用户篡改别人的编号。检查源码里是不是这么做的如果不是这是一个必须修的安全问题。同理管理员操作要单独校验权限不能只靠隐藏菜单。最后说一个验证方法把整个系统当成黑盒用 Postman 或 curl 直接打 Servlet 接口绕过 jsp 页面。这样能区分是前端问题还是后端问题。比如借书接口返回 500但 jsp 页面看不出错误直接打接口看堆栈就清楚了。我习惯在开发阶段把e.printStackTrace()保留上线前再换成日志框架。这套源码最大的价值不是它本身多完善而是它给了你一个可以随便改、随便拆的完整样本改坏了重导一次数据库就行这种低成本试错的机会比看十篇教程都管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表