☰
JSP+JDBC+MySQL+Servlet图书管理项目实战:从源码到部署全解析
2026/10/7 6:31:52 网站建设 项目流程

简介:面向Java Web初学者的完整图书管理项目源码,基于JSP、JDBC、MySQL与Servlet技术栈,实现了图书信息的新增、修改、删除和查询功能,适合课程设计或毕业设计参考。压缩包共包含九十五个文件,包括十七个Java类与Servlet处理逻辑、十三个HTML页面、十一个CSS样式表、五个XML配置项、一个SQL数据库脚本以及数十张页面截图,整体大小约一点七兆,目录结构清晰,便于按模块对照学习。目前已有两百七十五人学习,作为入门级Java Web项目具有不错的参考价值。通过分析源码,可以深入理解JSP负责视图展示、Servlet承担请求控制、JDBC连接MySQL执行数据操作的分层思路,掌握从数据库设计到页面渲染的完整流程;同时借助数据库脚本与配置文件快速搭建运行环境,结合截图素材验证界面效果,对巩固Java Web开发技能和实践MVC模式都很有帮助。

1. JSP+JDBC+MySQL+Servlet 图书管理项目:先搞清楚这套源码到底能给你什么

如果你是计算机相关专业的学生,大概率在课程设计或毕业设计里见过这个标题。一个用 JSP 做页面、Servlet 做控制器、JDBC 操作 MySQL 的图书管理项目,几乎是 JavaWeb 方向最经典的起步组合,也是一代代学生“从 Hello World 走向完整项目”的第一道坎。这套源码能让你看到前端页面和后端逻辑是怎么通过 HTTP 请求串联起来的,也能让你理解表结构设计、连接池配置、增删改查这些基本功在实际项目里怎么落。

但很多人拿到源码后第一个困惑是:这玩意儿到底怎么跑起来?Tomcat 版本、JDK 版本、MySQL 版本、驱动 jar 包,任何一个不匹配都会让项目在启动阶段就翻车。这个项目本身的技术栈虽然老,但它把 JavaWeb 最核心的“请求—处理—响应”链路完整走了一遍,非常适合用来补课。如果你正被“项目跑不起来”卡住,或者想拿它改一个能过答辩的课设,本章先帮你把整体框架拆清楚。

2. 源码目录与服务端技术栈:为什么 JSP 是视图、Servlet 是控制器、JDBC 是数据通道

2.1 解压后先看目录:命令与结构判断

拿到 zip 包第一件事不是急着导入 IDE,而是先把压缩包解压,看清目录结构再决定用 Eclipse 还是 IDEA 打开,以及是否要转成 Maven 工程。老式 JavaWeb 项目和 Maven 项目的目录差异非常大,判断错了后面全是坑。

unzip jsp+jdbc+mysql+servlet图书管理项目源码.zip -d book-manager cd book-manager find . -type f | head -50

解压命令执行完后,重点看有没有pom.xml。有它,说明是 Maven 工程,依赖由中央仓库统一管理;没有,说明是纯 lib 目录放 jar 包的传统 Web 工程。早期课程设计绝大多数是后者,lib文件夹里直接躺着 mysql-connector-java 的 jar 包,src目录下按com.xxx.dao、com.xxx.servlet、com.xxx.entity分包。看目录只是第一步,真正的技术栈判断还得看web.xml。

WEB-INF/web.xml里的配置能告诉你几件关键事:Servlet 版本决定了 Tomcat 版本下限;项目欢迎页指向哪个 JSP;以及有没有配置统一编码过滤器。常见配置是欢迎页指向login.jsp或index.jsp,Servlet 版本 3.0 或 3.1,对应 Tomcat 7/8。如果你电脑装的是 Tomcat 9 或 10,Servlet 3.1 的工程理论上能跑,但验证头(请求头校验)和包名变化会引来不必要的麻烦,建议优先换 Tomcat 8.5。

