
简介这是一套基于Java技术栈的图书管理系统完整源码采用JSP、Servlet、JDBC与MySQL组合实现适合正在学习Java Web开发、需要理解MVC设计模式的初学者与在校学生可作为课程设计或练手项目的参考范例。压缩包共95个文件约1.7MB其中17个java文件承载Servlet与业务逻辑13个html与37个jpg、4个png等图片资源构成页面与素材11个css和3个js负责样式与交互5个xml与1个sql脚本用于配置和数据库建表另有iml等工程文件。项目按web-INF、jsp、servlet、lib、css、js等目录组织清晰体现视图、控制与数据访问的分层结构涵盖登录注册、图书列表、增删改查等典型功能。已有275人学习下载读者可借此掌握JDBC连接MySQL、Servlet处理请求、JSP渲染页面的完整流程并对照源码理解MVC在Web开发中的落地方式为后续Java Web进阶打下基础。1. 从一份 jspjdbcmysqlservlet 图书管理项目源码说起这套老技术栈为什么还值得动手跑一遍很多人第一次拿到jspjdbcmysqlservlet图书管理项目源码.zip第一反应是「这玩意儿是不是过时了」。我特别理解这种心态毕竟现在满屏都是 Spring Boot、MyBatis、Vue 前后端分离谁还愿意回头看 JSP 这种在 HTML 里写 Java 代码的东西。但如果你真的把这份源码解压、配好数据库、丢进 Tomcat 跑起来你会发现它反而是理解 JavaWeb 底层最直接的一条路——没有框架帮你把请求封装得严严实实HttpServletRequest怎么拿参数、Servlet生命周期怎么走、JDBC 怎么和 MySQL 握手全都摊在你面前。这份项目本质上是一个典型的 MVC 分层小系统Servlet 当控制器接请求JSP 当视图渲染页面JDBC 直连 MySQL 做增删改查业务对象是图书书名、作者、出版社、库存、价格这些字段。它适合三类人一是刚学完 Java 语法、想找个完整案例把 Servlet 和 JSP 串起来的学生二是要交基于 JSP 的毕设选题、需要一份能跑通的骨架的人三是工作几年但一直用框架、想回头补一补「框架到底帮你干了什么」的工程师。接下来我不讲空理论直接按「环境怎么搭 → 代码怎么分层 → 参数怎么调 → 坑在哪」的顺序把这份源码从解压到跑通的全过程拆开讲你照着做就能在自己机器上复现一套。2. 把 JSP 项目跑起来IDEA 建工程、Tomcat 配置与 MySQL 建库建表2.1 环境选型为什么用 IDEA Tomcat MySQL 8 这套组合先说选型理由避免你中途换工具踩更多坑。IDE 用 IntelliJ IDEA因为热词里idea新建jsp项目的搜索量一直很高IDEA 对 Web 项目的 Artifact 和 Tomcat 集成做得比 Eclipse 顺手尤其是打 war 包和配置上下文路径这两步。服务器用 Tomcat 8.5 或 9.0别上 Tomcat 10——Tomcat 10 把javax.servlet换成了jakarta.servlet这份老源码里的import javax.servlet.*会全部报红改包名能改到你怀疑人生。JDK 用 8 或 11JDK 17 配老 Tomcat 也容易出幺蛾子。数据库用 MySQL 8.0字符集统一utf8mb4。热词里mysql安装教程、windows 安装 mysql 8、mysql安装配置教程反复出现说明很多人卡在装库这一步。我的建议是 Windows 直接用官方 MSI 安装包勾上 MySQL Server 和 MySQL WorkbenchLinux 用rpm安装mysql或 apt 都行但一定要记住 root 密码后面 JDBC 连接要用。连接池这块这份源码大概率是每次请求DriverManager.getConnection直连能跑但性能差后面第 5 章我会讲怎么换成 Druid 或 HikariCP。2.2 在 IDEA 里新建 Web 工程并导入源码不要直接「Open」那个 zip 解压出来的文件夹就当项目用老项目的目录结构经常和 IDEA 的模块识别对不上。正确做法是新建一个 Java Enterprise 工程再把源码文件按包结构拷进去。# 1. 解压源码先看清楚目录结构再动手 unzip jspjdbcmysqlservlet图书管理项目源码.zip -d bookmanage cd bookmanage # 典型结构长这样 # src/ Java 源码Servlet、DAO、实体类 # WebContent/ 或 web/ JSP、WEB-INF、web.xml # lib/ 第三方 jarmysql-connector-java 等 find . -maxdepth 2 -type dIDEA 操作步骤 1. File - New - Project - Java Enterprise 2. 勾选 Web ApplicationJDK 选 1.8 3. Application Server 选已配置好的 Tomcat 9.0 4. 项目建好后把源码 src 下的包整体拷到 src/main/java 5. 把 WebContent/web 下的内容拷到 src/main/webapp 6. lib 下的 jar 拷到 webapp/WEB-INF/lib右键 Add as Library逻辑说明IDEA 的 Java Enterprise 工程默认用 Maven 或普通模块管理src/main/java放 Java 代码src/main/webapp放 JSP 和静态资源WEB-INF/lib放 jar。老项目如果是WebContent目录直接对应到webapp即可。参数上web.xml里的welcome-file决定首页通常是login.jsp或index.jsp先打开它确认入口。提示如果导入后 JSP 里% page %报红检查 Project Structure - Modules - Dependencies 里有没有把 Tomcat 的servlet-api.jar加进去Scope 设为 Provided。2.3 建库建表图书表的字段设计与初始化 SQLJDBC 能不能跑通一半取决于数据库建得对不对。图书管理系统的核心表就一张book外加一张user做登录。-- 建库字符集必须 utf8mb4否则中文书名会乱码 CREATE DATABASE bookdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE bookdb; -- 图书表 CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) COMMENT 作者, publisher VARCHAR(80) COMMENT 出版社, price DECIMAL(10,2) DEFAULT 0.00 COMMENT 价格, stock INT DEFAULT 0 COMMENT 库存, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 插一条测试数据 INSERT INTO book(name, author, publisher, price, stock) VALUES (JavaWeb 从入门到跑通, 老张, 机械工业出版社, 59.90, 10); INSERT INTO user(username, password) VALUES (admin, 123456);逻辑说明id用自增主键price用DECIMAL而不是FLOAT因为金额用浮点会有精度误差这是血泪经验。stock默认值设 0对应热词里mysql设置默认值为0的诉求。字符集三处都要统一库、表、连接串缺一处中文就变问号。参数说明VARCHAR(100)对书名够用作者 50 够用DECIMAL(10,2)表示总共 10 位、小数 2 位最大能存 99999999.99图书价格绰绰有余。create_time用CURRENT_TIMESTAMP自动填充省得代码里手动 set。2.4 配置 Tomcat 与 JDBC 连接串数据库建好后改 JDBC 工具类里的连接信息。老项目一般有个DBUtil.java或BaseDao.java。public class DBUtil { // 注意MySQL 8 的驱动类是 com.mysql.cj.jdbc.Driver不是老的 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // useSSLfalse 关掉 SSL 警告serverTimezone 必须指定否则时间差 8 小时 private static final String URL jdbc:mysql://localhost:3306/bookdb?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4; private static final String USER root; private static final String PASSWORD 你的密码; public static Connection getConnection() throws Exception { Class.forName(DRIVER); // 加载驱动 return DriverManager.getConnection(URL, USER, PASSWORD); } }逻辑说明Class.forName触发驱动注册MySQL 8 的驱动 jar 是mysql-connector-java-8.x.jar放WEB-INF/lib下。连接串里serverTimezoneAsia/Shanghai是必须的不写会报时区错误或时间错乱useSSLfalse只是关掉连接时的 SSL 警告本地开发够用。参数说明端口默认 3306库名bookdb要和建库时一致。如果报Access denied for user是账号密码错报Unknown database是库名写错或没建库报Public Key Retrieval is not allowed在连接串后加allowPublicKeyRetrievaltrue。3. Servlet 生命周期与 JDBC 增删改查把控制器和 DAO 层写清楚3.1 Servlet 生命周期init、service、destroy 到底什么时候被调热词里servlet生命周期是高频问题很多人背了概念但不知道它对写代码有什么用。简单说Tomcat 启动时或第一次访问该 Servlet 时调用init()之后每个请求都走service()再分派到doGet/doPostTomcat 关闭或重新部署时调destroy()。这意味着init()里适合放只执行一次的初始化比如加载配置doGet/doPost里放每次请求都要做的业务。public class BookListServlet extends HttpServlet { private BookDao bookDao; Override public void init() throws ServletException { // 只执行一次适合初始化 DAO bookDao new BookDao(); System.out.println(BookListServlet 初始化完成); } Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListBook books bookDao.findAll(); // 每次请求查库 req.setAttribute(books, books); req.getRequestDispatcher(/book_list.jsp).forward(req, resp); } Override public void destroy() { System.out.println(BookListServlet 销毁); } }逻辑说明init()里 new 一个 DAO避免每次请求都创建doGet查完数据用setAttribute存进 request 域再forward到 JSP 渲染。注意forward是服务器内部跳转浏览器地址栏不变sendRedirect是让浏览器重新请求地址栏会变两者别混用。参数说明req.setAttribute的 key 叫booksJSP 里就得用${books}或%request.getAttribute(books)%取名字必须对上。getRequestDispatcher的路径以/开头相对 webapp 根目录。3.2 JDBC 增删改查PreparedStatement 与四个核心方法DAO 层是这份源码的核心热词里jdbc增删改查、jdbc插入用户数据、第1关jdbc插入用户数据都指向这里。统一用PreparedStatement别用Statement拼字符串否则既有 SQL 注入风险又容易因为引号出错。public class BookDao { // 查询全部 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 (Exception e) { e.printStackTrace(); // 生产环境应换成日志 } return list; } // 新增 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 (Exception e) { e.printStackTrace(); return 0; } } // 删除 public int delete(int id) { String sql DELETE FROM book WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); return 0; } } // 修改 public int update(Book b) { String sql UPDATE book SET name?, author?, price?, stock? WHERE id?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, b.getName()); ps.setString(2, b.getAuthor()); ps.setBigDecimal(3, b.getPrice()); ps.setInt(4, b.getStock()); ps.setInt(5, b.getId()); return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); return 0; } } }逻辑说明用 try-with-resources 自动关闭Connection、PreparedStatement、ResultSet避免连接泄漏——这是新手最容易翻车的地方忘了 close 跑几次就Too many connections。executeQuery返回结果集用于查询executeUpdate返回 int 用于增删改。参数说明ps.setString(1, ...)的下标从 1 开始不是 0写错会报Parameter index out of range。setBigDecimal对应DECIMAL字段setInt对应INT。查询时rs.getInt(id)用列名比用下标可读性好但列名要和 SQL 里查出来的别名一致。3.3 JSP 页面渲染EL 表达式与 JSTL 循环输出图书列表视图层用 JSP热词里jsp入门、jsp个人信息展示页面、jsp入门头歌实训答案说明很多人卡在 JSP 语法。核心就两点用 EL 表达式${}取值用 JSTL 的c:forEach循环。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle图书列表/title/head body h2图书管理/h2 table border1 cellpadding6 trthID/thth书名/thth作者/thth价格/thth库存/thth操作/th/tr c:forEach items${books} varb tr td${b.id}/td td${b.name}/td td${b.author}/td td${b.price}/td td${b.stock}/td td a hrefbook?actiondeleteid${b.id}删除/a a hrefbook?actioneditid${b.id}编辑/a /td /tr /c:forEach /table /body /html逻辑说明c:forEach遍历 request 域里的books每次循环把当前对象赋给b${b.name}会自动调用getName()。用 JSTL 需要jstl.jar和standard.jar放WEB-INF/lib否则标签不生效。参数说明items${books}里的名字必须和 Servlet 里setAttribute(books, ...)一致。varb是循环变量名随便起但要和${}里对上。删除链接用?actiondeleteid传参Servlet 里用req.getParameter(action)判断走哪个分支。4. 避坑与排查JSPJDBCMySQLServlet 项目最常见的 5 个翻车现场4.1 中文乱码从请求到响应到数据库的三处编码现象页面显示的书名是「????」或「锟斤拷」。原因编码没统一。解决分三处JSP 页面顶部% page contentTypetext/html;charsetUTF-8 %Servlet 里req.setCharacterEncoding(UTF-8)和resp.setContentType(text/html;charsetUTF-8)数据库连接串加characterEncodingutf8mb4库表也用 utf8mb4。三处缺一处都可能乱码尤其是 POST 请求不设setCharacterEncoding必乱。4.2 驱动加载失败ClassNotFoundException 与版本不匹配现象启动报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver老写法找不到。解决改驱动类名并确认mysql-connector-java-8.x.jar真的在WEB-INF/lib下且被 IDEA 识别为库。热词里intellij idea sqlserver jdbc 自动下载 download from maven failed反映的是同类问题——依赖没下下来手动把 jar 拷进 lib 最稳。4.3 连接泄漏Too many connections 是怎么攒出来的现象跑一会儿报Too many connections。原因每次请求都getConnection但没 close连接池被占满。解决所有 JDBC 操作都用 try-with-resources或者 finally 里显式 close。更彻底的做法是引入连接池第 5 章讲。另外检查有没有在循环里反复 getConnection那是灾难。4.4 404 与 500路径写错和空指针各占一半现象访问页面 404或提交表单 500。原因404 多半是web.xml里url-pattern和表单 action 对不上或者 JSP 放错目录500 常见是req.getParameter拿到 null 直接.trim()或转 int。解决先在浏览器看请求 URL对照web.xml的 servlet-mapping500 就看 Tomcat 控制台堆栈定位到具体行对可能为 null 的参数做判空。4.5 时间差 8 小时serverTimezone 不写就中招现象插入的时间比实际早或晚 8 小时。原因MySQL 8 默认时区和 JVM 不一致连接串没指定serverTimezone。解决连接串加serverTimezoneAsia/Shanghai。这个坑很隐蔽因为数据能插进去只是时间不对不仔细看发现不了。5. 从能跑到好用连接池替换与 war 包部署的进阶技巧项目跑通只是起点真正让它像个能交付的东西还得做两件事换连接池、打 war 包部署。先说连接池。前面每次请求DriverManager.getConnection都要经历 TCP 握手和认证QPS 一高就顶不住。换成 Druid 或 HikariCP连接复用性能立竿见影。以 Druid 为例加druid-1.2.x.jar到 lib写个工具类public class DruidUtil { private static DataSource ds; static { DruidDataSource d new DruidDataSource(); d.setUrl(jdbc:mysql://localhost:3306/bookdb?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4); d.setUsername(root); d.setPassword(你的密码); d.setInitialSize(5); // 初始连接数 d.setMaxActive(20); // 最大连接数 d.setMinIdle(5); // 最小空闲 d.setMaxWait(3000); // 获取连接超时 3 秒 ds d; } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }参数说明initialSize是启动时建几条连接maxActive是上限maxWait是拿不到连接等多久就抛异常。这几个值按机器和并发调本地开发 5/20 够用。DAO 里把DBUtil.getConnection()换成DruidUtil.getConnection()即可其余代码不动。再说打包部署。热词里传统jsp项目打包war是刚需。IDEA 里 File - Project Structure - Artifacts加一个 Web Application: Archive输出.war。然后丢进 Tomcat 的webapps目录启动 Tomcat 会自动解压部署访问http://localhost:8080/项目名/。注意 war 包里必须包含WEB-INF/lib下的所有 jar 和web.xml漏了就是 404 或 ClassNotFound。最后说个验证技巧部署后别只看首页能不能开要完整走一遍「登录 → 列表 → 新增 → 编辑 → 删除」每步看 Tomcat 日志有没有异常。我自己的习惯是先在本地把增删改查全点一遍确认数据库里数据真的变了再考虑往服务器上放。这套 JSPJDBCMySQLServlet 的老组合跑通一次你对 JavaWeb 的理解会比看十篇框架教程都扎实。希望帮到你。本文还有配套的精品资源点击获取