
简介基于jspmysqlservlet的JSP图书馆图书管理系统源码包面向Java Web初学者及需要完成课程设计或毕业设计的在校学生。系统包含管理员、游客、学生三类角色覆盖管理员登录、用户/图书管理、罚款缴纳学生借阅、归还、借阅记录查看、密码修改以及游客浏览图书等核心业务流程可作为理解ServletJSP请求处理与数据交互的完整练习项目。资源为zip压缩包共112个文件其中包括45个Java源码文件与24个JSP页面文件构成控制器、DAO、工具类等分层结构另有37张PNG图片和2个GIF图用于界面装饰2个MD说明文档与1个PDF可辅助阅读。整体仅1.76MB结构紧凑适合快速下载部署与对照学习。该源码已吸引263人浏览学习包内包含登录校验、增删改查、借阅归还等典型功能模块可帮助读者了解Java Web项目从页面展示到数据库交互的完整流程是一套能运行、易改写的实战素材。1. JSP 图书管理系统课设与毕设都绕不开的 Java Web 整合样板如果你正在做 Java Web 课程设计或者接手别人的老项目大概率绕不开这个组合JSP Servlet MySQL。这套图书管理系统就是典型代表——管理员管书、管用户、收罚款学生借书还书看记录游客翻翻馆藏角色边界清清楚楚代码结构也是早期 Java Web 项目最常见的 Servlet DAO JSP 三层写法。它能解决的不只是「交一份课设」而是让你在还不太熟悉 Spring 的时候把 Servlet 生命周期、请求转发与重定向、JDBC 访问、Filter 鉴权这些基础功一遍过手。适合的人群很明确Java Web 刚学完理论想动手整合的在校生以及需要快速捡起传统 JSP 项目维护技能的从业者。这份源码我拆解完的感受是功能不算多但作为练手和改写底子信息量足够实在。2. 系统结构与角色权限先看清代码怎么组织再谈改哪里拿到一个 JSP 老项目第一件事不是急着跑起来而是先把目录结构和角色权限捋清楚。这个系统从功能描述来看是三种角色三类权限管理员管全局学生操作借还游客只读浏览。对应的代码组织也有明确分层下面拆开讲。2.1 三层结构怎么落的Servlet 做控制、JSP 做展示、DAO 做数据这套系统的代码组织方式是典型的 JSP Model 2 变体。控制层由BookServlet、UserServlet、LoginServlet、CodeServlet这些类承担它们接收 HTTP 请求调用业务方法最后通过转发或重定向把结果交给 JSP 页面。JSP 只负责渲染不在页面里写 JDBC 代码。数据访问层集中在 DAO 实现类比如BorrowDaoImpl负责跟 MySQL 打交道。LoginFilter是贯穿全局的过滤器作用是做登录态校验。这个设计在课设里很常见但也是很多新手第一个翻车点——Filter 不生效往往不是代码问题而是 web.xml 或注解配置没配对。后面避坑章节我会专门展开。为什么这个结构值得细看因为它的边界清晰Servlet 里不写 SQLJSP 里不写 Java 流程控制DAO 里不做页面跳转。你改一个功能点时知道该动哪个文件。比如要给图书表加个 ISBN 字段改动路径是数据库表 → 实体类 → DAO 接口与实现 → Servlet 参数接收 → JSP 表单顺序非常机械。2.2 三个角色对应的功能点用一张权限表说清边界角色可操作功能涉及核心文件管理员登录、用户管理、图书管理、罚款缴纳LoginServlet、UserServlet、BookServlet学生登录、图书借阅、图书归还、借阅记录查看、密码修改BookServlet、BorrowDaoImpl游客查看所有图书、查看图书详情BookServlet只读分支游客不需要登录就能访问图书列表和详情页这说明系统里至少有一个不受LoginFilter拦截的路径放行规则。学生和管理员都涉及登录但登录后的跳转目标不同LoginServlet里一般会通过用户角色字段做分支判断。罚款缴纳这个功能点比较有意思它通常挂在借阅记录上逾期未还生成罚款记录管理员确认收款后标记已缴。BorrowDaoImpl里应该能看到相关的 update 语句。权限边界决定你改代码时的安全范围。比如你想让游客也能借书那就不是改一个按钮的问题而是要同时动过滤器放行规则、Servlet 的角色判断、JSP 页面的按钮渲染条件。这个系统的角色划分刚好适合做这类改造练习。2.3 工具类的作用ReflectionUtils 和 ImgUtils 不是摆设源码清单里的ReflectionUtils.java值得单独说。它一般是做反射封装用的——把 ResultSet 的数据自动映射到实体对象省去手写一堆rs.getString(xxx)的重复代码。这种工具在 DAO 层出现说明作者有意识减少样板代码。你可以自己看一下它的实现大概率是通过反射遍历实体类的字段名再按字段名从 ResultSet 里取值。ImgUtils.java和CodeServlet.java配合使用用来生成验证码图片。验证码在登录页很常见但这里面有个坑验证码图片不显示十有八九是ImgUtils里用了BufferedImage时没有正确设置响应头Content-Type或者图片输出流没关闭。这两个工具类你改系统时大概率会动到先看懂它们能让后续改造顺手很多。3. 环境搭建与部署全过程从 JDK 到 Tomcat 再到 war 包这套系统要跑起来环境组合是固定的JDK 8、Tomcat 8 或 9、MySQL 5.7 或 8.0、IDEA 或 Eclipse。下面是我实际部署时走的完整流程每一步都有参数说明。3.1 数据库准备建库建表与导入初始数据先启动 MySQL 服务然后在命令行或 Navicat 里执行建库语句。一般项目里会附带 SQL 文件没有的话就要手动创建。我建议用命令行方式确认连接mysql -u root -p # 输入密码后进入 MySQL 终端 CREATE DATABASE IF NOT EXISTS library_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE library_db; SOURCE /path/to/library.sql;逻辑说明第一步创建数据库指定utf8mb4字符集是因为图书管理系统里可能有中文书名、作者名utf8 在老版本 MySQL 里对四字节字符支持不完整utf8mb4更稳妥。第二步执行 SQL 脚本自动建表并写入初始数据。参数说明DEFAULT CHARACTER SET utf8mb4决定了表和字段的默认字符集如果你后续要用LIKE查询中文书名字符集不统一会导致查不到。COLLATE utf8mb4_general_ci是排序规则ci表示大小写不敏感。连接数据库时注意账号权限。如果 MySQL 8.0 版本默认认证插件是caching_sha2_password老版本的 JDBC 驱动5.x连不上要么换驱动到 8.x要么在 MySQL 里改账号插件ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;这段命令只在 MySQL 8 且使用旧驱动时才需要执行。如果你直接用 mysql-connector-java 8.x 驱动就不用改认证插件。这个坑我在避坑章节还会展开讲一次因为它在部署环节出现频率实在太高。3.2 IDEA 导入项目与 JDK/Tomcat 配置打开 IDEA选择File → New → Project from Existing Sources选中源码根目录Maven 项目选 Maven 导入非 Maven 直接选Web类型。这个系统如果是纯 Servlet 项目大概率没有pom.xml全靠 WEB-INF/lib 下的 jar 包支撑导入后要检查项目的Project Structure。# 检查 JDK 版本 java -version # 检查 Tomcat 版本目录 ls /path/to/tomcat/lib关键点项目使用的 JDK 版本要和 IDEA 里Project Structure → Project → SDK一致否则编译报错。Tomcat 在 IDEA 里配置时Application Server选你本地解压的 Tomcat 目录Deployment标签页里Application context一般填/或/library。如果填了/library所有 JSP 页面里涉及的路径都要带/library前缀不然页面跳转会 404。运行配置建议设置JAVA_OPTS-Dfile.encodingUTF-8这段参数保证 Tomcat 运行时 JVM 的默认字符集是 UTF-8避免 JSP 页面中文乱码。很多乱码问题不是页面编码没写而是容器启动参数没指定。3.3 传统 JSP 项目打包 war 并部署到独立 Tomcat课设交作业要稳定演示或者要把项目放进 Linux 服务器我更建议直接打 war 包不依赖 IDEA 的嵌入式 Tomcat。IDEA 里Build → Build Artifacts → 选择 war 包或者用 Maven 的package命令mvn clean package -DskipTests # 非 Maven 项目直接用 IDEA Build Artifact 生成 war生成后把图书馆管理系统.war复制到 Tomcat 的webapps目录启动 Tomcat 后会自动解压。访问路径是http://localhost:8080/图书馆管理系统/ 或 http://localhost:8080/项目目录名/这里最容易踩坑的是项目名带中文或空格导致 URL 编码问题。我的习惯是打成 war 包前手动改名为library.war干净省事。如果 Tomcat 启动后报端口占用# Linux / macOS 查看端口占用 lsof -i:8080 # Windows 查看端口占用 netstat -ano | findstr 8080找到占用进程后杀掉或者修改 Tomcat 的conf/server.xml里Connector port8080改为 8081 等空闲端口。传统 JSP 项目打包 war 的坑在于web.xml 的配置版本要与 Tomcat 兼容——web.xml 头是 3.1 版本声明Tomcat 7 解析会报错需要降级到 3.0 或换 Tomcat 8。3.4 数据库连接参数JDBC URL 与账号密码的配置位置传统 JSP 项目的数据库连接配置常见于两种地方WEB-INF/classes/db.properties或直接在 DAO 实现类里硬编码。这个系统的连接信息在哪你打开BorrowDaoImpl.java或类似的 DAO 类看一眼就知道。通常长这样private static final String URL jdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456;逻辑说明useSSLfalse是因为本地开发环境不需要 SSL 加密连接加了反而可能因为证书问题连接失败serverTimezoneAsia/Shanghai是 MySQL 8.x 驱动必填项不填会报时区异常characterEncodingutf8保证 JDBC 传输过程中文字编码一致。参数说明library_db必须和你在 MySQL 里创建的库名一致USER和PASSWORD改成你本机的 MySQL 账号。连接失败时先拿这个 JDBC URL 直接跑一个独立 Java 类测试判断是驱动问题还是数据库权限问题比在 Tomcat 里反复重启看日志快得多。我个人的习惯是写一个带main方法的测试类专门验证连接字符串是否正确通过后再启动 Web 应用。4. 核心代码走读从登录到借书的关键链路部署跑通后接下来值得做的事是读懂几条关键链路的代码实现。别拿到源码就想着改界面先把登录、验证码、过滤器、借还书这几条主路径摸熟后面加功能才知道往哪接。4.1 登录接口与验证码校验LoginServlet 的执行顺序登录流程一般是页面提交用户名、密码、验证码 →LoginServlet接收 → 先比验证码 → 再查用户表 → 成功则写 Session。伪代码逻辑如下// LoginServlet.java 核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) { String username request.getParameter(username); String password request.getParameter(password); String code request.getParameter(code); // 1. 先校验验证码比对 Session 中 CodeServlet 生成的字符串 String sessionCode (String) request.getSession().getAttribute(code); if (!code.equalsIgnoreCase(sessionCode)) { request.setAttribute(msg, 验证码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } // 2. 验证账号密码UserServlet 或独立 UserDao 查库 User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { request.getSession().setAttribute(user, user); response.sendRedirect(index.jsp); // 管理员/学生跳转不同页面 } else { request.setAttribute(msg, 账号或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }逻辑说明第 1 步先校验验证码这样做能减少一次无效的数据库查询——验证码不对就不用查用户表了。equalsIgnoreCase让验证码校验不区分大小写用户体验好一点。第 2 步查用户表成功把 user 对象放进 Session后续过滤器从 Session 取值判断登录状态。注意登录失败返回用的是forward而不是sendRedirect这样msg属性才能在login.jsp页面上显示。参数说明request.getParameter拿到的都是浏览器提交的字符串如果数据库里密码是加密存储的这里需要一个 MD5 或 SHA 工具类做比对。如果你看到 DAO 里的 SQL 是直接拼接字符串那就是存在 SQL 注入风险的写法——面试官特别喜欢问这个你可以顺手改成PreparedStatement参数绑定。4.2 登录过滤器LoginFilter 拦截了什么、放行了什么LoginFilter实现了javax.servlet.Filter接口。我见过很多课设里的过滤器逻辑简单粗暴所有请求都拦截没登录就重定向到登录页。但这个系统有游客角色说明过滤器做了路径放行。// LoginFilter.java 核心逻辑 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getRequestURI(); // 例如 /library/login.jsp // 放行登录页、验证码、静态资源、游客可访问的图书列表与详情 if (path.endsWith(login.jsp) || path.contains(CodeServlet) || path.endsWith(.css) || path.endsWith(.js) || path.contains(BookServlet) request.getParameter(action) null) { chain.doFilter(req, resp); return; } // 检查 Session 用户 Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); }逻辑说明过滤器的核心就是「路径白名单 Session 检查」。path.endsWith(login.jsp)是页面放行path.contains(CodeServlet)是验证码接口放行——如果不放行登录页上的验证码图片会因为未登录被重定向直接破图。BookServlet且不带action参数时是游客查看图书列表需要放行。参数说明request.getContextPath()返回项目上下文路径比如/library。用endsWith判断路径有个隐患logout.jsp之类以.jsp结尾的页面会被误拦截需要加白名单反过来如果某个 Servlet 路径以login开头contains(login)也容易误放行。理解这个逻辑后你再加新页面时就知道要同步改过滤器白名单了。4.3 图书借阅与归还BorrowDaoImpl 里的核心 SQL借书和还书是学生角色最重要的操作。BorrowDaoImpl里至少要有两个方法新增借阅记录和更新归还状态。借书的 SQL 通常长这样INSERT INTO borrow_records (book_id, user_id, borrow_date, due_date, status) VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY), borrowed);归还的 SQL 一般是更新状态UPDATE borrow_records SET return_date NOW(), status returned WHERE id ? AND user_id ?;逻辑说明due_date用DATE_ADD(NOW(), INTERVAL 30 DAY)表示默认借期 30 天这是计算逾期罚款的时间基础。status字段维护借阅状态管理员罚款列表大概率就是查status overdue或due_date NOW()的记录。你打开BorrowDaoImpl.java看时重点看它有没有用事务——借一本书应该同时完成「插入借阅记录」和「更新图书库存状态」两步操作这两步必须在一个事务里否则会出现书借出去了但库存没减的脏数据。如果源码里没有事务这是你改造时的第一个加分点。把两个操作包在Connection的setAutoCommit(false)和等会结束commit()里中间任何一步异常就rollback()。课设答辩时说出这个改进点比你说「我加了十个页面」有用得多。4.4 验证码生成CodeServlet 与 ImgUtils 的配合CodeServlet负责生成随机字符串并存入 SessionImgUtils负责画图。它们的分工是CodeServlet 调 ImgUtils 生成图片同时把字符串放到 Session。ImgUtils的核心是BufferedImage绘制参考逻辑如下// CodeServlet.java 片段 String code randomCode(4); // 生成 4 位随机字母数字 request.getSession().setAttribute(code, code); ImgUtils.writeCode(response.getOutputStream(), code);ImgUtils.writeCode内部会用 Graphics2D 画背景、画干扰线最后ImageIO.write(image, png, out)。如果验证码图片一直加载不出来先检查两件事第一response.setContentType(image/png)有没有写第二CodeServlet的 URL 有没有被LoginFilter拦截。这两个问题分别对应「浏览器不认识返回的数据」和「请求根本没到达 Servlet」。5. 部署与运行避坑手册五个高频翻车现场与对策这套系统在部署和改动时我遇到了不少真实问题。下面整理五条高频踩坑记录每一条都按现象 → 原因 → 解决来写你可以直接对照排查。5.1 浏览器访问项目报 404现象Tomcat 启动正常访问http://localhost:8080/library/index.jsp显示 404。原因最常见的是「项目没有成功发布到 webapps」。IDEA 里用嵌入式 Tomcat 运行时不报错但打包后的 war 没复制到 Tomcat 的 webapps 目录或者 war 包解压后的目录名和你访问路径不一致——比如 war 包叫library.war解压后目录是library你访问library/才对。解决确认webapps目录下有没有解压后的项目文件夹没有就复制 war 进去重启 Tomcat访问路径严格按解压后的目录名来。5.2 登录页验证码图片加载失败现象登录页其他元素渲染正常但验证码图片区域破图或一直转圈。原因CodeServlet被LoginFilter拦了请求没到达 Servlet或者CodeServlet没在web.xml里注册注解方式WebServlet在 Tomcat 7 部分版本不生效。另一个原因是ImgUtils在输出图片前没有设置Content-Type响应头浏览器不知道这是图片数据。解决先在过滤器白名单里加上CodeServlet路径再确认 Servlet 注册方式——传统项目建议在web.xml里用servlet和servlet-mapping显式声明最后在ImgUtils.writeCode方法第一行加response.setContentType(image/png)。5.3 登录后页面跳转反复横跳登录页现象输入正确账号密码后地址栏变成login.jsp或提示「请先登录」但明明刚登录成功。原因登录成功后sendRedirect跳转的路径不对。比如当前项目上下文是/library代码里写的是response.sendRedirect(index.jsp)浏览器解析时会跳到当前路径下的index.jsp如果当前 URL 是/library/login.jsp那解析出来是/library/index.jsp看起来没问题。但如果跳转写成了/index.jsp就会变成http://localhost:8080/index.jsp直接绕过项目上下文被 Tomcat 默认应用或 404 接住。解决所有sendRedirect和getRequestDispatcher路径统一用request.getContextPath()拼前缀。例如response.sendRedirect(request.getContextPath() /index.jsp);5.4 MySQL 8.0 连不上Public Key Retrieval is not allowed现象Tomcat 启动或访问数据库时报Public Key Retrieval is not allowed或Access denied for user。原因MySQL 8 默认认证插件caching_sha2_password老版本驱动5.x连接时如果没有建立 SSL 加密通道需要先向服务器请求公钥但 JDBC URL 里没启用allowPublicKeyRetrievaltrue。解决在 JDBC URL 里加上allowPublicKeyRetrievaltrueuseSSLfalse或者干脆换成 mysql-connector-java 8.x 驱动。我倾向于两个都做——改驱动版本是根治加参数是兜底。5.5 JSP 页面中文乱码现象页面上的中文全部变成问号或乱码。原因三个层面都可能出问题——JSP 文件本身编码不是 UTF-8Tomcat 的 URI 编码没设JDBC 连接的 characterEncoding 不对。解决逐个排查。IDEA 里确认 JSP 文件右下角显示 UTF-8JSP 页面头声明% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%Tomcat 的server.xml里 Connector 加URIEncodingUTF-8JDBC URL 里已经带characterEncodingutf8就不用动。这套组合拳打完乱码基本消失。如果还有问题检查 MySQL 表和字段的字符集是否utf8mb4。6. 进阶改造技巧给这套系统加上图书封面上传与分页系统跑通、代码读懂之后真正的开始是动手改。我给你两个低成本高回报的改造方向图书封面图片上传和图书列表分页查询。这两个功能常见于课设答辩加分项也是从「能跑」到「像正规项目」的明显分界。6.1 封面上传复用已有的 ImgUtils 思路加一个 UploadServlet这个系统的工具类里已经有图片处理的基础但你大概率需要一个上传接口。我的做法是这样的// UploadServlet.java 片段 protected void doPost(HttpServletRequest request, HttpServletResponse response) { // 1. 从请求里拿到上传文件 Part filePart request.getPart(cover); String fileName System.currentTimeMillis() .jpg; String savePath request.getServletContext().getRealPath(/uploads); filePart.write(savePath File.separator fileName); // 2. 把相对路径存到数据库的 book.cover 字段 String coverPath uploads/ fileName; bookDao.updateCover(Integer.parseInt(request.getParameter(bookId)), coverPath); // 3. 重定向回图书管理页 response.sendRedirect(request.getContextPath() /BookServlet?actionlist); }逻辑说明第一步用标准 Servlet 3.0 的Part接口接收上传文件文件名用System.currentTimeMillis()拼时间戳避免重名——这是做上传最朴素实用的一招。getRealPath(/uploads)获取项目部署路径下的uploads目录的绝对地址Tomcat 部署的 webapps 目录里会自动生成。第三步把相对路径uploads/xxx.jpg存数据库JSP 页面上${book.cover}直接拼img标签的src。注意如果你在 IDEA 嵌入式 Tomcat 里跑getRealPath指向的是 IDEA 的编译输出目录上传的图片可能在重启后消失。稳妥做法是传到外部目录比如D:/library_uploads然后给 Tomcat 加虚拟路径映射。课设演示场景下传到项目目录够用但你要知道边界在哪。6.2 分页查询把 BookServlet 的 list 方法改成 LIMIT 翻页游客查看所有图书图书一多页面就会拉很长。分页改造的核心是给 SQL 加LIMIT和算总页数逻辑参考// BookServlet.java 中 list 方法片段 int pageSize 10; // 每页 10 条 int currentPage 1; String pageParam request.getParameter(page); if (pageParam ! null !pageParam.isEmpty()) { currentPage Integer.parseInt(pageParam); } int offset (currentPage - 1) * pageSize; ListBook bookList bookDao.findByPage(offset, pageSize); int totalCount bookDao.countAll(); int totalPages (int) Math.ceil((double) totalCount / pageSize); request.setAttribute(bookList, bookList); request.setAttribute(currentPage, currentPage); request.setAttribute(totalPages, totalPages); request.getRequestDispatcher(/book_list.jsp).forward(request, response);逻辑说明offset (currentPage - 1) * pageSize是分页的核心公式第二页就是LIMIT 10, 10意思是跳过前 10 条取 10 条。totalPages用Math.ceil向上取整最后一页不足 10 条也能正常显示。JSP 页码区放一个循环从 1 到totalPages生成链接样式上把当前页高亮即可。DAO 层对应的 SQL 是SELECT * FROM books LIMIT ?, ?;LIMIT的第一个问号是 offset第二个是 pageSize注意PreparedStatement里两个参数都是setInt。这套改完列表页的性能和体验会明显提升而且代码量不大。如果你的数据量上千条还可以继续优化加一个搜索关键字参数WHERE title LIKE ?拼接在LIMIT前面一个小型检索功能就出来了。这套系统我拆完最深的感受是JSP Servlet 虽然是「老技术」但正是因为它老所有的概念都是裸的——请求怎么进来、Session 怎么存、过滤器怎么拦、SQL 怎么写都能一眼看穿。从那以后我每次接手老项目都强制自己先画一张「请求路径 → Servlet → DAO → JSP」的跳转图再动代码半小时的看图时间能省下三小时的排查时间。希望帮到你。本文还有配套的精品资源点击获取