这个项目里,JSP 负责直接渲染 HTML 并嵌入 Java 代码片段,即服务端页面;Servlet 负责接收浏览器请求、调用 DAO 层、把结果转发回 JSP;JDBC 是 Java 连接 MySQL 的唯一官方通道。它的调用链是:浏览器发起 HTTP 请求 → Tomcat 根据web.xml里的映射规则找到对应的 Servlet → Servlet 调用 DAO → DAO 通过 JDBC 执行 SQL → 结果返回 Servlet → Servlet 转发或用重定向交给 JSP 渲染成页面。理解不了这条链路,后面你一调就报 404 或 500。

2.2 前端 JSP 页面到底在做什么:动态数据渲染与表单提交的常见写法

JSP 在这个项目里的作用非常大,它同时干了展示和接收参数两件事。很多人在读代码时最不舒服的地方就是 JSP 里频繁插入<% %>与<%= %>,但恰恰是这个不舒服能让你看清服务端渲染的完整过程。

<% List<Book> bookList = (List<Book>) request.getAttribute("bookList"); for (Book book : bookList) { %> <tr> <td><%= book.getName() %></td> <td><%= book.getPrice() %></td> <td><a href="BookServlet?action=edit&id=<%= book.getId() %>">编辑</a></td> </tr> <% } %>

这段代码的核心价值在于:Servlet 把数据塞进request对象后,JSP 直接强转取出并渲染。请求转发模式下request.getAttribute能取到值,前提是 Servlet 里用的是request.getRequestDispatcher("bookList.jsp").forward(request, response)而不是sendRedirect。重定向会让浏览器重新发一次请求,request对象已经不是同一个了,JSP 里取出来就是 null。这是个新手必踩的坑,也是老手一眼能看出代码水平的细节。

表单提交则是 JSP 页面向 Servlet 传参的主要方式。<form action="BookServlet" method="post">里的 action 必须与web.xml中的 servlet-mapping 里的 url-pattern 匹配。常见的错误是action写成/BookServlet带斜杠,而 url-pattern 是/BookServlet不带项目上下文,实际部署后路径就变复杂了。基路径的问题通常用<%=request.getContextPath()%>拼接来规避,例如${pageContext.request.contextPath}/BookServlet。老式项目很少用 EL 表达式,但你自己写的时候建议用,能少一堆字符串拼接的麻烦。

2.3 JDBC 驱动选择与 MySQL 版本匹配

JDBC 是整个项目里最低层、也最容易出“黑匣子”问题的环节。驱动 jar 包版本不对,或者连接 URL 格式写错,报错信息往往含糊其辞,你得从上千行堆栈里定位真正原因。

Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/bookdb?useSSL=false&characterEncoding=utf8"; String username = "root"; String password = "123456"; Connection conn = DriverManager.getConnection(url, username, password);

这段代码是 MySQL 5.x 配老版本驱动的标准写法。Class.forName是反射加载驱动类,注册到 DriverManager。注意com.mysql.jdbc.Driver是 MySQL 5.x 驱动包里的类名;如果你用的是 MySQL 8 以上版本和配套驱动,类名必须改成com.mysql.cj.jdbc.Driver,同时 URL 里需要补serverTimezone=Asia/Shanghai,否则会报时区错误。很多人在“MySQL 8 驱动连接 MySQL 5.7 数据库”或反过来混用时在这里翻了车。

数据库账号密码要检查两个位置,一个是db.properties或jdbc.properties配置文件,一个是 DAO 工具类里面直接写死的地方。老式课设常犯的毛病是把密码写在 Java 类里硬编码,你拿到源码后第一件事是全局搜索pwd或password字符串,把所有数据库连接配置统一改掉。另外 URL 中useSSL=false建议保留,本地开发环境不开 SSL 能减少握手时间;如果出现SSL connection error就检查这一项是不是被改成了 true。

2.4 数据库初始化:SQL 脚本导入顺序与数据一致性

图书管理项目一般自带一个book.sql或init.sql文件,里面是建库、建表、插测试数据的语句。导入时用命令行最稳妥,而不是在 Navicat 里双击执行。命令行能让你看到每一条语句的执行结果,出错马上定位。

mysql -uroot -p < init.sql

