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

JSP图书管理系统开发实战:从Model2架构到Servlet事务处理全解析

发布时间:2026/9/26 17:33:25

资讯中心
01
ARTICLE

JSP图书管理系统开发实战:从Model2架构到Servlet事务处理全解析

JSP图书管理系统开发实战:从Model2架构到Servlet事务处理全解析
简介基于JSP的图书管理系统毕业设计文档docx格式438KB适合高校计算机相关专业学生、毕业设计开发者以及需要了解B/S架构图书管理系统的技术人员。文档以系统设计与实现为主线完整呈现了从需求分析、系统架构到功能模块的实现思路覆盖管理员、教师、读者三类权限划分以及图书查询、借阅归还、库存管理等核心功能。基于JSP与MySQL的开发方案使读者能快速理解动态网页技术如何与数据库交互并掌握系统权限设计、业务逻辑处理等关键知识点。文档还提及图书分类检索、借阅状态查询、逾期提醒、预约等功能体现出系统设计的完整性与实用性。资源包内包含1个Word文档文件结构清晰按封面、摘要、目录、正文等毕业设计标准章节组织并详细说明开发背景、开发环境MyEclipse、JSP、MySQL、系统模块划分等内容可直接用于课题参考、文档结构借鉴或二次开发设计。已有284人学习浏览是一份值得参考的课程设计与毕业设计辅助资料。1. 基于jsp的图书管理系统为什么这个“老”技术路线还值得认真做一遍每年到毕业设计选题季总有人犹豫要不要碰 JSP 图书管理系统觉得 Spring Boot 才是主流JSP 看起来像上个时代的产物。我和这类项目打交道这些年一个真实感受是如果你做的是中小规模的业务系统、课程设计或毕设JSP Servlet JavaBean 的 Model2 架构不仅完全够用而且它把“请求怎么进来、页面怎么出去、数据怎么落库”这条链路拆得非常清晰学会它之后再切任何 MVC 框架都有降维打击的感觉。这个项目能解决的不只是图书借还那点功能更是一套 JavaWeb 基础能力的完整闭环技术选型、数据库设计、Session 控制、事务处理和部署排错。适合正在做 jsp 入门训练的人、选题还没定的 jsp 相关毕设人群以及要接手老系统维护的从业者。2. 先定架构再写代码Model2 的职责边界与数据库表设计2.1 JSP Servlet JavaBean 的 Model2 到底怎么划分职责Model2 是 JavaWeb 官方推荐的 MVC 落地形态核心规则一句话JSP 里不写业务逻辑Servlet 里不写 SQLJavaBean 负责数据和业务规则。具体到本项目JSP 页面只负责把数据渲染出来比如 book_list.jsp 只做循环输出Servlet 拿到请求后做参数校验然后调用 JavaBean也就是 Service DAO 那层拿到结果再决定转发到哪个 JSP 页面。这样拆的好处是页面改版不会影响业务逻辑换数据库驱动也不会动到页面代码。很多初学者写 JSP 项目的第一个误区是不自觉地回到 Model1 模式在 jsp 页面里直接写死 JDBC 代码页面里 % % 嵌套着数据库查询一个页面三四百行。这种写法的确跑得通但图书管理系统的功能一多比如加上读者管理、借阅历史、逾期罚款改一个页面很可能把另一个功能带崩。按 jsp modeled2 思想实现用户注册功能就是最典型的练习register.jsp 收集表单RegisterServlet 接收请求并校验UserDAO 负责查重和插入成功失败各跳一个结果页面。看似多写了好几个类但每个类都能单独测试出了问题也知道去哪个文件找。Model2 里还有一层经常被忽略Servlet 不应该直接操作 HttpSession 里的对象去做业务判断。比如判断当前用户能否借书应该是 BorrowService.checkCanBorrow(user, book) 返回一个结果对象Servlet 只负责把这个结果放进 request 或 session再转发页面。把业务规则写在 Servlet 里会导致两个 Servlet 都要借书时把规则抄两遍之后修改逾期限制条件就会漏改。2.2 图书管理系统的表结构设计四张表如何把业务闭环串起来图书管理系统的核心业务是“谁在什么时候借了哪本书什么时候还有没有超期”。按这个线索设计表最少需要四张核心表用户表、图书表、借阅表再加一个分类表用于图书分类统计。分类表不是必需的但加上它之后图书列表页做分类筛选和统计报表会省大量工作。用户表user包含 id、username、password、real_name、role、phone、create_time 这七个字段。role 区分管理员和普通读者管理员能进图书管理后台读者只能查书和借还。password 字段要注意不能明文存至少做一次加盐哈希。图书表book包含 id、isbn、book_name、author、publisher、category_id、location、total_count、avail_count。total_count 是馆藏总量avail_count 是当前可借数量每次借书减一、还书加一。为什么不通过统计 borrow 表去实时算可借数量因为图书列表页需要高频展示可借数如果每次都 COUNT 一次在数据量上来后查询会明显变慢冗余一个字段更直接。借阅表borrow是整张设计的中心字段包括 id、book_id、user_id、borrow_date、due_date、return_date、status、fine。borrow_date 是借出日期due_date 是应还日期return_date 是实际归还日期status 用 0/1/2 表示借出中、已按时还、逾期归还fine 是逾期产生的罚款金额。所有时间字段都用 date 类型不要用 varchar否则后面算逾期天数时你还得先做字符串解析。建表 SQL 示例CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, real_name VARCHAR(50) DEFAULT NULL, role TINYINT DEFAULT 1 COMMENT 0-管理员 1-读者, phone VARCHAR(20) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( id INT NOT NULL AUTO_INCREMENT, isbn VARCHAR(20) DEFAULT NULL, book_name VARCHAR(200) NOT NULL, author VARCHAR(100) DEFAULT NULL, publisher VARCHAR(100) DEFAULT NULL, category_id INT DEFAULT NULL, location VARCHAR(50) DEFAULT NULL COMMENT 馆藏位置, total_count INT DEFAULT 0, avail_count INT DEFAULT 0, PRIMARY KEY (id), KEY idx_book_name (book_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE borrow ( id INT NOT NULL AUTO_INCREMENT, book_id INT NOT NULL, user_id INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE DEFAULT NULL, status TINYINT DEFAULT 0 COMMENT 0-借出 1-已还 2-逾期归还, fine DECIMAL(6,2) DEFAULT 0.00, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_book_id (book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关于外键设计上 user_id 和 book_id 是逻辑关联但我建议不要建物理外键约束。物理外键在删除图书或用户时会引来一堆约束冲突而且 Tomcat 多线程环境下外键检查会带来额外开销。业务层保证一致性就够了这个取舍是很多生产项目验证过的习惯。2.3 项目目录结构与请求路径约定先想好再动手用 IDEA 新建 jsp 项目时不要选普通的 Java 模块然后手动加 web 目录直接选 Jakarta EE 或 Java Enterprise 分类下的 Web Application 模板IDEA 会自动生成 webapp 目录和 WEB-INF/web.xml。传统 jsp 项目打包 war 之后最终目录结构大概长这样src/main/java/com/library/ ├── controller/ # Servlet ├── service/ # 业务逻辑 ├── dao/ # 数据库访问 ├── model/ # 实体类 └── util/ # 工具类DBUtil、MD5Util src/main/webapp/ ├── WEB-INF/ │ ├── web.xml │ └── jsp/ # 页面前端控制器统一从 servlet 转发进来 ├── static/ # css、js、images └── index.jspJSP 页面尽量放在 WEB-INF 目录下因为外部浏览器直接访问 WEB-INF 下的文件会被 Tomcat 拒绝这样能强制页面必须经过 Servlet 转发才能打开避免用户绕过登录拦截器直接访问某个页面。访问路径上建议按资源 动作命名/login、/book/list、/book/add、/book/update、/borrow/add、/borrow/return。每个 Servlet 用 doGet 和 doPost 区分页面跳转和数据提交不要搞 /bookAdd 和 /bookAddSubmit 这种双 Servlet 方案。这里还要做一件事把所有 Servlet 的映射路径统一以后通过注解 WebServlet 配置不要再在 web.xml 里写一长串 servlet-mapping。注解方式少写很多配置IDEA 编译期也能校验映射是否有冲突。项目小的时候可能感觉不到区别等图书、用户、借阅、统计这些模块加起来超过十个 Servlet 时web.xml 里找映射就是灾难了。3. 从登录到借书核心模块的实现与参数调优3.1 登录、注册与 Session 控制每个请求都要过的关卡登录模块是整个系统的第一道门它的实现质量决定后面所有功能的体验。按 Model2 思路登录流程拆成三层login.jsp 表单收集用户名密码LoginServlet 接收并校验UserDAO 查库比对密码成功就创建 Session 并转发到首页失败就回登录页带错误信息。密码的比对不要直接查数据库里的哈希值做相等判断而是把用户输入的密码加同样的盐、做同样的哈希再去和库里存的哈希比对。这样即使数据库泄露原始密码也不会直接暴露。LoginServlet 的代码核心框架如下WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); User user new UserService().login(username, password); if (user null) { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/WEB-INF/jsp/login.jsp) .forward(request, response); return; } HttpSession session request.getSession(); session.setAttribute(user, user); session.setMaxInactiveInterval(60 * 30); // 30分钟超时 response.sendRedirect(request.getContextPath() /index); } }逻辑说明request.setCharacterEncoding(UTF-8) 必须在 getParameter 之前调用否则中文用户名会乱码。用户校验失败时用 forward 返回登录页这样地址栏还是 /login刷新不会重复提交表单登录成功用 sendRedirect 重定向到首页顺便让浏览器重新发起一次 GET 请求。session.setMaxInactiveInterval(30分钟) 是安全上的必要设置图书管理系统里如果用户借完书不退出浏览器一直开着Session 就一直有效存在被他人冒用的风险。用户个人信息展示页面也是这套逻辑的简单延伸登录后从 session 里取出 user 对象在 user_info.jsp 显示用户名、真实姓名、当前借阅数量。要注意 session 里存的是 JavaBean 对象不是散装的用户名和密码字符串否则 JSP 里要多个 session 属性拼一个完整用户管理起来很不优雅。注册功能按 jsp modeled2 思想拆分RegisterServlet 接收表单先调用 UserService.checkUsername(username) 判断是否已存在再调用 UserDAO.insert(user) 落库。事务上有一个小坑用户名查重和插入是两步并发下可能两个请求同时通过查重然后都插入成功所以 user 表的 username 字段必须建 UNIQUE 约束数据库兜底应用层只是提示友好。3.2 图书的增删改查DAO 层的写法决定你后期维护的心情图书管理是系统的核心模块也是八成以上的代码量所在。先写一个 DBUtil 封装数据库连接的获取与关闭每个 DAO 方法都通过它拿连接、执行 SQL、返回结果。这里要严格使用 PreparedStatement不管 SQL 里有没有用户输入统一用它一是避免 SQL 注入二是预编译语句在 MySQL 端有执行计划缓存反复执行时比 Statement 快。DBUtil 的关键代码如下public class DBUtil { private static String url; private static String user; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(jdbc.properties)) { Properties props new Properties(); props.load(in); Class.forName(props.getProperty(driver)); url props.getProperty(url); user props.getProperty(username); password props.getProperty(password); } catch (Exception e) { throw new ExceptionInInitializerError(数据库配置加载失败); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }逻辑说明静态代码块在类加载时执行一次读取 jdbc.properties 并注册 JDBC 驱动。把连接参数放配置文件而不是写死在代码里是维护阶段最重要的决定后面部署到别的机器时不用重新编译 war 包只需要改配置文件。这里没有引入连接池毕设和课设访问量不大DriverManager 每次新建连接可以接受如果你要放到有持续压力的环境换成 Druid 连接池也就多改四五行代码。图书新增和查询的 DAO 写法有一个容易翻车的地方增删改用 executeUpdate查询用 executeQuery两者不能混。很多新手在写更新操作时误用 executeQuery结果拿不到结果集程序直接抛异常。还有一个高频问题是资源释放Connection、PreparedStatement、ResultSet 三个都要关按 ResultSet→PreparedStatement→Connection 的顺序反向关闭。Java 7 之后的 try-with-resources 写法可以直接解决问题public ListBook searchBooks(String keyword, int offset, int limit) throws SQLException { String sql SELECT * FROM book WHERE book_name LIKE ? LIMIT ?, ?; ListBook books new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, offset); ps.setInt(3, limit); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setBookName(rs.getString(book_name)); book.setAuthor(rs.getString(author)); book.setAvailCount(rs.getInt(avail_count)); books.add(book); } } } return books; }逻辑说明LIKE 模糊搜索用通配符拼在参数值里而不是拼在 SQL 语句模板里这样预处理语句仍然有效。LIMIT ?, ? 两个参数offset 是起始行limit 是每页条数这就是给列表页加分页功能要用的。参数的设置顺序必须和 SQL 中 ? 出现顺序完全一致一个常见的低级错误是把 offset 和 limit 的顺序填反导致第二页开始数据错乱。用 try-with-resources 之后连接和语句块结束后自动关闭不用再写 finally 里一长串判断非空关闭的模板代码。图书删除有一点要提醒有借阅记录的图书不能物理删除否则 borrow 表里的历史记录会变成孤儿数据查借阅历史时 join 图书表就查不出书名。常见做法是加一个 status 字段0 正常 1 下架列表默认只查 status0。这个设计一开始可能觉得多余做到借阅统计那一步你会感谢当初留了这个字段。3.3 借书与还书事务一致性在这里体现借书不是往 borrow 表里 insert 一条记录就完事它同时涉及两个操作插入借阅记录、减少图书的可借库存。这两个操作必须在一个数据库事务里要么都成功要么都失败。如果在插入借阅记录后、更新库存前程序抛了异常就会出现借阅记录存在但库存没减的脏数据。BorrowService 里用如下方式处理public boolean borrowBook(int userId, int bookId) { String sqlBorrow INSERT INTO borrow (book_id, user_id, borrow_date, due_date, status) VALUES (?, ?, CURRENT_DATE, DATE_ADD(CURRENT_DATE, INTERVAL 30 DAY), 0); String sqlReduce UPDATE book SET avail_count avail_count - 1 WHERE id ? AND avail_count 0; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); try (PreparedStatement ps1 conn.prepareStatement(sqlBorrow)) { ps1.setInt(1, bookId); ps1.setInt(2, userId); ps1.executeUpdate(); } try (PreparedStatement ps2 conn.prepareStatement(sqlReduce)) { ps2.setInt(1, bookId); int rows ps2.executeUpdate(); if (rows 0) { conn.rollback(); return false; } } conn.commit(); return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { log.error(回滚失败, ex); } } log.error(借书失败, e); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { log.error(关闭连接失败, e); } } } }逻辑说明setAutoCommit(false) 关闭自动提交后后续所有 SQL 都在同一个事务里只有执行到 commit 才真正生效。更新库存那条 SQL 里用 avail_count 0 作为条件数据库在更新行数上做了并发控制两个用户同时借最后一本书时只有一个 update 能返回行数 1另一个返回 0 触发回滚这比先 SELECT 查库存再决定借不借可靠得多。due_date 直接由数据库计算为当前日期加 30 天避免应用层和数据库时间不一致。还书流程和借书对称更新 borrow 表的 return_date 为当前日期根据是否超过 due_date 计算罚款再把 book 表的 avail_count 加回来。罚款金额按天计算每本书每天 0.5 元在 SQL 里用 DATEDIFF 完成天数计算而不是在 Java 里用两个 Date 对象手动相除再取整后者遇到闰年时间差会有玄学错误。还书操作同样要包事务更新归还状态和加回库存必须保持一致。4. 把项目跑起来Tomcat 部署、war 打包与路径规划4.1 IDEA 中配置 Tomcat 与两种部署方式直接部署和 war 部署开发阶段IDEA 里最省事的方式是配置本地 Tomcat 直接部署。进入 Run/Debug Configurations新增 Tomcat Server Local 配置在 Deployment 标签页把项目以 artifact 方式添加进去Application context 填 /library然后就能直接点运行。这种方式开发调试方便改完页面代码 JSP 文件会自动重新加载适合日常写代码的阶段。但交付和部署阶段传统 jsp 项目打包 war 才是正确姿势。IDEA 里的操作路径是 File → Project Structure → Artifacts选择 Web Application: ArchiveIDEA 会自动分析项目依赖把当前项目打成 war 包。构建完成后从 Build → Build Artifacts 菜单执行构建war 文件就输出到 out/artifacts 目录下。这一步是在给系统一个完整的交付形态war 包可以在任何装了 Java 和 Tomcat 的机器上部署不再依赖 IDE。war 包的命名直接影响访问路径。如果 war 包叫 library.war部署到 Tomcat 的 webapps 目录后访问地址就是 http://localhost:8080/library/。如果你希望浏览器直接通过 http://localhost:8080 访问系统把 war 包改名为 ROOT.war 再拷贝到 webapps 目录或者提前在 Tomcat 的 conf/server.xml 里添加一个 Context 配置指定路径。开发时用 /library 路径前后端代码里所有跳转都要带这个上下文路径这就显露出一个经典的坑代码里写了跳转 /book/list实际部署到某个环境后路径变成了 /library/book/list于是 404。解决方案是所有页面跳转和 Servlet 重定向一律用 request.getContextPath() 拼接路径例如 response.sendRedirect(request.getContextPath() /index)页面里的表单 action 同样用 ${pageContext.request.contextPath} 开头。这样无论部署路径叫什么代码都能自适应。4.2 传统 JSP 项目的连接参数外置改配置不再重新打包传统 JSP 项目打包 war 后lib 目录和 classes 目录都封在里面如果 JDBC 连接参数写在 DBUtil 的 Java 代码里部署后要换数据库服务器就得重新打包、重新部署这在自己电脑上无感但到了正式环境就是一次不小的运维操作。解决办法是让配置文件随依赖进入 war 包但保持可外部覆盖。具体做法src/main/resources 目录下放 jdbc.properties这个目录下的文件在 war 打包后会出现在 WEB-INF/classes 里。部署时不要直接编辑 war 包里的文件而是把配置文件复制到 Tomcat 的 lib 目录同级或 classpath 可覆盖的位置或者在启动参数里指定外部配置路径。更简单的做法把 jdbc.properties 放在 Tomcat/conf 目录下然后在启动参数或环境变量里把该目录加入 classpath。这样修改密码和地址只需要改外部文件后重启 Tomcat不需要动 war 包。jdbc.properties 的内容一般包含五项drivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20参数说明url 里的 characterEncodingutf8mb4 是中文乱码问题的关键MySQL 8 以上还要带 serverTimezone 参数否则 JDBC 驱动会因为本地时区和 MySQL 时区不一致报 timezone 相关的异常。useSSLfalse 是因为本地内网环境不需要 SSL 握手加了 SSL 反而每次连接都有握手开销。username 和 password 不要用 root 账号做应用连接创建一个专门账号并只授权 library 库这是生产环境的基本操作但教学项目里十有八九都忽略。4.3 Nginx 能不能解析 JSP动静分离的职责划分这个问题在部署阶段几乎一定会碰到有人把 JSP 项目放到 Nginx 的 html 目录下然后访问时发现页面变成了一堆 JSP 源码。原因很简单Nginx 本来就是静态 Web 服务器它不认识 JSP 这种动态页面标记语言更不能执行 Java 代码。JSP 是给 Tomcat 解析的Tomcat 通过 Jasper 引擎把 JSP 编译成 Servlet再执行后输出 HTML。所以 Nginx 在 JSP 项目里的正确定位是反向代理和静态资源服务器而不是执行引擎。常见的部署结构是Nginx 监听 80 端口对外分发请求.jsp 结尾的请求通过 proxy_pass 转发给本机 8080 端口的 Tomcatcss、js、png 等静态资源直接在 Nginx 本地磁盘上找不经过 Tomcat。这个配置写法如下location /library/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~* \.(css|js|png|jpg|gif|ico)$ { root /opt/library/static; expires 7d; }参数说明proxy_set_header 的三个参数决定 Tomcat 日志里记录的客户端 IP 是否正确不设置 X-Forwarded-For 的话所有请求在 Tomcat 看来都来自 Nginx 的 IP做 IP 限制或访问日志分析时数据就废了。静态资源的 expires 7d 是告诉浏览器可以缓存 7 天图片和样式文件不用每次请求都回源。要注意一个细节location /library/ 里的 proxy_pass 如果带 URI 部分比如 proxy_pass http://127.0.0.1:8080/Nginx 转发时会用替换后的路径转发容易把路径搞乱保持这种不带末尾斜杠的写法最稳妥。如果你是在 Windows 上本地联调也可以直接用 Tomcat 的 8080 端口访问完全不引入 Nginx。但了解这套动静分离结构仍然有价值因为正式部署时几乎绕不开它。5. 避坑与排查JSP 图书管理系统最常踩的五个翻车现场5.1 页面中文全部变成问号三处编码不一致现象登录页和图书列表页的所有中文显示为问号数据库里存进去的中文也是问号。原因JSP 文件保存编码、Tomcat 解析 JSP 的编码、MySQL 表字符集这三者至少有一处不是 UTF-8。解决先把所有 JSP 文件顶部加上 page 指令指定 contentTypetext/html; charsetUTF-8 和 pageEncodingUTF-8再把 web.xml 里配置一个 CharacterEncodingFilter 过滤器强制把所有请求和响应都设为 UTF-8最后检查数据库建表用的是 utf8mb4 而不是 latin1。这三处都改了以后重启 Tomcat 再看绝大多数乱码问题都能消失。如果还有乱码检查一下 Tomcat 的 server.xml 里 Connector 是否配置了 URIEncodingUTF-8这个配置负责处理 URL 地址栏参数里的中文。5.2 页面 404 且路径里有两种可能性现象明明资源存在访问 /book/list 却报 404地址栏手动加项目名 /library/book/list 又能访问。原因代码里所有跳转路径没有拼接上下文路径IDEA 配置的 Application context 和 war 包实际部署路径不一致。解决全项目搜索所有 sendRedirect、forward、表单 action、a 标签 href强制统一用 request.getContextPath() 或 JSP 的 ${pageContext.request.contextPath} 前缀。这里没有捷径只能逐处排查因为我见过有人只改了 Servlet 里的重定向忘了改 JSP 页面里的 50 个 a 标签结果首页能进点任何功能按钮全部 404。5.3 页面报错 Connection is not available现象系统运行一段时间后操作图书列表或登录时突然报错检查 Tomcat 日志看到 Connection is not available, request timed out。原因数据库连接没有正确释放。DBUtil 里的 getConnection 每次从 DriverManager 拿新连接用完没关闭MySQL 默认的 max_connections 被耗尽后续请求全部排队等待。解决检查所有 DAO 方法确保 Connection、PreparedStatement、ResultSet 全部在 finally 块中关闭直接用 try-with-resources 重构。如果是连接池环境确认每个 borrow 方法里的事务结束后连接确实归还。这种问题在开发机上不明显因为开发机通常只有你一个人在用一旦多几个人同时点页面就开始报错。5.4 日期计算差一天或完全没有逾期判断现象借书 30 天后还书系统不判定为逾期或者还书时间显示比实际时间差一天。原因借书时把 java.util.Date 直接通过 setDate 存进数据库而 JDBC 的 setDate 只接受 java.sql.Date两者转换时丢失了时分秒信息。更隐蔽的问题是计算逾期天数时用了 System.currentTimeMillis() 求差再除以 86400000遇到夏令时或时区偏移会得到错误的天数。解决在 Java 代码里统一使用 LocalDate 处理日期存库时用 java.sql.Date.valueOf(localDate)查出来再用 rs.getDate(due_date).toLocalDate() 还原。判断逾期用 LocalDate.now().isAfter(dueDate) 而不是把日期转成毫秒做数学计算。5.5 Spring Boot 思维套在传统 JSP 项目上目录结构对不上现象有人做过 Spring Boot 项目后回头写传统 JSP发现配置文件写好但 Tomcat 启动后找不到页面JSP 也没有被编译。原因传统 JSP 项目的 JSP 文件必须放在 webapp 目录下由 Tomcat 直接管理Spring Boot 里常见的 src/main/resources/templates 目录下放 JSP 是跑不起来的。解决回到传统 JSP 的目录约定webapp/WEB-INF/jsp 放页面web.xml 或注解方式注册 Servlet。如果确实想在 Spring Boot 里集成 JSP需要在 application.properties 里配置视图解析器前缀前缀为 /WEB-INF/jsp/ 且必须打成 war 包而不是内嵌 Tomcat 的 jar 方式运行这个限制是 Spring Boot 官方文档里都写明的不要硬踩。6. 让系统从“能跑”到“能用”分页、防注入与一小时验证清单图书列表页最终一定要加分页因为一本本把结果全部渲染到页面上数据到 200 条以后页面就开始明显卡顿。分页参数上pageSize 设 10 或 20 都行关键是查询接口里同时返回总条数前端才能算出总页数。我的习惯是每次分页请求把当前页数、每页条数和搜索关键词都放在 URL 参数里这样刷新页面后还能保持原来的查询条件这个体验细节很影响使用时的心情。安全方面所有的 SQL 操作必须走 PreparedStatement这不是可选项。有一个练习项目里见过用字符串拼接 SQL 做搜索的在书名框里输入一个单引号整个 SQL 语句就炸了这就是典型的 SQL 注入的引子。PreparedStatement 的预编译机制天然规避了这个问题但前提是你不要把参数拼进 SQL 模板里。最后是一份一小时验证清单新部署完系统后逐项执行一遍注册一个新账号、用新账号登录、查看自己的 jsp 个人信息展示页面、修改密码后重新登录、管理员新增图书、修改图书信息、下架一本图书、读者借一本书、库存对应减一、归还这本书、制造一本超期图书确认罚款计算正确。这套流程走完系统的核心链路就算验证通过。做这个项目时我印象最深的一个教训是借书事务里忘了处理库存不足的回滚当时觉得查一下库存再插入借阅记录就够了结果并发测试时两台机器同时借同一本书库存变成了负数。后来才明白数据库事务里的行级条件更新才是正解这个认知后来帮我避免了不少线上问题。希望这些经验能帮你在做图书管理系统时少走一些弯路也祝你顺利跑通这个经典的 JavaWeb 毕业设计项目。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

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

03
WHY YAOTU

想打造同款高转化官网?

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

◈

场景化定制

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

◐

营销型架构

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

▲

全周期服务

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

免费获取你的建站方案

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