JSP+JDBC+MySQL+Servlet图书管理项目源码实战与避坑指南
发布时间:2026/9/28 22:56:55 锦皓数字建站

简介这是一套基于Java技术栈的图书管理系统完整源码采用JSP、Servlet、JDBC与MySQL组合开发适合Java Web初学者与课程设计、毕业设计参考者用于理解MVC模式在真实项目中的落地方式。压缩包共95个文件约1.7MB以37张jpg与4张png图片、17个java源文件、13个html页面、11个css样式、5个xml配置、3个js脚本及1个sql建表脚本为主涵盖视图页面、业务逻辑、数据库脚本与静态资源。项目按web-INF、jsp、servlet、lib、css、js等模块组织清晰呈现登录注册、图书列表、增删改查等典型功能的实现路径。已有275人学习下载。读者可从中获得可直接运行的工程结构、JDBC连接MySQL的完整示例、Servlet处理请求与JSP展示数据的配合方式以及SQL建表与数据操作脚本便于对照调试、二次修改与知识梳理。1. 从一份 webDemo-master 源码说起JSPJDBCMySQLServlet 图书管理项目能跑起来吗很多人拿到jspjdbcmysqlservlet图书管理项目源码.zip的第一反应是解压、丢进 IDE、点运行然后被 404、500、驱动加载失败轮番教育。这份源码包的结构其实很典型webDemo-master目录下是pom.xml、src、test、main、webDemo.iml说明它原本是 IntelliJ IDEA 工程用 Maven 管理依赖核心代码放在src/main里JSP 页面、Servlet 类、JDBC 工具类、MySQL 建表脚本大概率分散在webapp、java、resources几个子目录中。它解决的不是“高并发图书系统”这种问题而是让你在一个能跑通的闭环里看清 JSP 负责视图、Servlet 负责控制、JDBC 负责数据访问、MySQL 负责持久化这条链路到底怎么接。适合刚学完 Java Web 语法、想找一个完整案例把jdbc增删改查串起来的人也适合需要基于 JSP 做毕设选题、想拿现成骨架改业务的学生。但“能跑起来”和“能改明白”是两回事下面按实际落地顺序拆。2. 环境与工程结构把 webDemo-master 导入 IDEA 并补齐 MySQL 依赖2.1 先看清 pom.xml 和目录布局再动手拿到源码别急着点运行先确认三件事JDK 版本、Maven 依赖、Web 目录结构。webDemo.iml是 IDEA 的模块文件说明作者用的是 IDEApom.xml决定 Servlet API、JSP API、MySQL 驱动、JSTL 这些 jar 从哪来。常见做法是打开pom.xml看dependencies里有没有mysql-connector-java或mysql-connector-j有没有javax.servlet-api且scope是provided。如果缺 MySQL 驱动启动后第一次访问数据库就会抛ClassNotFoundException: com.mysql.cj.jdbc.Driver这是最典型的翻车点。目录上src/main/java放 Servlet 和 JDBC 工具类src/main/webapp放 JSP、WEB-INF/web.xml、css、jssrc/main/resources放数据库配置或 SQL 脚本。如果解压后发现没有webapp而是WebContent那是 Eclipse 风格导入 IDEA 时要手动指定 Web 资源目录否则 JSP 全部 404。2.2 用 Maven 补齐依赖并锁定版本在pom.xml里确认或补上下面这几段。版本号按你本地 MySQL 选MySQL 8 用8.0.x驱动MySQL 5.7 可以用5.1.x但驱动类名不同后面会讲。dependencies !-- Servlet API由 Tomcat 提供scope 必须是 provided -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API同样由容器提供 -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- MySQL 8 驱动注意 groupId 和 artifactId 与旧版不同 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency !-- JSTLJSP 里用 c:forEach 遍历图书列表时需要 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies逻辑说明Servlet 和 JSP 的 API 包必须用provided因为 Tomcat 运行时自带如果打成compile塞进WEB-INF/lib会和容器版本冲突表现为NoSuchMethodError或页面直接 500。MySQL 驱动用compile它必须进WEB-INF/lib才能被 JDBC 的DriverManager加载。JSTL 是否必需取决于 JSP 里有没有用标签库如果页面顶部有% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %缺了它就会报Unable to find taglib。参数上mysql-connector-j8.x 的驱动类是com.mysql.cj.jdbc.DriverURL 要带时区和 SSL 参数否则控制台会刷The server time zone value is unrecognized或 SSL 警告。5.1.x 驱动类是com.mysql.jdbc.DriverURL 可以简单写jdbc:mysql://localhost:3306/库名。这两个版本不能混混了就是this version of the jdbc driver is only compatible with...这类报错。2.3 配置 Tomcat 并确认上下文路径IDEA 里Run → Edit Configurations → → Tomcat Server → Local在Deployment标签页添加war exploded工件。Application context建议设成/webDemo或/设成/时访问http://localhost:8080/直接进首页设成/webDemo就要带路径。JSP 里如果写了绝对路径/book/list上下文路径变了就会 404所以更稳的做法是 JSP 里用${pageContext.request.contextPath}拼路径。启动前确认web.xml的welcome-file-list指向哪个页面常见是login.jsp或index.jsp。如果web.xml里 Servlet 映射用的是WebServlet注解而不是 XML 配置要确认web.xml的metadata-complete不是true否则注解不生效所有 Servlet 都 404。3. 数据库落地建库建表、JDBC 连接与增删改查的完整链路3.1 建库建表和连接参数先在 MySQL 里建库建表。源码包里通常带一个.sql文件在src/main/resources或项目根目录用 Navicat 或命令行导入都行。如果没有按图书管理的典型字段建CREATE DATABASE IF NOT EXISTS bookdb DEFAULT CHARACTER SET utf8mb4; USE bookdb; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), price DECIMAL(10,2), stock INT DEFAULT 0 ); CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL );字符集用utf8mb4而不是utf8否则书名里的生僻字或 emoji 会插入失败。price用DECIMAL不用FLOAT金额计算不会出现0.30000000000000004这种玄学。JDBC 连接参数写在工具类里常见做法是抽一个DBUtilpublic class DBUtil { // MySQL 8 驱动类5.x 要改成 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // useSSLfalse 关掉 SSL 警告serverTimezone 必须指定否则时区报错 private static final String URL jdbc:mysql://localhost:3306/bookdb?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); // 加载驱动只执行一次 } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动未找到, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 关闭顺序ResultSet → Statement → Connection任一为 null 要跳过 try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }逻辑说明Class.forName在静态块里执行一次即可JDBC 4.0 以后其实可以省略但显式写更稳。serverTimezone不写时MySQL 8 驱动会抛The server time zone value ?D1ú±ê×?ê±?? is unrecognized这是血泪经验里出现频率最高的报错之一。useSSLfalse只是关掉连接加密警告本地开发够用生产环境要按实际配。3.2 用 PreparedStatement 做增删改查图书列表查询、添加、删除、修改四个操作统一用PreparedStatement不要用字符串拼接Statement否则书名里带单引号就 SQL 报错还留注入风险。// 查询全部图书 public ListBook findAll() { ListBook list new ArrayList(); String sql SELECT id, name, author, publisher, price, stock FROM book ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setName(rs.getString(name)); b.setAuthor(rs.getString(author)); b.setPrice(rs.getBigDecimal(price)); b.setStock(rs.getInt(stock)); list.add(b); } } catch (SQLException e) { e.printStackTrace(); } return list; } // 添加图书? 是占位符按顺序 setXxx public int insert(Book b) { String sql INSERT INTO book(name, author, publisher, price, stock) VALUES(?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, b.getName()); ps.setString(2, b.getAuthor()); ps.setString(3, b.getPublisher()); ps.setBigDecimal(4, b.getPrice()); ps.setInt(5, b.getStock()); return ps.executeUpdate(); // 返回受影响行数 } catch (SQLException e) { e.printStackTrace(); return 0; } }逻辑说明try-with-resources会自动关闭Connection、PreparedStatement、ResultSet比手动finally里关更省心也不会漏。executeQuery返回ResultSet用于查询executeUpdate返回 int 用于增删改。占位符下标从 1 开始不是 0这是新手最容易数错的地方。getBigDecimal对应DECIMAL字段用getDouble也能取但会丢精度。3.3 Servlet 接收请求并转发 JSPServlet 负责从请求里取参数、调 JDBC、把结果塞进request再转发给 JSP。以图书列表为例WebServlet(/book/list) public class BookListServlet extends HttpServlet { private BookDao bookDao new BookDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListBook books bookDao.findAll(); req.setAttribute(books, books); // 存进 request 域 req.getRequestDispatcher(/book/list.jsp).forward(req, resp); // 转发不改变 URL } }逻辑说明WebServlet(/book/list)把 URL 映射到这个类访问/book/list就进doGet。setAttribute把数据放进 request 域JSP 里用${books}或c:forEach items${books}取。forward是服务器内部跳转浏览器地址栏不变如果用sendRedirectrequest 域数据会丢列表页就空了。JSP 里遍历时记得先引入 JSTL 标签库否则c:forEach不解析直接原样输出到页面。4. 避坑与排查JSPJDBCMySQL 项目最容易翻车的五个点4.1 现象启动报 ClassNotFoundException: com.mysql.cj.jdbc.Driver原因MySQL 驱动 jar 没进WEB-INF/lib或者pom.xml里依赖 scope 写成了provided打包时被排除。解决确认pom.xml里mysql-connector-j没有provided在 IDEA 的Project Structure → Artifacts里看WEB-INF/lib下有没有驱动 jar没有就重新 build 工件。如果用的是 Tomcat 的lib目录放驱动要确认 Tomcat 重启过。4.2 现象页面 500报 The server time zone value is unrecognized原因MySQL 8 驱动要求 URL 里显式指定serverTimezone不写就按系统时区猜中文 Windows 上猜不出来。解决URL 加serverTimezoneAsia/Shanghai或者serverTimezoneUTC。改完重启 Tomcat不用改数据库。4.3 现象JSP 页面中文乱码书名显示问号原因三个环节都可能出问题——数据库字符集、连接 URL 字符集、JSP 页面编码。解决数据库建库时用utf8mb4URL 加characterEncodingutf8JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里response.setContentType(text/html;charsetUTF-8)。四处都对齐才不乱码只改一处往往没用。4.4 现象Servlet 全部 404但 JSP 能访问原因web.xml里metadata-completetrue导致WebServlet注解不生效或者 Servlet 类没放在src/main/java下而被当成普通文件。解决把web.xml的metadata-complete改成false或删掉该属性确认 Servlet 类在src/main/java的包路径下且pom.xml里packaging是war不是jar。4.5 现象修改 JSP 后刷新页面没变化原因Tomcat 缓存了编译后的 JSP Servlet或者 IDEA 没有把更新同步到war exploded目录。解决IDEA 里Build → Rebuild ProjectTomcat 配置里On Update action选Update classes and resourcesOn frame deactivation也选Update classes and resources。改 Java 代码必须重启 Tomcat改 JSP 可以只更新资源。5. 从能跑到能改把图书管理骨架改成自己的业务模块5.1 先验证再改造别一上来就动数据库拿到这份源码第一件事不是改功能而是按原样跑通一遍导入 IDEA、补依赖、建库导数据、配 Tomcat、访问登录页、走一遍图书列表和添加。跑通之后再动代码否则出了问题分不清是环境问题还是你改出来的问题。我一般会先跑通登录和列表两个页面确认 JDBC 连接、Servlet 映射、JSP 渲染三条链路都正常再开始改。验证方法很直接在DBUtil.getConnection()里打一行System.out.println(连接成功)启动后看控制台有没有输出在BookListServlet里打System.out.println(books.size())看查出来几条。控制台有输出但页面空问题在 JSP 或转发控制台没输出问题在 Servlet 映射或数据库连接。5.2 改造成借阅管理模块的字段映射图书管理改成借阅管理核心是加一张借阅表和对应的 Servlet、JSP。借阅表字段建议字段类型说明idINT AUTO_INCREMENT主键book_idINT外键关联 book.iduser_idINT外键关联 user.idborrow_dateDATETIME借出时间return_dateDATETIME归还时间未还则为 NULLstatusTINYINT0 借出1 已还对应的 DAO 方法照抄BookDao的结构把 SQL 换成借阅表的增删改查。Servlet 里多一步借书时先查stock是否大于 0大于 0 才插入借阅记录并UPDATE book SET stock stock - 1两个操作放在同一个Connection里手动控制事务避免借了书库存没减。// 借书扣库存和插记录必须同一事务 public boolean borrow(int bookId, int userId) { String updateStock UPDATE book SET stock stock - 1 WHERE id ? AND stock 0; String insertRecord INSERT INTO borrow(book_id, user_id, borrow_date, status) VALUES(?,?,NOW(),0); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关掉自动提交 try (PreparedStatement ps1 conn.prepareStatement(updateStock)) { ps1.setInt(1, bookId); if (ps1.executeUpdate() 0) { // 库存不足回滚 conn.rollback(); return false; } } try (PreparedStatement ps2 conn.prepareStatement(insertRecord)) { ps2.setInt(1, bookId); ps2.setInt(2, userId); ps2.executeUpdate(); } conn.commit(); // 两步都成功才提交 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }逻辑说明setAutoCommit(false)之后executeUpdate不会立即写库必须显式commit。UPDATE ... WHERE stock 0这个条件很关键它把“库存判断”和“扣减”合成一条原子 SQL避免先查后扣之间被其他请求插队。如果executeUpdate返回 0说明库存已经不够直接rollback返回失败。两步都成功才commit任何一步抛异常都在catch里回滚。这套写法比在 Java 里先SELECT stock再判断再UPDATE稳得多后者在并发下会超卖。5.3 打包成 war 部署到独立 TomcatIDEA 里跑通不代表能部署到服务器。传统 JSP 项目打包 war 的常见做法是mvn clean package在target目录下生成webDemo.war丢进 Tomcat 的webapps目录启动 Tomcat 自动解压。注意pom.xml里packaging必须是warmaven-war-plugin的failOnMissingWebXml在web.xml存在时不用管不存在时要设成false。打包后如果访问 404先看webapps下有没有解压出目录再看logs/catalina.out里的部署日志通常是依赖冲突或web.xml配置错误。从那以后我每次拿到这类 JSP 老项目都强制先跑通登录和列表两条链路再动代码改完一个模块就打包部署一次不在 IDE 里攒一堆改动。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。