看到Query OK连续出现才说明导入成功。导入完成后用use bookdb; show tables;确认表都在。表结构至少要包含三张:图书表book(id、name、author、price、publisher、stock 或类似字段)、用户表user(id、username、password)、借阅记录表(如果功能多的话)。有些项目只有一张图书表加一张用户表,纯做增删改查也能过。

注意 SQL 文件里的编码格式。如果文件是 UTF-8 而 MySQL 客户端默认是 latin1,导入后中文会变成乱码。导入前执行set names utf8;,或者在命令行加--default-character-set=utf8参数。这种问题的特征是代码没问题但页面显示中文乱码,第一反应应该去查数据库里存进去的数据本身是不是乱的。

3. 跑通本地环境:Tomcat 版本、MySQL 连接参数与项目导入三分法

3.1 为什么 Tomcat 8.5 是这个项目最稳的运行底座

技术老的项目有一个共性:对运行环境挑剔。JSP 和 Servlet 的版本是工程创建时定死的,你用太高版本的 Tomcat 反而容易出现兼容问题。Tomcat 8.5 支持 Servlet 3.1、JSP 2.3,和绝大多数课设项目的web.xml头部声明匹配;Tomcat 9 对应 Servlet 4.0,Tomcat 10 开始把javax.servlet包改名为jakarta.servlet,老代码直接无法编译。所以最稳的 Tomcat 版本是 8.5.xx,如果下载不到,Tomcat 9 也可以尝试,但小于 9 的版本就别用了。

Tomcat 的配置有一个关键点:端口。默认 8080 可能被占用,特别是你电脑里已经装了其他服务。修改conf/server.xml里的<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />,把端口号改掉。这里还经常伴随一个问题:Tomcat 启动一闪而过,看不到日志。这时候要去logs/catalina.out或logs/localhost.log里查 JVM 内存和端口异常。

3.2 导入 Eclipse 与 IDEA 的两种姿势和踩坑点

Eclipse 和 IDEA 对老式 Web 工程的处理方式略有不同。我不能替你决定用哪个 IDE,但两种方式的要点都可以讲清楚。Eclipse 用户选File > Import > General > Existing Projects into Workspace,但前提是工程里有.project和.classpath文件,否则导入后不识别为 Java 工程。如果导入后代码没报错但无法运行,多半是 Project Facets 里的 Dynamic Web Module 版本没勾对。

IDEA 导入同样有讲究。选择File > New > Project from Existing Sources,把根目录指到解压出的文件夹。IDEA 会识别出这是 Web 工程,要求你配置本地 Tomcat。有一步容易漏:File > Project Structure > Libraries里手动添加WEB-INF/lib下的驱动 jar 包,否则编译时找不到com.mysql.jdbc.Driver。老式工程没有 Maven 帮你下载依赖,所有 jar 都是手工管理,动手添加是必须的。

导入后第一件事不是点运行按钮,而是先Build > Rebuild Project,确认编译通过。如果一堆Cannot resolve symbol 'HttpServlet'报错,说明缺少 Servlet API 的 jar 包。Tomcat 安装目录下的lib/servlet-api.jar可以手动加进工程,或者用 IDE 的 Tomcat 集成功能自动补上。有些源码包里没有 lib 目录,那是别人删了 jar 包再压缩上传的,你得自己补齐驱动、servlet-api、jstl 三个核心包,否则连编译都过不去。

3.3 MySQL 连接配置文件重新对齐:把 URL、账号、密码放到一个地方

把数据库配置集中管理是这套源码必须做的一次“体检”。老式项目常见两种做法,一种是写死在 DBUtil 的 Java 代码里,另一种是读取db.properties。后者显然更好改,但拿到手的源码未必采用,需要你自己重构。新建一个db.properties文件放在src根目录,内容如下:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/bookdb?useSSL=false&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

配套在工具类里加载:

static { InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("db.properties"); Properties props = new Properties(); props.load(in); Class.forName(props.getProperty("jdbc.driver")); url = props.getProperty("jdbc.url"); username = props.getProperty("jdbc.username"); password = props.getProperty("jdbc.password"); }

