简介:基于JDBC+JSP+Servlet架构的图书管理系统完整项目,面向Java Web初学者及课程设计、毕业设计人群,可用于快速实现图书信息管理、借阅归还等核心功能。项目内含完整源码、数据库脚本及项目说明文档,按指引配置环境即可直接运行,无需额外修改,属于95分以上高分必过级别的完整方案。资源包共141个文件,以Java类文件、JSP页面、SQL脚本、XML配置及少量JAR依赖为主,同时包含说明文档和配置文件,整体仅5.12MB,便于部署与分享。已有100人学习浏览,内容完整性高,适合作为课程设计参考。通过项目可掌握JDBC数据库连接、Servlet请求处理、JSP页面渲染以及三层架构分层设计等关键技能,也可作为课程答辩与功能扩展的起点,适合需要快速完成高要求作业或入门Java Web开发实践的读者。
1. 拿到压缩包先别急着导入:这套 JDBC+JSP+Servlet 图书管理系统帮你省掉的三类麻烦
如果你正在做基于 JSP 的毕设选题,或是刚学完 JavaWeb 想找一个完整项目练手,这个压缩包给的正是最经典的一套组合:JDBC 负责和 MySQL 打交道,JSP 负责把页面画出来,Servlet 在中间接收请求、调数据、跳页面。源码、数据库脚本、项目说明三样齐全,意味着你不用再东拼西凑去拼一个能跑的 Demo——至少起步阶段,别人踩过的坑已经被填平了大半。
但我要先说句泼冷水的话:直接「导入即运行」在这个项目上不太现实,更常见的是导入后第一次启动就翻车。原因多半不在代码,而在环境错位:JDK 版本、Tomcat 版本、MySQL 版本、驱动 jar 版本,四个东西只要有一个对不上,整套就跑不起来。这篇文章就顺着「先懂架构、再配环境、后踩坑、最后验证」的顺序,把从拿到 zip 到能上台演示的全过程拆给你看。
2. JDBC+JSP+Servlet 三层架构:图书管理系统的骨架为什么这样搭
2.1 把三层职责拆开:Servlet 只做调度,JSP 只做展示,JDBC 只做存取
很多初学者拿到一个 JavaWeb 项目,第一反应是找「哪个文件是首页」,然后跟着链接点。点着点着就迷路了,因为请求会从 JSP 跳到 Servlet,又从 Servlet 跳回另一个 JSP,中间还夹着 DAO 和工具类。要真正读懂这个图书管理系统,先别管具体代码,先把三层各自管什么想清楚。
这套结构对应的是 JSP Model 2 思想,也就是 MVC 在 JavaWeb 里的落地形态。Servlet 是控制器,接收浏览器发来的请求,解析参数,调用业务方法,最后决定跳转到哪个页面;JSP 是视图,只负责把数据渲染成 HTML,里面尽量不写 Java 逻辑;JDBC 封装在 DAO 层和工具类里,是模型的一部分,负责对 MySQL 执行增删改查。三者各干各的活,耦合度低,改页面不用动数据库代码,换数据库不用改 JSP。
| 组件 | 角色 | 典型工作 | 对应包/目录 |
|---|---|---|---|
| Servlet | 控制器 | 接收请求、调 DAO、跳转页面 | com.xxx.servlet |
| JSP | 视图 | 展示列表、接收表单输入 | WebContent 下的 .jsp |
| JDBC + DAO | 模型 | Connection、PreparedStatement、结果集封装 | com.xxx.dao、com.xxx.util |
如果你发现某个 JSP 里直接写了Class.forName或者DriverManager.getConnection,说明这个项目的分层并不彻底,读起来会费劲一些。常见的做法是单独抽一个JDBCUtil工具类,所有 DAO 都通过它拿连接,这样连接参数只需要维护一份。
2.2 一次「添加图书」请求的完整流转:从表单到数据库再回到列表页
理解这套系统最快的方式,是跟着一个请求走完全程。以「管理员添加一本新书」为例,流程是这样的:浏览器打开addBook.jsp填写书名、作者、价格,点击提交后表单以 POST 方式发到addBook这个地址,AddBookServlet的doPost方法被触发。
Servlet 里的逻辑很固定:先设置请求编码,再逐个取出表单参数,组装成一个 Book 对象,调用BookDao.insert(book)。DAO 方法内部通过 JDBCUtil 拿到 Connection,用 PreparedStatement 执行 INSERT,返回受影响行数。最后 Servlet 根据结果做跳转——成功就重定向到图书列表页,失败就把错误信息塞进 request 域再 forward 到错误页。
@WebServlet("/addBook") public class AddBookServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 处理中文乱码:POST 请求参数必须指定 UTF-8 request.setCharacterEncoding("UTF-8"); // 2. 从表单取参数,注意字段名要和 JSP 里的 name 一致 String bookName = request.getParameter("bookName"); String author = request.getParameter("author"); double price = Double.parseDouble(request.getParameter("price")); // 3. 调用 DAO 层执行插入,返回 1 表示成功 BookDao dao = new BookDao(); Book book = new Book(bookName, author, price); int rows = dao.insertBook(book); // 4. 根据结果决定跳转:成功重定向到列表页,避免刷新重复提交 if (rows > 0) { response.sendRedirect(request.getContextPath() + "/listBook"); } else { request.setAttribute("msg", "添加失败"); request.getRequestDispatcher("/error.jsp").forward(request, response); } } }这里有两个容易被忽略的细节。第一,request.getContextPath()返回的是项目部署名,写重定向路径时一定带上它,否则换项目名后链接全部失效;第二,成功用sendRedirect,失败用forward,前者是浏览器重新发一次请求,后者是服务端内部转发——搞混了会出现表单重复提交或地址栏和页面内容对不上的情况。
再往下走,BookDao内部大概长这样,典型的 JDBC 增删改查模板,闭眼都能背下来:
public int insertBook(Book book) { String sql = "INSERT INTO t_book (book_name, author, price, stock) VALUES (?, ?, ?, ?)"; try (Connection conn = JDBCUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, book.getName()); ps.setString(2, book.getAuthor()); ps.setDouble(3, book.getPrice()); ps.setInt(4, 1); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } }PreparedStatement的?占位符能防止 SQL 注入,这是这套代码里最值得学的习惯。try-with-resources写法让 Connection 和 PreparedStatement 自动关闭,不用手动写 finally,省心不少。但要注意,DAO 层把 SQLException 吞掉后只返回 0,Servlet 拿到的信息有限,排查问题时多半要靠e.printStackTrace()输出的日志。
2.3 源码该按什么顺序读:四个包加一份 SQL 脚本的阅读路线
拿到压缩包解压后,你看到的目录结构通常不是一堆文件乱放,而是有规律的。读懂这个规律,比逐行看代码更省时间。我一般建议先忽略 JSP,从数据库和工具类读起,由底向上看。
推荐的阅读顺序是这样的:第一份先看数据库脚本,搞清楚有几张表、表之间什么关系、有哪些测试账号——这是整个系统的地基,表结构没看明白,后面所有 SQL 都是黑匣子;第二份看JDBCUtil工具类,确认连接哪个库、用户名密码是什么;第三份看 DAO 层,把每张表对应的增删改查方法列出来;第四份看 Servlet 层,对照@WebServlet注解或 web.xml 里的映射,理清每个 URL 对应哪个动作;最后再打开 JSP,此时页面上每个表单、每个链接指向哪个地址,你已经能自己推断出来了。
包名通常是com.xxx.bean(实体类)、com.xxx.dao(数据访问)、com.xxx.servlet(控制器)、com.xxx.util(工具类),有些项目还会加com.xxx.filter放编码过滤器。如果看到的是entity、mapper这类命名,也只是习惯差异,职责是一样的。这个阅读路线同样适用于其他 JavaWeb 课设项目——CRUD 的骨架几乎都长这样,换的只是表和字段名。
3. 本地跑通最小环境:JDK、Tomcat、MySQL 的版本匹配与第一次启动
3.1 版本匹配是第一道门槛:JDK 8 + Tomcat 9 + MySQL 5.7/8.0 搭配表
这套系统能不能跑起来,第一道门槛不是代码,是环境。我见过太多人卡在「源码没问题但就是 500」上,查到最后是 Tomcat 10 的包名从javax.servlet变成了jakarta.servlet,而项目里全是用javax写的,直接编译报错。所以环境版本这事,得放在所有操作前面。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8(8u202 及之前) | 最稳,别用太高版本,JDK 17+ 会有模块化报错风险 |
| Tomcat | 8.5 或 9.0 | 不要用 Tomcat 10+,javax与jakarta包名不兼容 |
| MySQL | 5.7 或 8.0 | 5.7 用旧驱动,8.0 用新驱动,二者写法不同 |
| mysql-connector-java | 5.1.49 或 8.0.x | 关键:JDBC 驱动版本必须和 MySQL 版本对得上 |
| Eclipse / IDEA | 任意较新版本 | IDE 版本不太影响运行,影响的是 Tomcat 集成方式 |
一个很现实的建议:如果项目说明里写了环境要求,先按它来;如果没写,默认 JDK 8 + Tomcat 9 + MySQL 5.7 + 驱动 5.1.49 是兼容性最好的组合。MySQL 5.7 对驱动要求宽松,而 MySQL 8.0 必须要com.mysql.cj.jdbc.Driver这个新驱动类,URL 参数也比 5.x 多要求两个,后面避坑章节会细说。
3.2 用 Eclipse/IDEA 导入源码:两个必改配置与 jar 的放置位置
Eclipse 用户的操作路径一般是:File → Import → Existing Projects into Workspace → 选择解压后的目录。IDEA 用户则是 Open 选中解压目录,等它识别成 Maven 或普通 Web 项目。导入后先别急着启动,先改两个地方。
第一个必改配置是编译级别。右键项目 → Properties → Java Compiler,把 Compiler compliance level 调成和你本机 JDK 一致(通常是 1.8),再在 Java Build Path 里确认 JRE System Library 没有红色叉号。第二个必改配置是 Tomcat 运行时。项目上右键 → Properties → Targeted Runtimes,勾选你配置好的 Tomcat 版本,如果不勾,Eclipse 不会把项目发布到 Tomcat 的 webapps 下。
最容易被忽略的是驱动 jar 的放置位置。很多人把mysql-connector-java.jar加进了 Build Path,在 IDE 里编译通过,一启动 Tomcat 就报ClassNotFoundException。原因在于 Tomcat 运行时的类加载机制:WEB-INF/lib 下的 jar 才会被加载,Build Path 里的 jar 只对编译生效。常见做法是把 jar 复制到WebContent/WEB-INF/lib(Eclipse)或src/main/webapp/WEB-INF/lib(IDEA)目录下。这个细节卡掉过无数新手,属于典型的「环境对,目录不对」问题。
3.3 执行数据库脚本:建库建表插测试账号的三个细节
数据库脚本一般叫bookdb.sql、library.sql或者init.sql,打开后你会发现里面有完整的建库、建表、插入语句。执行方式有两种:一种是直接用 Navicat 或 MySQL Workbench 打开 sql 文件执行,另一种是在命令行里用 source 命令。
mysql -uroot -p Enter password: ******** source C:/booksystem/bookdb.sql;执行时注意三个细节。第一,确认脚本开头有没有CREATE DATABASE IF NOT EXISTS,如果没有,你得手动先建库再执行,否则会报「No database selected」;第二,脚本里如果用了utf8mb4字符集,MySQL 5.5.3 以下版本不支持,但现在基本不会遇到这么老的版本;第三,脚本里插入的测试账号密码通常是明文,比如admin / 123456,先记下来,后面登录页面要用。
执行成功后,用SHOW TABLES;确认表都建出来了。常见的表有t_book(图书)、t_user(用户)、t_borrow(借阅记录),具体表名以脚本为准。如果发现表没建全,大概率是脚本执行到一半报错中断了,修复报错后重新 source 一遍即可——脚本通常写成可重复执行的形式,里面会带DROP TABLE IF EXISTS。
3.4 改 JDBC 连接参数:driver、url、username、password 四个值怎么填
数据库建好了,接下来要把项目里的连接信息改成你本机的。连接参数一般集中在两类位置:独立的db.properties配置文件,或者JDBCUtil.java类开头的静态变量。如果是配置文件,类里面会用Properties.load()读取——这也是更推荐的做法,改参数不用重编译。
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/bookdb?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai username=root password=你的数据库密码这四个值逐个解释。driver是驱动类全名,MySQL 8.0 用com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver,用错了会报ClassNotFoundException。url里localhost:3306是数据库地址和端口,bookdb是库名,后面的三个参数分别关闭 SSL、指定 UTF-8 编码、设置时区——MySQL 8.0 缺serverTimezone会直接报时区错误。username和password填你本机 MySQL 的登录凭据,不是脚本里那个测试账号,别搞混了。
改完之后验证配置是否生效,不需要启动 Tomcat,写个简单的 main 方法或者用 JDBC 工具类直接跑一下连接测试。如果getConnection()不抛异常,说明数据库这一环已经通了,再启动项目就有了底气。很多人的思维误区是「项目启动报错才去查数据库配置」,其实提前单独验证连接,能把问题范围缩小一半。
4. 避坑:从编译报错到页面 500 的六个排查记录
这一章的每条记录都是我在实际跑这类课设项目时反复遇到过的,按「现象 → 原因 → 解决」的顺序写,你照着排就行。
4.1 ClassNotFoundException:驱动 jar 没进 WEB-INF/lib
现象:Tomcat 一启动,控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,但项目在 IDE 里编译完全正常。
原因:前面提过,编译期和运行期的 classpath 不是一回事。IDE 的 Build Path 只保证代码能通过编译,Tomcat 实际加载类时,只会扫描 WEB-INF/lib 和 WEB-INF/classes。jar 没放对位置,运行时自然找不到驱动类。
解决:把 mysql 驱动 jar 复制到 WEB-INF/lib 下,然后在 IDE 里刷新项目,重新发布,重启 Tomcat。还没解决的话,检查是不是复制到了 src 目录下、类路径被 IDE 缓存住——右键项目 → Clean 之后再重启,基本能好。这个错误排掉后,后面还有一长串和数据库相关的报错,所以把它放在第一条。
4.2 中文全是问号:url、数据库排序规则、页面编码三层都要对齐
现象:页面显示正常,但往数据库里插入中文后,表里存的是??;或者反过来,页面上的中文全是乱码。
原因:字符集没对齐。这套链路里任何一环编码不一致,中文就会出问题——JSP 页面的pageEncoding、Servlet 的请求编码、JDBC URL 里的characterEncoding、数据库表和字段的排序规则,四个环节缺一不可。
解决:按三层逐一排查。数据库层面,建库时指定DEFAULT CHARACTER SET utf8mb4,已存在的表用ALTER TABLE t_book CONVERT TO CHARACTER SET utf8mb4;转换。JDBC 层面,URL 里加上characterEncoding=utf8。代码层面,Servlet 顶部加request.setCharacterEncoding("UTF-8"),JSP 顶部确认有pageEncoding="UTF-8"。另外注意,用 Navicat 查看数据时也把连接编码设成 UTF-8,否则数据库里是对的,工具显示成乱码,白排查半天。
4.3 Servlet 路径 404:注解映射与 web.xml 到底以哪个为准
现象:点页面上的链接,地址栏 URL 看起来正确,但 Tomcat 返回 404。检查@WebServlet("/listBook")没问题,web.xml 里也有对应的<servlet-mapping>。
原因:项目同时用了注解和 web.xml 两套配置,或者 web.xml 中配置了一个旧路径,注解配置的是新路径,实际生效的是 web.xml 里那份。还有一种情况是链接地址写死了/listBook,而项目部署名不为空,比如/bookmanager/listBook才能访问,少了项目名就是 404。
解决:统一用一种配置方式,推荐注解,因为省事且直观。访问路径统一用request.getContextPath()拼接,或者在 JSP 里写${pageContext.request.contextPath}/listBook。如果确定用的是 web.xml,就检查<url-pattern>的写法,注意 Servlet 的<servlet-name>要和<servlet-mapping>里完全一致,大小写都不能差。
4.4 MySQL 8.0 连不上:useSSL=false 与 serverTimezone 缺一不可
现象:控制台报Establishing SSL connection without server's identity verification is not recommended警告,紧跟着是The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,驱动类已经改成com.mysql.cj.jdbc.Driver了还是不行。
原因:MySQL 8.0 的 JDBC 驱动默认要求 SSL 校验和时区明确指定。本地开发连的是没有配置 SSL 的 MySQL 服务,而时区没有指定时,驱动会读取系统默认时区,一旦不是驱动认识的格式就抛异常。这个报错两个坑叠在一起,排查时容易顾此失彼。
解决:在 URL 里同时加上两个参数,一个都不能少:
url=jdbc:mysql://localhost:3306/bookdb?useSSL=false&serverTimezone=Asia/ShanghaiuseSSL=false是本地开发的标准写法,生产环境另说;serverTimezone=Asia/Shanghai指定东八区。如果还是报错,看下 MySQL 服务时区:命令行执行SELECT @@global.time_zone;,返回 SYSTEM 的话可以执行SET GLOBAL time_zone = '+08:00';再重启服务,两者取其一即可。
4.5 改了数据库连不上旧表:脚本版本与库结构不同步
现象:系统能登录,但点进某个功能页面报 SQL 语法错误或者Unknown column 'xxx' in 'field list'。你的数据库是重新导入的,源码是最新的,对不上。
原因:压缩包里的 SQL 脚本和源码不是同一个版本。源码里的 DAO 层写了某个字段,而脚本里建的表还没有这个列。这类项目经常改版,源码更新了,脚本回溯不完整,就会出现「代码跑得比表结构快」的情况。数据库同步工具只能保证数据一致,保证不了表结构和代码版本的匹配,尤其当你手动改过表之后更容易踩中。
解决:以源码为准反推表结构。打开报错对应的 DAO 类,把里面用到的所有字段列出来,对照数据库表结构,缺哪列补哪列:
ALTER TABLE t_borrow ADD COLUMN return_time DATETIME DEFAULT NULL;改完之后,把这张表的结构导出留存。以后每次跑新代码,先对比一次表结构,避免重复踩。做课设答辩前,强烈推荐用新库重新执行一遍完整脚本跑通全流程——这是检验脚本完整性的唯一标准,别拿改过的旧库当验证环境。
4.6 Tomcat 启动端口被占用:先杀进程还是先改端口
现象:启动 Tomcat 时控制台报Port 8080 required by Tomcat v9.0 Server at localhost is already in use,服务起不来。
原因:8080 端口被占用了。占用它的可能是另一个 Tomcat 实例,也可能是其他软件——之前遇到过一次是某些数据库同步工具的 Web 管理端默认占了 8080,排查时根本想不到。
解决:先确定是谁占的,再决定杀还是改。命令行执行:
netstat -ano | findstr 8080 taskkill /PID 占用的PID /F如果是残留的 Java 进程,杀掉重开就行。要是这台机器上有多个项目经常要跑,或者端口被系统服务占用不便杀,就改 Tomcat 端口——在 IDE 的 Server 配置里改 HTTP port,或者在 Tomcat 安装目录conf/server.xml里改<Connector port="8080">。注意 IDE 里改的和 server.xml 里改的要保持一致,否则 IDE 会报端口不一致的警告。
5. 把「能跑」变成「能答辩」:三条验证路径与一个备份习惯
5.1 按「读者 + 管理员」双角色验证功能闭环
图书管理系统通常有读者和管理员两种角色。不要只验证管理员能登录、能加书就收工,要把读者的完整链路也走一遍。读者能做的事一般包括:检索图书、查看图书详情、借书、还书、查看个人借阅记录——这里面「查看个人借阅记录」就是典型的 JSP 个人信息展示页面,也是评委喜欢追问的地方。
验证时按业务场景走,而不是按菜单点:读者 A 借了一本书 → 库存减一 → 读者 A 的借阅列表出现记录 → 还书后记录闭合,库存加一。每一步都要回到数据库确认数据真的变了,而不是只看页面提示「操作成功」。很多项目页面好看,数据对不上,一问细节就露馅。
5.2 用一组 SQL 清单验证数据完整性与关联关系
页面验证属于黑盒,还得用 SQL 做白盒检查。设计好场景后,执行下面这类查询来核对结果:
-- 检查每张表的记录数是否和页面显示一致 SELECT COUNT(*) FROM t_book; SELECT COUNT(*) FROM t_user; SELECT COUNT(*) FROM t_borrow; -- 检查借阅记录和图书/用户的关联是否完整 SELECT b.book_name, u.username, br.borrow_time, br.return_time FROM t_borrow br JOIN t_book b ON br.book_id = b.id JOIN t_user u ON br.user_id = u.id;重点看三件事:关联查询不报错、没有冗余数据、时间字段格式正确。如果借阅表里出现了图书表不存在的 book_id,说明借书时没有做外键约束或者代码里没校验,这类逻辑漏洞答辩时很容易被问出来。提前用 SQL 查一遍,比现场手忙脚乱翻代码强得多。
5.3 每次改动都留「后悔药」:版本化 SQL 脚本
开发的习惯决定了项目能不能长期维护。我的做法是每次对数据库有结构变更,就导出一份新的 SQL 脚本,文件名带上版本号,比如bookdb_v1.0.sql、bookdb_v1.1.sql。用的是 Navicat 或 mysqldump 都可以:
mysqldump -uroot -p --databases bookdb > bookdb_v1.1.sql导出后,在项目说明里追加一小段变更日志,写上改了什么表、加了什么字段、为什么改。这样哪怕后面改崩了,也能随时回滚到上一个可用版本。这个习惯在课设阶段看不出多大价值,等代码量上来、要同时维护多套环境时,它就是你的后悔药。
这几条都做到之后,这个项目就不只是「能跑」,而是能经得起追问、能拿得出手。我每次做完一个 JavaWeb 项目,都会强迫自己走一遍双角色流程、跑一遍 SQL 校验、导一份带版本号的脚本备份,这套流程虽然琐碎,但帮我避开过不少答辩现场的尴尬时刻。希望你也能在动手之前先把这几点沉淀成习惯,希望帮到你。
本文还有配套的精品资源,点击获取