
简介基于ServletJSP的图书管理系统是一份面向Java Web初学者的完整项目源码实现了图书信息的添加、删除、修改、查询等核心功能并配有数据库SQL脚本和说明文档帮助开发者理解Servlet与JSP的分工协作及典型Web应用架构。压缩包共419个文件大小约14.91MB包含24个Java源文件、14个JSP页面、18个CSS样式、6个JS脚本、13个XML配置、SQL建表脚本及jar依赖包等可直接导入IntelliJ IDEA并配置Tomcat运行。已有2216人学习下载适合作为课程设计、毕业设计或自学练手项目。通过阅读源码与部署调试读者可掌握JDBC数据库操作、jQuery Ajax异步交互、前端表单校验以及项目目录结构规范结合readME中的环境配置与运行截图能快速跑通整个项目为后续开发更复杂的企业级Java Web应用打下基础。1. 项目概述1.1 这个项目解决了什么问题图书管理系统我在接触Java Web开发的第一年就做过这个项目。后来在GitHub上看到各种版本的图书管理系统有Spring Boot的、有SSM的但基于ServletJSP的这套方案反而在学习和教学场景里用得最多。这个项目的核心价值在于它不需要任何框架完全靠Java原生技术栈就能跑起来。Servlet负责接收前端请求、调用业务逻辑、控制页面跳转JSP负责渲染动态页面配合JDBC操作MySQL数据库就是一套完整的Java Web应用。说白了这就是Java Web开发的地基——Spring MVC的请求分发逻辑、MyBatis的ORM思想、前端控制器的设计模式追根溯源都能在这套ServletJSP方案里找到影子。适合谁来学我建议三类人群重点看在校学生尤其是计算机软件相关专业课程设计、毕业设计经常会选这个题目准备参加Java Web相关岗位面试的候选人搞懂这套交互流程面试里聊框架的原理部分就不虚想手工从零搭建一个Web应用、理解HTTP交互本质的开发者。我在这篇文章里不会只贴一堆代码了事而是把整个项目从设计思路、环境搭建、代码实现到部署排错完整梳理一遍重点写清楚每个环节“为什么要这么做”以及实测中容易踩的坑。1.2 我打算怎么讲这个项目整个项目的完整代码从DAO到Servlet到JSP页面保守估计在两三千行上下。如果按照技术模块一个个写读者很容易看懵。我的计划是分四步走第一步讲整体架构设计搭建好思路框架第二步把核心的开发流程走一遍从建表到写DAO到写Servlet到写JSP页面第三步把登录、图书增删改查、分页这几个核心功能实现拆开讲第四步分享我在实际开发中遇到的高频问题和排查思路。看完这篇文章你应该能独立完成一个基于ServletJSP的图书管理系统并且对Java Web的开发模式有一个系统的认知。2. 整体方案设计2.1 为什么选 Servlet JSP而不是 Spring Boot现在网上到处都是Spring Boot的教程很多学生一上来就Spring Boot MyBatis-Plus做完图书管理系统却连请求是怎么进到Controller的都不清楚。把框架换成Thymeleaf模板页面逻辑就不知道怎么写了。这就是基础不牢的表现。Servlet JSP组合的技术栈非常轻量依赖的东西就三样Tomcat服务器、JDBC驱动、JSTL标签库。它把Java Web开发的核心链路完整暴露出来——请求进来Servlet容器解析HTTP请求调用service()方法通过doGet/doPost分发写业务代码再通过请求转发或重定向把结果交给JSP渲染。这套流程你亲手走一遍再回头理解Spring MVC的DispatcherServlet、HandlerMapping那套机制就顺理成章了。另外还有一个很现实的原因很多学校的Java Web课程和毕业设计仍然用Servlet JSP做考核要求有的老师甚至明确要求不能用框架。已经工作的朋友如果接手老系统也会碰到一些用XML配置Servlet的遗留项目。2.2 三层架构设计从请求到响应的完整链路我做这个项目用的是最经典的JSP Servlet DAO三层结构表示层JSP页面负责数据的展示和用户交互不写Java业务逻辑只用EL表达式和JSTL标签库来遍历数据、做条件判断控制层Servlet接收请求参数调用业务逻辑设置request作用域数据完成页面转发或重定向数据访问层DAO专门负责和数据库打交道通过JDBC执行SQL语句把ResultSet结果集转换成Java对象。这三层之间是严格的单向依赖JSP只依赖Servlet设置的数据Servlet只依赖DAO接口DAO只依赖数据库连接配置。如果把业务逻辑写在JSP里比如在JSP页面中直接new一个DAO对象查数据库系统前期跑得没问题等你想把JSP换成FreeMarker模板引擎时这些逻辑全部作废相当于整层重写。我写的分层代码具体是这样划分的src/main/java ├── com.library.dao # 数据访问层 │ ├── BookDAO.java │ ├── AdminDAO.java │ └── DBUtil.java # 数据库连接工具类 ├── com.library.entity # 实体类 │ ├── Book.java │ └── Admin.java ├── com.library.servlet # 控制层 │ ├── LoginServlet.java │ ├── BookListServlet.java │ ├── BookAddServlet.java │ ├── BookUpdateServlet.java │ └── BookDeleteServlet.java └── com.library.filter # 编码过滤器 └── EncodingFilter.java webapp ├── WEB-INF │ ├── web.xml # Servlet映射配置 │ └── jsp │ ├── login.jsp │ ├── book_list.jsp │ ├── book_add.jsp │ └── book_update.jsp ├── static │ ├── css │ └── js └── index.jsp我特意把JSP页面放在WEB-INF目录下这是不安全的隐患点。如果把JSP直接放在webapp根目录访问者的浏览器地址栏输入localhost:8080/你的项目名/login.jspTomcat会直接解析该页面。如果这个页面里没有JavaBean校验看得到页面不过问题不大。但如果放在WEB-INF下用户就无法直接通过URL访问该路径必须要靠Servlet转发进入多了一层安全控制。2.3 环境选型与版本搭配照我这个配置不折腾技术选型的时间我一般控制在半小时内没必要在这一步反复纠结。推荐一套我已跑通多次的环境组合组件推荐版本说明JDKJDK 81.8最稳定的Java版本兼容性最好TomcatTomcat 8.5 或 9.0支持Servlet 3.1/4.0规范IDEEclipse IDE for Enterprise Java 或 IDEA都行顺手就好数据库MySQL 5.7 或 8.05.7稳定8.0要配置时区稍微注意数据库驱动mysql-connector-java 5.1.495.7 / 8.0.x8.0版本要和MySQL对应JSTLjstl 1.2支持EL表达式和标签库这里有一个非常容易踩的坑JDK版本不要一味追求新。我用JDK 11配置Tomcat 9遇到过模块化访问权限的问题需要额外修改catalina.properties文件很麻烦。JDK 8配Tomcat 8.5是最省心的组合如果你不是做新特性开发没必要给自己找麻烦。3. 环境准备与数据库搭建3.1 五分钟搭好开发环境开发环境的搭建步骤比较固定我按顺序说一遍大家照着做就行。装好JDK后记得在系统环境变量里配置JAVA_HOME和PATH这个如果不配置Tomcat启动时找不到Java环境。然后在命令行输入java -version能看到版本信息就说明装好了。Eclipse建议选择Eclipse IDE for Enterprise Java Developers因为Java EE版本自带Web开发插件新建Dynamic Web Project时不用再去装额外插件。在Eclipse中配置Tomcat时Server Runtime Environment要选对对应的Tomcat版本。IDEA社区版其实也能做Java Web开发但配置上比Eclipse稍繁琐需要手动添加Application Server新手更容易卡在这一步。数据库这边装好MySQL后用Navicat或命令行创建数据库CREATE DATABASE IF NOT EXISTS library_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library_system;字符集我一般用utf8mb4比utf8多支持一些特殊字符而且和现在的前后端交互编码处理好搭配。Windows下MySQL 8.0如果连接时报告Public Key Retrieval is not allowed原因在于驱动版本和MySQL密码加密方式不兼容。有两个解决办法把驱动升级到mysql-connector-java 8.0.13以上或者在JDBC连接串中加上allowPublicKeyRetrievaltrue参数。3.2 建表语句和初始数据图书管理系统至少需要两张表管理员表和图书表。管理员表负责登录认证图书表存图书的核心字段。CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), price DECIMAL(10, 2), stock INT DEFAULT 0, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO admin (username, password) VALUES (admin, 123456);密码我这边直接明文存了实际生产环境起码要用MD5加盐处理。但你做课设和毕设的时候老师更关注的是系统流程是否跑通了这块不会深究。不过你可以在文档里加一句“密码存储可采用MD5加密”显得你考虑得比较全面。3.3 JDBC连接工具类的编写DBUtil是整个系统的底盘没有它后面的DAO层全是空中楼阁。我写的工具类会完成两件事加载MySQL驱动获取数据库连接。package com.library.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/library_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你自己的密码; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这里有两个地方值得注意。第一旧版本的驱动类是com.mysql.jdbc.Driver从驱动8.0开始改成了com.mysql.cj.jdbc.Driver用错会直接报ClassNotFoundException。第二连接URL中必须带上useSSLfalse不然MySQL会提示SSL连接警告虽然在控制台只是黄色警告但看着膈应而且某些环境下会导致连接报错。4. 核心功能实现登录、CRUD和分页4.1 登录功能的ServletJSP实现流程登录功能是所有Web系统的标配它的完整链路能帮助你理解请求是怎么流转的。用户在login.jsp页面输入用户名密码点击提交按钮表单以POST方式提交到LoginServlet。我在项目中用web.xml方式配置Servlet映射而不是用注解方式因为很多老项目和教材中依然采用这种配置方式。web.xml里这样配servlet servlet-nameLoginServlet/servlet-name servlet-classcom.library.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping注意url-pattern是/login不是/loginServlet。这个路径对应表单里action的取值——比较正规的写法是这样form action${pageContext.request.contextPath}/login methodpost input typetext nameusername placeholder用户名 required input typepassword namepassword placeholder密码 required button typesubmit登 录/button /form接下来看LoginServlet里的代码逻辑WebServlet(name LoginServlet, urlPatterns {/login}) public class LoginServlet extends HttpServlet { private AdminDAO adminDAO new AdminDAO(); 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); Admin admin adminDAO.findByUsernameAndPassword(username, password); if (admin ! null) { HttpSession session request.getSession(); session.setAttribute(adminInfo, admin); session.setMaxInactiveInterval(1800); // 半小时过期 response.sendRedirect(request.getContextPath() /bookList); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/WEB-INF/jsp/login.jsp).forward(request, response); } } }这里我用了WebServlet注解这是Servlet 3.0的特性省掉了web.xml里的一部分配置。不过搜索热词里专门提到“使用web.xml方式编写servlet的完整示例”说明不少同学的项目模板还是用XML配置的。两种方式都能跑注解更简洁XML更传统、在某些框架整合场景下仍不可替代你至少要能看懂XML写法。为了防止有些教程里混用两种方式导致冲突我的建议是你自己写的Servlet可以统一用注解但WEB-INF下的web.xml也要保留基本配置比如欢迎页面、Session过期时间、编码过滤器配置这些依然需要web.xml来完成。登录成功后我把管理员信息放进了Session。为什么是Session而不是request因为request作用域在一次请求结束后就销毁了跳转到bookList列表页后原来那条request对象已经不复存在。而Session保留了整个会话期间的数据后续页面还需要判断“当前有没有人登录”来拦截未登录用户。在JSP页面里判断当前用户是否已登录这样写c:if test${empty sessionScope.adminInfo} scriptlocation.hreflogin.jsp/script /c:if4.2 图书列表展示与新增图书实现图书列表页是整个系统的门面我用JSTL标签库配合EL表达式来遍历图书数据。先看Servlet侧WebServlet(/bookList) public class BookListServlet extends HttpServlet { private BookDAO bookDAO new BookDAO(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ListBook bookList bookDAO.findAll(); request.setAttribute(books, bookList); request.getRequestDispatcher(/WEB-INF/jsp/book_list.jsp).forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }然后在JSP页面用c:forEach循环输出。这里有个细节pageContext.request.contextPath是JSP EL表达式里获取项目根路径的标准写法用它拼接CSS、JS路径前端资源目录结构调整后就不容易出问题。新增图书的表单提交到BookAddServletServlet里做两件事从请求中取出表单字段组装成Book对象调用DAO的add方法插入数据库。插入成功后我用的重定向方式——response.sendRedirect(bookList)。这就是Post/Redirect/Get模式。如果使用forward跳转用户按F5刷新页面浏览器会重复提交上一次的POST请求造成重复插入数据。重定向是浏览器重新发起了一次GET请求就不会触发重新提交。4.3 编辑与删除图书的细节处理编辑功能有两种处理思路。一种是把编辑和新增共用一个Servlet通过是否有id参数来区分另一种是拆成两个Servlet。从代码清晰角度我推荐拆成两个。修改操作我分两步第一步BookEditServlet根据id把原数据查出回显到编辑页面第二步BookUpdateServlet接收表单修改后的数据执行UPDATE语句。修改图书表里对应的记录注意一定要用PreparedStatement的setInt方法来设置id。如果直接把前端传来的id拼接到SQL语句里会存在SQL注入风险。我在代码里完整演示了PreparedStatement的用法public boolean updateBook(Book book) { String sql UPDATE book SET book_name?, author?, publisher?, price?, stock? WHERE id?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, book.getBookName()); ps.setString(2, book.getAuthor()); ps.setString(3, book.getPublisher()); ps.setBigDecimal(4, book.getPrice()); ps.setInt(5, book.getStock()); ps.setInt(6, book.getId()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }每条SQL都用PreparedStatement预处理好处不必多说。Java Web课设里如果因为字符串拼接SQL导致数据库被注入老师一看心里就有数。删除操作我用的GET方式URL长这样bookDelete?id3。有些同学稀里糊涂写成POST请求页面提交后报405错误。GET和POST都要在Servlet里重写对应方法或者让doPost方法调用doGet逻辑不然Tomcat会返回405 Method Not Allowed错误。4.4 分页功能让列表页告别三层楼那么长图书一多全部塞在一个页面上既难看又影响性能。分页是必做功能。我的分页思路是三个参数当前页码pageNum、每页条数pageSize、总记录数totalCount。public ListBook findByPage(int pageNum, int pageSize) { String sql SELECT * FROM book ORDER BY id LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); // 执行查询并组装ListBook返回 } catch (SQLException e) { e.printStackTrace(); return null; } }LIMIT子句的第一个参数是偏移量pageNum1时偏移0pageNum2时偏移10公式是(pageNum - 1) * pageSize这个是分页功能最核心的地方。然后还要有一个方法计算总页数——总页数 向上取整(count / pageSize)。public int getTotalPage(int pageSize) { String sql SELECT COUNT(*) FROM book; // 执行查询返回总记录数 // 总页数 (totalCount pageSize - 1) / pageSize }JSP页面底部加上翻页导航。上一页的链接是pageNum - 1下一页是pageNum 1同时做边界判断。补边界判断时注意三点第一页不显示“上一页”最后一页不显示“下一页”pageNum小于1时强制变为1。我实测下来每页10条数据、总数据量几百本这个场景这种手写分页性能完全够用。页码导航里要注意URL编码问题参数从JSP拼接时不要漏掉项目路径。5. 项目运行与常见报错排查5.1 启动Tomcat部署项目的两种方式方式一在Eclipse/IDEA里右键项目 → Run As → Run on ServerIDE帮你完成部署开发调试最方便。方式二把项目打成war包放到Tomcat的webapps目录下启动Tomcat自动解压部署。两种方式各有适用场景。开发阶段推荐方式一它支持热部署和断点调试。最终演示或交付时建议方式二它更接近实际生产环境的部署方式。注意打war包之前要先把项目里的测试数据清理一下。访问路径一般是http://localhost:8080/项目名/。如果首页配置了欢迎页面直接输入项目名就能跳转。我在web.xml里配置的是welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list5.2 高频问题排查速查表这里我把自己做项目时常见的报错整理成一张速查表每一类都亲自踩过报错现象可能原因解决方案404访问Servlet报错url-pattern拼写错误、项目未正确部署检查web.xml/注解中的路径清理Tomcat重新部署500 ClassNotFoundExceptionmysql驱动jar没放进WEB-INF/lib把mysql-connector-java包复制到WEB-INF/lib下中文乱码请求参数POST请求没设置编码过滤器配置CharacterEncodingFiltervalue设为UTF-8中文乱码页面输出页面contentType没指定UTF-8JSP顶部写pageEncodingUTF-8 contentTypetext/html;charsetUTF-8数据库连接失败密码错误、驱动版本不匹配、时区问题检查DBUtil的URL和密码8.0驱动加serverTimezone参数JSP页面内容没有更新Tomcat缓存了旧class文件强制刷新、Clean Tomcat工作目录或重启TomcatJSTL标签显示为源码JSTL jar没有引入添加jstl-1.2.jar 到 lib 目录5.3 排查思路和避坑建议我给你的排错建议先看Tomcat日志再看浏览器开发者工具。Tomcat的localhost.log日志通常已经告诉你发生了什么异常比如Servlet类路径写错了、SQL语句报错了、端口被占用了等。浏览器开发者工具的Network面板能看到前端请求的状态码404还是500一目了然直接缩小排查范围。有一个经验的坑我要特别提醒修改了Servlet类之后Tomcat经常不会自动重新加载一定要手动右键项目 → Clean再重启Tomcat。这比代码写错更让人抓狂很多同学改了半天代码没反应一重启服务器才发现是没部署成功。搜索词里提到“jsp改了不生效”多半也是这个原因。Eclipse里还有一项Server Options里的“Automatically publish when resources change”我建议勾选上可以省掉部分手动发布操作。数据库连接池这个点如果你有闲心可以试试把DBUtil换成c3p0或Druid连接池。同样的代码结构只是把获取连接的方式换一下对系统的抗并发能力会有明显提升。这也是评审老师比较看重的一个加分点。6. 功能扩展建议与实践体会6.1 从课设到完整项目的三个扩展方向图书管理系统做出来后如果你想把它进一步打磨成可以写进简历的项目我建议从以下几个方向入手第一个方向是引入Maven构建。当前的项目结构是Eclipse动态Web项目的原始形态换成Maven后依赖管理变得极其轻松——mysql驱动、jstl、servlet-api放到pom.xml就行不用再到处找jar包。把自己的项目改造成Maven项目的过程中你对依赖冲突、构建生命周期的理解会明显上个台阶。第二个方向是把图书借阅的完整流程做出来。很多图书管理系统都只做了图书的增删改查缺少借书、还书、借阅记录、逾期管理这些核心业务。把借阅流程加进去系统的业务复杂度立刻提升你需要在多个表之间做事务处理这对积累业务设计经验很有帮助。第三个方向是前端交互升级。JSP页面默认比较朴素你可以引入Bootstrap或者Layui把界面打磨一下但需要注意引入外部CSS/JS的资源文件名和路径要保持规范不然JSP页面的相对路径容易出错。重点不是你要做得多好看而是要学会如何在JSP中管理静态资源和动态数据区的分工协作。6.2 实际做这个项目我学到的三件事第一件事不要提前去套模板抄代码。我以前也买过毕业设计的整套源码最后发现项目跑起来是没问题可是一旦老师追问某个类的作用、某段逻辑的意图我就支支吾吾说不清楚。做技术项目理解永远比代码本身重要。你自己敲一遍踩一遍坑面试时才能理直气壮地说这是自己做的东西。第二件事写日志一定要从第一天就开始。做课设不用log4jSystem.out.println也能凑合但你至少要把中间结果打印出来。比如登录失败时打印一下SQL语句和传入的参数连接数据库时打印一下URL——排查问题的速度直接翻倍。第三件事文末再提醒一个容易忽略的点Session失效时间。很多同学的图书管理系统做完了发现登录一段时间不操作页面突然跳回登录页就说系统有Bug。其实这就是Session过期了。这是一种安全机制反而是加分项。如果你想在体验上更友好可以在JSP页面加上Session超时自动跳转的JS提示但后台校验的逻辑不能缺——页面跳转只是表象每次请求到Servlet时都要校验Session是否有效这才是真正的防护。说到底基于ServletJSP的图书管理系统它不只是一个课设题目更是通往Java Web框架世界的一扇门。把这一步走扎实了以后学Spring Boot、Spring Cloud你会发现自己比直接上手框架的人看得透彻得多。这个项目我前后迭代了三个版本每次重写都有新的体会。这次把踩坑和经验整理出来希望能帮你少走一些弯路。本文还有配套的精品资源点击获取