把这个配置外置以后,换环境就不用改 Java 代码再重新编译了。但是文件路径必须确认是src根目录且类加载器能找到。getResourceAsStream("db.properties")不带斜杠是当前类所在包路径的类路径根,如果你把文件放在别的子包下,这句代码直接返回 null,后面props.load就抛空指针异常。

带 MySQL 8 驱动时,记得把配置文件里的驱动类名改成com.mysql.cj.jdbc.Driver,URL 加serverTimezone=Asia/Shanghai,版本对齐后连接成功率能提高一大截。这个配置环节因为太基础,经常被忽视,但恰恰是项目跑不起来的首要原因。

3.4 启动 Tomcat 后的自检清单:从 404 到 500 的问题排查

部署完成、Tomcat 跑起来以后,先用浏览器访问http://localhost:8080/项目名/。这一步可能出现三种结果:页面白屏、404、500。白屏先看 Tomcat 控制台有没有异常,通常是有类没找到或数据库连接失败;404 是 URL 路径或上下文路径不对,确认项目名是否带/;500 多数是 Servlet 代码或 JSP 编译出错。

一个有效的自检方法:把浏览器访问的 URL 和web.xml里的 servlet-mapping 对照看。比如登录页能打开但点登录报 404,问题几乎一定在action="UserServlet"写错或映射不一致。比如点登录直接 500,看 Tomcat 日志里的Caused by行,最常见的两个原因:SQL 语句语法问题或字段不存在。日志里的堆栈信息会直接告诉你错误发生在哪一行代码,对照代码改就行。

到这里,项目已经在本地立住了。后面真正花时间的往往不是业务功能,而是各种环境细节——字符集、外键约束、资源释放。下一个章节专门把这几个硬骨头拆开。

4. 图书增删改查的业务闭环:从 DAO 层到 Servlet 再到 JSP 的完整数据流

4.1 增删改查操作对应的 SQL 与关键参数

图书管理核心是增删改查四类操作,它们在你的 DAO 里各有对应方法。这里有两个容易出问题的地方,一个是 SQL 注入,另一个是查询结果集映射成对象时字段名大小写匹配。

// 添加图书 String sql = "INSERT INTO book(name, author, price, publisher) VALUES(?, ?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, book.getName()); ps.setString(2, book.getAuthor()); ps.setBigDecimal(3, book.getPrice()); ps.setString(4, book.getPublisher()); int rows = ps.executeUpdate();
// 删除图书 String sql = "DELETE FROM book WHERE id = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, id); int rows = ps.executeUpdate();

用PreparedStatement加?占位符,而不是直接拼接字符串,是老项目里最重要的优化点。用Statement拼接你也能达到目的,但万一书名里有单引号,SQL 就会因为语法错误直接炸掉,而且存在注入风险。setBigDecimal是专门给价格字段用的,货比三家地避开float/double带来的精度问题——数据库里price DECIMAL(10,2)配BigDecimal才是正路。

还有一个几乎所有新手都会踩的坑:增删改操作后要不要“刷新列表页”。在 Servlet 里,添加完图书后一般用重定向回到列表页,才能看到新数据。如果用了转发,浏览器地址栏不变,用户按一下 F5 就会重复提交上一次的请求,数据库里会出现两条一模一样的记录。这是一个典型的“刷新翻车”场景,用response.sendRedirect("BookServlet?action=list")就能避开。

4.2 Servlet 里 param 与 attribute 的区别:请求参数和数据传递的边界

Servlet 代码里有两类对象:从request.getParameter()拿到的用户输入和request.setAttribute()塞给 JSP 的数据。前者是浏览器通过 GET/POST 提交的字符串,后者是 Servlet 在内部处理完业务后传给视图渲染的对象。二者混用的后果是你从 JSP 里取不到值,或者误把一个参数当成对象去强转。

String action = request.getParameter("action"); if ("add".equals(action)) { String name = request.getParameter("name"); String author = request.getParameter("author"); String priceStr = request.getParameter("price"); BigDecimal price = new BigDecimal(priceStr); Book book = new Book(); book.setName(name); book.setAuthor(author); book.setPrice(price); BookDao dao = new BookDao(); dao.addBook(book); response.sendRedirect("BookServlet?action=list"); }

