十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java JSP Servlet MySQL图书管理系统从零搭建到编码实战

Java JSP Servlet MySQL图书管理系统从零搭建到编码实战 简介一套基于 JavaJSPServletMySQL 构建的 Web 图书管理系统项目源码面向 Java Web 初学人群与高校课程设计场景可作为课程设计、综合练手或答辩参考。项目完整覆盖图书增删改查、读者管理、借还书处理等核心业务模块清晰展现了 MVC 分层、JDBC 数据库访问、Session 会话跟踪、Cookie 状态维持、异常处理与基础安全防护等关键技术点。压缩包共102个文件包含26个JSP动态页面、13个Java源文件、12个CSS样式表、12个JavaScript脚本以及 MySQL 建表 SQL、XML 配置文件和 JAR 依赖包整包仅4.42MB结构简洁导入 IDE 即可运行调试。已有5540人浏览学习读者可从源码中直观理解请求从 JSP 到 Servlet、Service、DAO、再到 MySQL 的完整调用链路同时覆盖前端页面与后端逻辑是巩固经典 Java Web 技术栈、快速上手开发流程的实用资料。无论是制作毕业设计还是系统学习 Servlet/JSP 开发都能从中获得可直接借鉴的代码结构与实现思路。1. JavaJSPServletMySQL 图书管理系统为什么这套老组合还值得手写一遍JSP 在新项目里已经被 Spring Boot 系工具边缘化多年但「图书管理系统」这个题目恰恰是理解 Java Web 全链路最便宜的入口。它不靠框架藏细节浏览器发请求、Servlet 接参数、JDBC 查 MySQL、JSP 渲染结果每一步都显式存在。适合刚学完 Java 基础、准备做课程设计或期末 Web 作业的人也适合在面试前把 Servlet 生命周期、请求转发、session 作用域这些八股用代码重新过一遍的熟手。前提是本机装好了 JDK 8/11、Tomcat 9 和 MySQL 8.0接下来按这条路径走一个下午能跑通登录和图书列表两件核心事。2. 先立 MVCServlet 做控制、JSP 做展示、MySQL 做存储写图书管理系统最容易犯的错是一上来就拖页面。页面画完发现数据没地方放于是把 SQL 塞进 JSP 的脚本片段里项目就算“做完”了。先把职责分清楚后面每个功能都是同一套模板的复制改起来也只用动一处。2.1 Servlet、JSP、MySQL 在请求里各管哪一段Servlet 在整套体系里是唯一直接读写 HttpServletRequest、HttpServletResponse 的组件。登录、查询、借书、还书这几类操作本质都是“读参数、校验、查库、回写页面”控制器角色天然落在 Servlet 上。JSP 只做两件事从 request 或 session 里取已经准备好的数据再用 HTML 和标签把它铺出来。MySQL 负责把用户、图书、借阅记录落盘DAO 层通过 JDBC 与它通信。常见误用是把 Connection、PreparedStatement 直接写进 JSP 的% %里。单个 login.jsp 这样写很爽等加上 book_list、borrow 两个页面改一次样式就可能碰坏查询逻辑。我一般维持一条纪律JSP 里的 Java 代码只做遍历和取值任何带 SELECT、INSERT、UPDATE、DELETE 的语句只允许出现在 DAO 类里。这条纪律比选什么框架更重要。2.2 一份可以直接照抄的包结构book-manager/ ├─ src/main/java/ │ ├─ com.demo.entity/ User、Book、Borrow纯字段加 getter/setter │ ├─ com.demo.dao/ UserDao、BookDao只写 JDBC 和 SQL │ ├─ com.demo.servlet/ LoginServlet、BookServlet、BorrowServlet │ └─ com.demo.util/ DBUtil管理 Connection └─ src/main/webapp/ ├─ login.jsp ├─ book_list.jsp └─ WEB-INF/web.xmlentity 对应表结构字段名与列名一一对应DAO 返回 List 、User 这类对象完全不知道页面的存在Servlet 拿到结果后放进 request.setAttribute再 forward 给 JSP。层与层只通过方法签名耦合将来把 JDBC 换成连接池或者改页面结构互相不影响。这里要特别说清 forward 和 redirect 的区别这是面试和排错都会碰到的点。forward 是服务端内部跳转地址栏不变request 里 setAttribute 的数据在目标 JSP 还能拿到适合“登录失败回登录页带上错误提示”这种场景。redirect 是浏览器收到 302 后重新发起一次请求地址栏变化request 里的数据全部丢失适合登录成功后的跳转能防止按 F5 时表单被重复提交。2.3 三张表的字段设计与两个容易忽略的点图书管理系统再小也建议按 users、book、borrow 三张表建而不是把所有字段塞进一张表。借阅关系用关联表表达才能回答“谁借了哪本书、什么时候还的、哪本书还剩几本”。表名关键字段职责usersid, username, password, role登录账号role 区分管理员与普通读者bookid, isbn, name, author, publisher, price, stock, totalisbn 唯一stock 表示当前可借数borrowid, user_id, book_id, borrow_time, return_time归还时间为 NULL 表示未还设计上有两点容易被忽略。一是 book 表把“馆藏总量 total”和“当前可借 stock”分开借出时 stock 减一、还书时加一查询可借列表直接 WHERE stock 0不用去 borrow 表里数数量二是 borrow 表不要冗余 book_name、user_name 这类字段展示时用 JOIN 现查否则改书名时还得同步改历史借阅记录。user_id 和 book_id 上加普通索引数据量上来后 JOIN 不至于全表扫描。3. JavaJSPServletMySQL 落地从建库、登录到图书列表跑通3.1 MySQL 建库建表 SQLCREATE DATABASE IF NOT EXISTS book_manager DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE book_manager; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( id INT AUTO_INCREMENT PRIMARY KEY, isbn VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), price DECIMAL(8,2), stock INT NOT NULL DEFAULT 0, total INT NOT NULL DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE borrow ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, return_time DATETIME NULL, KEY idx_user (user_id), KEY idx_book (book_id), CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES users(id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO users (username, password) VALUES (admin, 123456); INSERT INTO book (isbn, name, author, publisher, price, stock, total) VALUES (978-7-111-00001-0, Java 核心技术 卷I, Cay S. Horstmann, 机械工业出版社, 119.00, 10, 10);库、表、连接串三处的字符集必须一致这里统一用 utf8mb4它比 utf8 多覆盖特殊字符。password 字段演示用明文实际项目至少做加盐哈希。price 用 DECIMAL(8,2) 而不是 FLOAT避免金额出现精度误差。借阅的外键约束在课程设计里可以保留能少掉一层脏数据检查。3.2 Maven 依赖与 DBUtil 连接工具用 IDEA 建 Maven war 工程pom.xml 里加三个依赖dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependenciesservlet-api 的 scope 是 provided因为 Tomcat 自己带了一份打包进 war 反而可能版本冲突。如果不用 Maven就把 jstl 和 mysql-connector 两个 jar 直接拷到 webapp/WEB-INF/lib 下效果一样。连接工具类先给最小可用版本package com.demo.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/book_manager ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD root; static { try { // 驱动类名对应 8.x 驱动5.x 不带 cj Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }连接串里四个参数各有一个坑characterEncodingutf8 是给驱动看的写成 UTF-8 带连字符会不识别serverTimezoneAsia/Shanghai 是 MySQL 8.x 驱动的硬要求不写就报时区错误useSSLfalse 关掉 8.x 默认的 TLS 握手告警。驱动类名随版本变出现 ClassNotFoundException 先查这一条。这里用 DriverManager 是为了把原理讲透作业里嫌每次建连慢可以换 Druid 或 HikariCPDAO 里的代码不用动。3.3 登录Servlet 里 forward 与 redirect 的首次交汇登录是最能体现 Servlet 风格的功能。LoginServlet 的 doPost 接收表单package com.demo.servlet; import com.demo.dao.UserDao; import com.demo.entity.User; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private final UserDao userDao new UserDao(); 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); User user userDao.findByUsernameAndPassword(username, password); if (user null) { // 失败用 forwardrequest 里的 error 属性才能被 JSP 读到 request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } HttpSession session request.getSession(); session.setAttribute(currentUser, user); response.sendRedirect(request.getContextPath() /book/list); } }要点有三个。request.setCharacterEncoding(UTF-8) 必须放在读取 getParameter 之前失败分支用 forwardrequest 里的 error 属性才能被 login.jsp 读到成功分支用 sendRedirect 且拼上 getContextPath()应用部署在非根路径时也能正确回到 /book/list不会出现 404。对应的 UserDao 用 PreparedStatement 占位符public User findByUsernameAndPassword(String username, String password) { String sql SELECT id, username, role FROM users WHERE username? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setRole(rs.getInt(role)); return u; } } } catch (SQLException e) { e.printStackTrace(); } return null; }SQL 里用 ? 占位参数由 ps.setString 传入而不是把用户名拼进 SQL 字符串里这是防注入的基本功。try-with-resources 保证 Connection、PreparedStatement、ResultSet 在方法结束时依次关闭手动写 finally 关连接很容易漏连接泄漏的典型症状就是跑几次操作后 Tomcat 报连接超时。3.4 图书列表Servlet 查库、JSP 渲染BookListServlet 负责查全表和权限判断WebServlet(/book/list) public class BookListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 没登录就回登录页靠 session 里的 currentUser 判断 if (request.getSession().getAttribute(currentUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } ListBook books new BookDao().findAll(); request.setAttribute(bookList, books); request.getRequestDispatcher(/book_list.jsp).forward(request, response); } }页面用 JSTL 而不是脚本片段% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle图书列表/title/head body h2图书列表/h2 table border1 trthISBN/thth书名/thth作者/thth价格/thth可借/th/tr c:forEach items${bookList} varb tr td${b.isbn}/td td${b.name}/td td${b.author}/td td${b.price}/td td${b.stock}/td /tr /c:forEach /table /body /html${b.isbn} 实际调用 Book 类的 getIsbn()页面里不再出现out.println。注意 taglib 那行依赖 3.2 里的 jstl 依赖缺了会报 The absolute uri cannot be resolved看到这条先检查 lib。WebServlet(/book/list) 是 Servlet 3.0 起的注解写法web.xml 里不用再逐个声明映射Tomcat 9 默认支持。4. Servlet 与 MySQL 的必调参数以及 JSP 改了不生效的排错路径4.1 五个必调参数参数位置建议值漏掉的症状characterEncodingutf8JDBC URLutf8不写 UTF-8中文入库变 ??serverTimezoneAsia/ShanghaiJDBC URLAsia/Shanghai连接时报时区错误useSSLfalseJDBC URLfalse驱动打印 TLS 告警URIEncodingUTF-8Tomcat conf/server.xmlUTF-8GET 传参中文乱码setCharacterEncoding(UTF-8)每个 doPost 开头UTF-8POST 表单乱码GET 和 POST 的乱码源头不同。GET 的参数拼在 URL 上由 Tomcat 的 URIEncoding 决定解码POST 的参数在请求体里靠服务端 setCharacterEncoding 决定。两个参数都设了仍然乱码再查 JSP 页面顶部的 contentType 和数据库连接串的 characterEncoding四级对齐之后基本不再出问题。4.2 高频报错速查表报错或现象原因处理ClassNotFoundException: com.mysql.cj.jdbc.Driver驱动 jar 不在 classpath依赖没引入或 jar 没进 WEB-INF/libThe server time zone value Öйú±ê׼ʱ¼ä is unrecognizedURL 缺 serverTimezone补上时区参数重启Access denied for user rootlocalhost账号或密码与 DBUtil 不一致用 MySQL 客户端确认JSP 改了但页面没变浏览器缓存或部署目录没同步清缓存删 work/CatalinaIDEA 里 Rebuild列表页 500The absolute uri cannot be resolvedjstl 依赖缺失补 jstl 1.2 jar刷新一次就重复插入一条数据表单提交后用 forward 跳转成功后改用 sendRedirect其中 JSP 改了不生效要解释一下机制。JSP 第一次被访问时Tomcat 才把它翻译成 Java 再编译成 class产物在 work/Catalina/localhost/应用名/org/apache/jsp/ 目录下。改 JSP 文件本身会被 Tomcat 检测并重新编译但浏览器缓存了旧页面、或者 IDE 没把修改同步到部署目录时看到的还是旧效果。排查顺序是强制刷新浏览器再删 work 目录重启 Tomcat最后看部署目录里 jsp 文件的时间戳。4.3 一种把排错范围缩小一半的测法JSP、Servlet、数据库三层同时出问题时只在浏览器里看页面分不清断在哪一层。常见做法是给 DAO 写一个只带 main 方法的测试类单独调用 findByUsernameAndPassword能跑通就证明 JDBC、连接串、SQL 都没问题问题只剩 Servlet 映射和 JSP 渲染。这个 main 方法不需要启动 TomcatMySQL 开着就行。提示main 里测试 SQL 输出中文正常而页面乱码时问题几乎可以锁定在 JSP 页面编码或表单提交编码而不是数据库。这样分层排查能省掉大量“页面 500 然后从 JSP 一路查到 SQL”的往返时间。5. 一个收尾技巧用 /book/* 通配把图书管理系统的 Servlet 入口收敛成一个功能一多LoginServlet、BookListServlet、BorrowServlet 各成一类每加一个动作就新增一个文件。把多个 Servlet 收敛成一个统一入口是 Servlet 项目里性价比最高的重构。用通配映射加路径分发WebServlet(/book/*) public class BookServlet extends HttpServlet { Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { User currentUser (User) request.getSession().getAttribute(currentUser); if (currentUser null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } String path request.getPathInfo(); // /list、/add、/return switch (path) { case /list: list(request, response); break; case /add: add(request, response); break; case /return: returnBook(request, response); break; default: response.sendError(404); } } private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ListBook books new BookDao().findAll(); req.setAttribute(bookList, books); req.getRequestDispatcher(/book_list.jsp).forward(req, resp); } }分发的关键在 getPathInfo()访问 /book/list 时它返回 /list访问 /book/add 返回 /add返回值就是动作名。覆盖 service 而不是 doGet/doPost让 GET 和 POST 都先过同一道登录校验权限判断只写一遍。新增动作时只加一个 case 分支和对应的私有方法不再新增 Servlet 类web.xml 也不用动。私有方法保持与 doGet 相同的 (HttpServletRequest, HttpServletResponse) 签名异常直接抛给容器统一处理不在分发层用 try 吞掉。验证这个收敛方式是否生效看两点未登录直接访问 /book/list按上面代码会先走鉴权分支返回 302 并跳到 login.jsp登录后重新访问同一地址返回 200且 book_list.jsp 里表格行数与 book 表记录数一致。顺手看浏览器开发者工具 Network 面板里 /book/list 的响应码能同时确认注解路径和 session 校验都工作正常。本文还有配套的精品资源点击获取
返回列表