这里有一个很实际的问题:priceStr是空字符串,或包含非数字字符时,new BigDecimal(priceStr)会抛NumberFormatException。正规写法是先判断非空再用try-catch包住。但很多课设源码不做校验,所以你拿到项目后最好自己补上——万一答辩老师故意输一个非法值,你就能现场救回来。action这个参数的取值决定了 Servlet 走哪个业务分支,所以 URL 里的?action=add是必须匹配的,写错就落入 else 分支或者什么都没发生。

4.3 JSP 列表页与编辑页之间怎么串起来:因为 id 的参数传递

编辑功能的链路比添加稍微复杂一点,因为你要把数据库里已有的数据回显到表单上。在列表页每个“编辑”链接上带上主键 id,Servlet 根据 id 查回整条记录,转发到一个edit.jsp,在表单的 value 里填入旧数据。

String idStr = request.getParameter("id"); int id = Integer.parseInt(idStr); Book book = bookDao.findById(id); request.setAttribute("book", book); request.getRequestDispatcher("edit.jsp").forward(request, response);

edit.jsp的表单提交路径要区分“更新”和“插入”两个动作。常见设计是BookServlet?action=update,通过一个隐藏字段<input type="hidden" name="id" value="<%= book.getId() %>" />把主键带过去。如果没有这个 hidden 字段,更新操作就不知道要改哪条记录;新手经常在这里漏掉,导致只能新增、更新失败。这是整个项目里最容易被忽略的隐藏参数之一。

4.4 事务与连接释放:“用完就关”为什么比功能本身更重要

增删改查本身的代码不难,难的是资源管理。JDBC 连接是昂贵资源,用完不还回连接池,系统迟早耗尽连接池报Connection is not available。这段代码是所有 DAO 方法的基本盘:

try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { // 业务代码 } catch (SQLException e) { e.printStackTrace(); }

Java 7 的 try-with-resources 语法可以自动关闭PreparedStatement和Connection,不用手工写finally块。源码若是老写法,里面一堆finally { conn.close(); },你再检查一遍是否所有分支都会关闭连接。最常见的问题是在一个方法里开了连接,某个return分支忘了关闭,一次两次看不出问题,跑一天后数据库连接数爆满,整个项目卡死。

多个表同时更新时需要开启事务,比如借书操作涉及“插入借阅记录”和“图书库存减一”两个动作,任何一个失败都应当回滚,不能出现记录有了但库存没减的情况。

Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 插入借阅记录 // 扣减库存 conn.commit(); } catch (SQLException e) { if (conn != null) { conn.rollback(); } } finally { if (conn != null) { conn.close(); } }

setAutoCommit(false)把这个连接从“每句 SQL 自动提交”切换到“手动提交”。注意:事务的作用范围是以Connection为边界的,你在一个方法里开连接才能管住同一个连接上的多条 SQL。这个逻辑单独写一个borrowBook方法然后在 Servlet 里调用,别把事务代码散写在 Servlet 里,否则线程安全会被你亲手破坏。

5. 图书管理项目避坑指南:乱码、SSL、驱动类名与运维细节——现象、原因与排查路径

5.1 tomcat 启动后访问页面 404:virtual path 与项目上下文路径的区别

现象:Tomcat 正常启动,浏览器访问localhost:8080能看到 Tomcat 首页,但访问localhost:8080/book/login.jsp直接 404。

原因:项目部署的上下文路径和你输入的 URL 不匹配。web.xml里 Servlet 映射用的是相对应用根路径的/BookServlet,但浏览器访问一个 JSP 时必须带上上下文路径,即/项目名/BookServlet或/项目名/login.jsp。如果你把项目打成 war 包部署时名字是book.war,那访问根就是/book;如果是直接把解压文件夹丢进webapps,访问根取决于文件夹名。很多人把压缩包解压后文件夹名是一长串乱码或带版本号,访问时用了文件夹名里的回车或空格,就报 404。

解决:访问路径统一走http://localhost:8080/你的文件夹名/login.jsp。更省心的办法是在页面里用${pageContext.request.contextPath}动态拼接,这样不管你项目文件夹叫什么都无需改代码。如果 JSP 页面里是硬编码<form action="/BookServlet">,那去掉前导斜杠或改用上下文路径。

5.2 MySQL 8 驱动连接老库报 SSL 错误或时区错误

现象:项目启动或首次查询时报The server time zone value '�й���׼ʱ��' is unrecognized,紧接着是一长串英文堆栈,后面还跟着 SSL 握手问题。

原因:使用的是 MySQL 8 驱动(类名com.mysql.cj.jdbc.Driver),但 URL 里没带serverTimezone和useSSL=false。MySQL 8 驱动默认开启 SSL 连接,并且强制要求服务端时区明确;本地开发的单机 MySQL 通常没配置过 SSL 证书,也没有明确时区,驱动就像一台没有地图的汽车到处乱撞。

解决:把 URL 改成jdbc:mysql://localhost:3306/bookdb?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8。如果你用的是 MySQL 5 驱动(类名com.mysql.jdbc.Driver),连接 MySQL 8 则反过来是驱动不支持新协议,建议换驱动包。两套组合不要混:MySQL 5.7 + mysql-connector-java 5.x,或者 MySQL 8 + mysql-connector-java 8.x,这是最不容易出错的两组搭配。

5.3 页面中文乱码:字符集“三处对齐”缺一不可

现象:数据库中查到的中文是正常的,但浏览器显示??或者乱码一堆。

原因:请求编码、响应编码、数据库连接编码、页面文件编码这四处只要有一处没设置成 UTF-8,中文就会在某一环被转成字节流后再被错误解码。JSP 文件的charset只是页面编码声明,不影响数据库交互;更多时候是 jdbc URL 少了characterEncoding=utf8,或者 JSP 里没有request.setCharacterEncoding("UTF-8")。

解决:三步走。第一步 JSP 页面顶部写<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>;第二步在 Web.xml 里配置全局编码过滤器CharacterEncodingFilter(如果你用的 Tomcat 没有自动配置的话),确保 POST 请求参数按 UTF-8 解码;第三步检查 jdbc URL,保证characterEncoding=utf8。三处都对齐了还是乱码,去 MySQL 命令行里查show variables like 'character%',确认库和表都是 utf8mb4。

5.4 删除图书失败,外键约束报错

现象:执行删除图书操作时,程序抛Cannot delete or update a parent row: a foreign key constraint fails,或者在命令行删除被拒绝。

原因:图书表被借阅记录表通过外键引用了,比如borrow_record表里有book_id指向book.id。数据库为了保证引用完整性,不允许直接删除仍有子记录依赖的父表行。部分老源码在设计borrow_record表时即使没明确声明外键约束,也通过业务逻辑保证了一致性,删除失败是根据业务规则来的。

解决:先处理关联数据,即先删除借阅记录再删除图书。在 DAO 层里把删除操作放进事务:DELETE FROM borrow_record WHERE book_id = ?然后DELETE FROM book WHERE id = ?。如果你只是为了演示功能而忽略外键,可以在建表时写FOREIGN KEY (book_id) REFERENCES book(id) ON DELETE CASCADE,但这对课设答辩不太友好,回答“业务规则强制要求先还书再删除”更有说服力。

5.5 表单提交后页面重复插入数据:重定向问题

现象:添加图书后列表页多了一条记录,但按 F5 刷新一次又多一条,变成了“手动重复提交”。

原因:Servlet 用forward转发到 JSP 页面,浏览器地址栏没变化,刷新就是重新提交上一次 POST 请求。POST 的语义是不幂等的,二次提交必然二次插入。

解决:使用 302 重定向。添加完成之后response.sendRedirect("BookServlet?action=list")代替forward。重定向本质是浏览器重新发了一个 GET 请求,地址栏变成列表页 URL,刷新只会重复 GET,不会触发重复插入。这个模式叫 Post/Redirect/Get,是老项目里稍显前端一点但后端非常重要的思路。

6. 从课设源码到能答辩的完整作品:三个有用扩展和性能验证思路

6.1 加一个“借阅排行榜”:聚合查询与时间字段处理

如果嫌这个项目功能太少,第一个值得加的功能是借阅排行榜。它涉及 SQL 聚合操作和多表连接,复杂度适中,基本能撑住答辩里的“项目亮点”环节。在 DAO 层写一个专门的方法:

SELECT b.name, COUNT(br.id) AS borrow_count FROM book b LEFT JOIN borrow_record br ON b.id = br.book_id GROUP BY b.id, b.name ORDER BY borrow_count DESC LIMIT 10

这里有两个参数需要说明一下:GROUP BY后面必须列出所有非聚合列,否则 MySQL 启用了ONLY_FULL_GROUP_BY会直接报错;LEFT JOIN的意义在于把“从没被借过的书”也显示出来,count 为 0 的记录也在里面。这种查询结果映射到 Java 对象时,要走List<Map<String, Object>>或新建一个持有name和borrowCount两个字段的 VO。别硬塞进Book实体类里,那样字段语义就乱了。

6.2 把重复的模糊查询逻辑抽出来:DAO 层 getConnection 的复用

项目里每个 DAO 方法都有一遍相同的getConnection、prepareStatement、close三段式,读着烦人而且容易漏。你可以在BaseDao里封装一个通用的增删改执行器和查询方法,参数是 SQL 和可变参数列表。

public int executeUpdate(String sql, Object... params) { try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } return ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } }

Object...是 Java 的可变参数,底层是一个 Object 数组。调用时executeUpdate(sql, book.getName(), book.getAuthor())和直接写executeUpdate(sql, new Object[]{...})效果一样。这套抽法能减少大约 30% 的重复代码,显著提升答辩时“你的代码风格”这关的印象分。把每张表的 DAO 都继承BaseDao,你要修一个 SQL 异常时只改一处,而不用三个 DAO 轮着改。

6.3 验证读写性能:用时间戳方法做前后对比,以及为什么批量插入更重要

扩展做完了,你得能证明它有用。最简单的性能验证方式是在 Servlet 的方法入口和出口各记一个时间戳:

long start = System.currentTimeMillis(); List<Book> books = bookDao.findByName(keyword); long end = System.currentTimeMillis(); System.out.println("查询耗时:" + (end - start) + "ms");

打印结果不能说明太多,但可以给你一个横向对比依据:加索引前后各跑一次,看耗时有没有下降。图书管理项目数据量小,性能差异几乎看不出来;这里的意义在于让你熟悉验证链路,而不是优化效果本身。如果导师让优化,核心方向是给book表的name字段加普通索引,在book表数据量大时,WHERE name LIKE '...'的全文扫描变索引范围扫描,效果立竿见影还是玄学就得看数据分布了。

6.4 很关键的一步:数据库脚本同步替换为 MySQL 8 注释规范

升级到 MySQL 8 后,老项目的 SQL 文件可能有兼容性细节。比如ENGINE=InnoDB DEFAULT CHARSET=utf8在 MySQL 8 里依然能用,但utf8已经不算推荐,改成utf8mb4对中文和多语言支持更完整。DEFAULT NULL或NOT NULL的定义检查完,尤其注意用了旧式TYPE=InnoDB的脚本——MySQL 8 里已经移除了TYPE关键字,必须写成ENGINE=InnoDB,否则导入直接报错。

把这些扩展做完,你的项目已经不是“拿源码改改”的了,而是能讲清楚设计思路的作品。我自己当年带过的学弟里,有人坚持在原项目上加了分页和借阅排行榜,答辩时用源码和 SQL 日志讲原理,比那些换皮改色的明显稳很多。

这里再补一句:不管项目运行得多顺利,都先备份一份能跑的完整版本再动手改。压缩包里的原始代码是个起点,跑起来的环境是真正宝贵的东西,很多人在扩展功能时不小心把连接配置弄坏了,结果原来的代码也启动不了了。复制一份放到另一个目录,给那个能跑的版本留个后悔药。

希望这篇拆解能帮你把这个经典项目真正吃透,少走那些我当年自己踩过的弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询