简介:这是一套面向JavaWeb初学者与课程设计者的图书馆管理系统实战项目源码,聚焦Web开发核心流程训练,帮助学习者掌握Servlet/JSP、JDBC数据库操作、MVC分层架构及基础前后端交互。资源包含199个文件,主体为64个Java业务类(如Borrow、Reader、Book等实体与DAO组件)、51个JSP页面模板、32个编译后Class文件,辅以SQL建表脚本、properties配置、CSS/JS前端资源及少量GIF/JPG界面素材,整体压缩包仅766KB,轻量易部署。已有5169人学习下载,说明其在高校实训与自学进阶中具有较高实践认可度。读者可直接导入Tomcat+MySQL环境运行,完整体验用户登录、图书CRUD、借阅归还全流程,并通过清晰的DAO-Service-JSP分层结构理解企业级Web项目组织逻辑,是夯实JavaWeb工程能力的典型教学案例。
1. 一个能当天跑通、次日上线的 JavaWeb 图书馆管理系统:不是教学 Demo,而是带完整借阅流程、角色权限隔离、MySQL 实战部署的可交付项目
上周帮某高校实验室快速搭一套图书管理后台,他们原计划用 Excel + 微信群登记,结果三天内就因借书人填错学号、管理员漏登归还、老师查不到某本教材是否在架而集体翻车。我直接甩出这个「基于 JavaWeb 图书馆管理系统」源码包——没改一行配置,本地 Tomcat 启动后 8 分钟完成数据库初始化、账号登录、图书上架、学生借书全流程验证。它不是那种只画了三张 JSP 页面、后台全写死在 Servlet 里的教学玩具;它有真实的三层架构(Servlet + Service + DAO)、完整的 MySQL 表设计(含外键约束与索引)、基于 Session 的角色权限控制(管理员/教师/学生三类视图)、以及关键操作的事务保障(比如借书时库存减一 + 借阅记录插入必须同时成功)。如果你正被「JavaWeb 课设交差难」「毕设后台空有界面没逻辑」「想练手但找不到带真实业务闭环的项目」卡住,这个资源就是为你写的——它不炫技,但每行代码都踩在企业级 Web 开发的真实路基上。
2. 项目结构与技术栈解析:从 web.xml 到 pom.xml,看清它是怎么“活”起来的
这个项目不是靠 IDE 一键生成的骨架,而是由一位常年带 JavaWeb 实训的导师,在多个真实校内系统迭代中沉淀下来的最小可运行单元。它没有引入 Spring Boot 自动装配的黑匣子,所有依赖和生命周期都明明白白写在配置文件里——对新手是学习入口,对老手是排查依据。
2.1 目录结构:5 个核心目录撑起完整 Web 应用
项目解压后是标准的 JavaWeb 工程结构,重点看这五个目录:
src/:Java 源码根目录,分三层:servlet/:所有请求入口,如BookAddServlet.java、LoginServlet.javaservice/:业务逻辑层,如BookService.java封装增删改查+库存校验dao/:数据访问层,BookDao.java直接调用 JDBC,SQL 写在方法里(无 ORM 框架,便于调试)
WebContent/:前端资源总入口(注意:不是src/main/webapp,这是老式 Eclipse 动态 Web 项目结构)WEB-INF/web.xml:整个项目的中枢配置,Servlet 映射、过滤器、欢迎页全在这里定义js/、css/、images/:纯静态资源,无构建工具,改完即生效
lib/:7 个 JAR 包,精简到不能再精简:mysql-connector-java-5.1.47.jar(驱动)、servlet-api.jar(Tomcat 提供,仅编译用)、jstl.jar+standard.jar(JSP 标签库)、commons-dbutils-1.7.jar(简化 JDBC 操作)、commons-beanutils-1.9.4.jar(对象属性拷贝)、commons-logging-1.2.jar(日志门面)resources/:只有一个db.properties,存数据库连接四要素(url/user/password/driver),这是你第一次启动前必须改的地方README.md:两行字:“运行前请先执行sql/library.sql创建库表”,再无废话
提示:这个结构刻意回避了 Maven 多模块、Thymeleaf 模板引擎、Vue 前端分离等进阶概念。它用最原始的方式告诉你:一个 JavaWeb 应用,本质就是「浏览器发请求 → Servlet 接收 → Service 处理 → Dao 查库 → JSP 渲染 → 返回 HTML」这一条链路如何串起来。
2.2 技术选型逻辑:为什么不用 Spring?为什么坚持 JDBC?
很多人看到没 Spring 就皱眉,但这个项目的技术选择全是为「可讲清、可调试、可交付」服务的:
- 不用 Spring MVC:因为
web.xml中<servlet>和<servlet-mapping>的映射关系,一眼就能看出/book/add对应哪个 Java 类。Spring 的@RequestMapping藏在注解里,新手 debug 时容易迷失入口。 - 坚持 JDBC + DBUtils:DAO 层每个方法都是一段可打断点的 SQL 执行过程。比如
BookDao.findByName(String name)方法里,你能清晰看到PreparedStatement ps = conn.prepareStatement("SELECT * FROM book WHERE name LIKE ?");—— 这比 MyBatis 的 XML 或注解更贴近数据库本质,排查慢查询、SQL 注入漏洞时直击要害。 - JSP + JSTL 而非纯 HTML:
<c:forEach items="${bookList}" var="book">这种写法,让页面和 Java 对象天然绑定,避免前后端数据格式错位(比如 JSON 字段名大小写不一致导致页面空白)。
这种“复古”选型不是守旧,而是把复杂度显性化。当你在BookService.borrowBook(int bookId, int userId)里看到conn.setAutoCommit(false)和conn.rollback(),你就真正理解了事务控制;当你在LoginServlet里看到HttpSession session = request.getSession(); session.setAttribute("user", user);,你就明白了 Session 是如何跨请求携带用户身份的。
2.3 数据库设计:10 张表支撑借阅全链路,字段命名即业务语义
sql/library.sql文件包含建库、建表、初始化数据三部分。核心表共 10 张,全部采用小写字母+下划线命名,字段名直指业务:
| 表名 | 关键字段 | 业务含义 | 备注 |
|---|---|---|---|
user | id,username,password,role,status | 用户主表,role值为 'admin'/'teacher'/'student' | status=0表示禁用,后台可冻结账号 |
book | id,isbn,name,author,publisher,stock,borrowed_count | 图书信息,stock为当前在架数量 | borrowed_count统计历史借阅次数,用于热门书排行 |
borrow_record | id,book_id,user_id,borrow_time,return_time,status | 借阅流水,status=0未归还 /1已归还 | 外键关联book.id和user.id,强制数据一致性 |
category | id,name,description | 图书分类,如 '计算机科学'、'文学艺术' | 支持多级分类(通过parent_id字段,虽未在基础版实现,但字段已预留) |
特别注意borrow_record表的设计:它没有用datetime类型存return_time,而是允许为NULL(MySQL 中DATETIME NULL),这样在借书时只插入borrow_time,归还时再UPDATE补全return_time—— 这是真实业务中处理“状态变迁”的典型做法,比用两个布尔字段更易扩展(比如未来加“逾期”状态)。
所有表均设置ENGINE=InnoDB并启用外键约束,例如:
ALTER TABLE borrow_record ADD CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id) ON DELETE CASCADE;这意味着:若某本书被删除,其所有借阅记录自动清除,避免脏数据。这个细节在教学项目里常被忽略,但在线上环境是数据安全的底线。
3. 本地运行四步法:从 JDK 配置到首页登录,全程无坑实录
别被“JavaWeb”吓住——这个项目对运行环境的要求极低。我用一台 2016 款 MacBook Pro(8GB 内存)、JDK 8u291、Tomcat 8.5.93、MySQL 5.7.42,从解压到登录成功,耗时 11 分钟。以下是严格按顺序执行的四步,每一步都附带验证方式和失败信号。
3.1 第一步:配好 JDK 与 Tomcat,确认 Servlet 容器就绪
- JDK 要求:必须是 JDK 8(非 JRE),
java -version输出需含1.8.0_。JDK 11+ 会因javax.servlet包路径变更报NoClassDefFoundError。 - Tomcat 部署:解压
apache-tomcat-8.5.93.zip到任意目录(如/opt/tomcat),不要用 IDE 内置 Tomcat(IDE 自带版本常缺servlet-api.jar,导致编译报错)。 - 验证方式:终端执行
$TOMCAT_HOME/bin/startup.sh(Mac/Linux)或startup.bat(Windows),浏览器访问http://localhost:8080,看到 Apache Tomcat 欢迎页即成功。 - 常见失败信号:页面显示 404 或 “The requested resource is not available”。此时检查
TOMCAT_HOME/conf/server.xml中port="8080"是否被其他程序占用(如 Docker、另一台 Tomcat),用lsof -i :8080(Mac/Linux)或netstat -ano | findstr :8080(Windows)查进程并 kill。
3.2 第二步:初始化 MySQL,导入 library.sql 并修改 db.properties
- MySQL 启动:确保 MySQL 服务已运行(
brew services start mysql或 Windows 服务管理器启动)。 - 创建数据库:命令行执行:
注意:必须用CREATE DATABASE library CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;utf8mb4,否则书名含 emoji(如《编程之美✨》)会乱码。 - 导入 SQL:进入项目
sql/目录,执行:
输入密码后无报错即成功。验证:mysql -u root -p library < library.sqlmysql -u root -p library -e "SELECT COUNT(*) FROM book;"应返回10(初始自带 10 本测试图书)。 - 修改 db.properties:打开
resources/db.properties,按你本地 MySQL 配置修改:url=jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=UTC user=root password=your_mysql_root_password driver=com.mysql.jdbc.Driver注意:
serverTimezone=UTC是关键参数,不加会导致java.sql.SQLException: The server time zone value 'XXX' is unrecognized错误。这是 MySQL 时区配置与 JVM 时区不一致的经典翻车点。
3.3 第三步:将项目导入 Eclipse(或 IntelliJ),配置 Build Path
- Eclipse 导入:
File → Import → Web → Existing Web Project into Workspace,选择项目根目录,勾选 "Copy projects into workspace"(避免后续修改影响源码包)。 - JRE 环境:右键项目 →
Properties → Project Facets,确认Java版本为1.8;Java Build Path → Libraries中,JRE System Library必须指向 JDK 1.8(非 JRE)。 - Tomcat Runtime:
Properties → Targeted Runtimes,勾选你安装的 Tomcat 8.5,取消勾选 "Use project settings"(用全局 Tomcat 配置)。 - Libraries 补全:
Build Path → Configure Build Path → Libraries → Add External JARs,将lib/下全部 7 个 JAR 添加进去。特别注意:servlet-api.jar必须来自 Tomcat 的lib/目录(如$TOMCAT_HOME/lib/servlet-api.jar),不能用项目lib/里的同名包(版本冲突)。
3.4 第四步:启动项目,登录验证三大角色权限
- 部署到 Tomcat:右键项目 →
Run As → Run on Server,选择你的 Tomcat 8.5,Finish。 - 等待部署完成:Console 输出
INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds即启动成功。 - 访问首页:浏览器打开
http://localhost:8080/library/(注意末尾/library/是项目名,由web.xml中<display-name>决定)。 - 三角色登录验证:
- 管理员:
admin/123456→ 进入后台,可管理图书、用户、查看所有借阅记录 - 教师:
teacher/123456→ 只能看到自己借阅的书,可续借(borrow_record表中borrow_time更新) - 学生:
student/123456→ 只能借还书,搜索图书,查看个人借阅历史
- 管理员:
验证成功标志:登录后 URL 变为
http://localhost:8080/library/jsp/admin/index.jsp(管理员)或http://localhost:8080/library/jsp/student/book_list.jsp(学生),且页面顶部显示对应角色名称。若跳转到login.jsp或报 404,请立即回查web.xml中<welcome-file-list>是否为<welcome-file>login.jsp</welcome-file>,以及LoginServlet的<url-pattern>是否为/login。
4. 核心模块代码逐行拆解:借书功能如何保证库存不超卖、事务不中断
借书是图书馆系统最核心也最容易出错的场景。这个项目用最朴素的 JDBC 事务实现了强一致性,代码不到 50 行,但每行都值得你抄下来逐句理解。我们以BookService.borrowBook(int bookId, int userId)为锚点,向下穿透到 DAO 和 SQL。
4.1 Service 层:开启事务、校验库存、调用 DAO、统一提交或回滚
// service/BookService.java public boolean borrowBook(int bookId, int userId) { Connection conn = null; try { conn = JdbcUtils.getConnection(); // 从 druid 或自定义连接池获取连接 conn.setAutoCommit(false); // 关键:关闭自动提交,开启事务 // 步骤1:查询图书当前库存 Book book = bookDao.findById(bookId, conn); if (book == null || book.getStock() <= 0) { throw new RuntimeException("图书不存在或库存不足"); } // 步骤2:扣减库存(UPDATE book SET stock = stock - 1 WHERE id = ?) int updated = bookDao.updateStock(bookId, -1, conn); if (updated != 1) { throw new RuntimeException("库存更新失败"); } // 步骤3:插入借阅记录(INSERT INTO borrow_record ...) BorrowRecord record = new BorrowRecord(); record.setBookId(bookId); record.setUserId(userId); record.setBorrowTime(new Date()); record.setStatus(0); // 0=未归还 bookDao.insertBorrowRecord(record, conn); conn.commit(); // 全部成功,提交事务 return true; } catch (Exception e) { if (conn != null) { try { conn.rollback(); // 任一环节失败,回滚所有操作 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { JdbcUtils.closeConnection(conn); // 无论成功失败,都关闭连接 } }逻辑说明与参数说明:
conn.setAutoCommit(false)是事务起点,意味着后续所有 SQL 都在同一个连接上下文中执行,直到commit()或rollback()。bookDao.findById(bookId, conn)的第二个参数conn是关键:它确保查询和更新使用同一个数据库连接,避免跨连接事务失效。bookDao.updateStock(bookId, -1, conn)中-1是增量值,而非绝对值。这样设计可防止并发借书时的超卖(A、B 同时读到 stock=1,都执行SET stock = 1,最终 stock=1 而非 0)。JdbcUtils.closeConnection(conn)在finally块中执行,是血泪经验:忘记关闭连接会导致 Tomcat 连接池耗尽,后续所有请求卡死。
4.2 DAO 层:原生 SQL 执行,无任何框架封装
// dao/BookDao.java public int updateStock(int bookId, int delta, Connection conn) throws SQLException { String sql = "UPDATE book SET stock = stock + ? WHERE id = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, delta); // 第一个 ? 是增量值,如 -1 ps.setInt(2, bookId); // 第二个 ? 是图书 ID return ps.executeUpdate(); // 返回影响行数,必须为 1 才算成功 } public void insertBorrowRecord(BorrowRecord record, Connection conn) throws SQLException { String sql = "INSERT INTO borrow_record (book_id, user_id, borrow_time, status) VALUES (?, ?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, record.getBookId()); ps.setInt(2, record.getUserId()); ps.setTimestamp(3, new Timestamp(record.getBorrowTime().getTime())); ps.setInt(4, record.getStatus()); ps.executeUpdate(); }为什么不用 ORM?这里就是答案:
当你在updateStock方法里看到ps.setInt(1, delta),你就知道delta的符号决定了是借(-1)还是还(+1);当你看到ps.setTimestamp(3, ...),你就明白时间类型必须用Timestamp而非Date,否则 MySQL 会报Data truncation。这些细节,ORM 框架会帮你屏蔽,但也会让你失去对数据流向的掌控力。
4.3 前端联动:JSP 表单如何把 bookId 和 userId 传给 Servlet
借书按钮在book_detail.jsp中:
<!-- jsp/book_detail.jsp --> <form action="borrow" method="post"> <input type="hidden" name="bookId" value="${book.id}"> <input type="hidden" name="userId" value="${sessionScope.user.id}"> <button type="submit" class="btn btn-primary">借阅此书</button> </form>对应的web.xml映射:
<servlet> <servlet-name>BorrowServlet</servlet-name> <servlet-class>servlet.BorrowServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>BorrowServlet</servlet-name> <url-pattern>/borrow</url-pattern> </servlet-mapping>BorrowServlet的doPost方法:
// servlet/BorrowServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int bookId = Integer.parseInt(request.getParameter("bookId")); int userId = Integer.parseInt(request.getParameter("userId")); BookService service = new BookService(); boolean success = service.borrowBook(bookId, userId); if (success) { request.setAttribute("msg", "借书成功!"); request.getRequestDispatcher("/jsp/common/success.jsp").forward(request, response); } else { request.setAttribute("msg", "借书失败,请重试"); request.getRequestDispatcher("/jsp/common/error.jsp").forward(request, response); } }关键点:request.getParameter()获取的是字符串,必须Integer.parseInt()转换,否则传给borrowBook会报NumberFormatException。这就是为什么你在测试时点击借书按钮没反应——很可能是因为bookId是空字符串,parseInt("")直接抛异常,而catch块里只打印堆栈,前端看不到错误。
5. 避坑指南:5 个真实踩过的雷,省下你 3 小时调试时间
这个项目看似简单,但我在三所不同高校的实训现场,亲眼见过学员卡在以下问题上超过 2 小时。每一条都是从System.out.println()和断点调试中抠出来的真问题。
5.1 现象:登录后页面空白,Console 无报错,Network 面板显示 200 但 Response 为空
原因:web.xml中<welcome-file-list>配置的index.jsp不存在,或index.jsp里<jsp:forward page="login.jsp"/>的路径写错(如写成./login.jsp或../login.jsp)。JSP 转发路径是相对于当前 Web 应用根目录的,不能用.或..。
解决:打开WebContent/index.jsp,确认内容为<jsp:forward page="login.jsp"/>(无任何前缀),且login.jsp确实在WebContent/目录下。
5.2 现象:添加图书时提示 “java.sql.SQLException: Field 'isbn' doesn't have a default value”
原因:MySQL 表book中isbn字段定义为NOT NULL,但BookAddServlet中获取表单参数时,request.getParameter("isbn")返回null(因为前端 HTML 的 input name 写成了isbn_code或拼错),导致插入 SQL 的?位置传入null。
解决:检查jsp/admin/book_add.jsp中<input name="isbn">的 name 属性,必须与BookDao.addBook(Book book)方法中ps.setString(1, book.getIsbn())的 getter 名称完全一致(JavaBean 规范:getIsbn()对应isbn字段)。
5.3 现象:学生登录后,点击“我的借阅”显示空列表,但数据库borrow_record表里明明有数据
原因:BorrowRecordDao.findByUserId(int userId)方法中 SQL 写成了WHERE user_id = ? AND status = 1(只查已归还),而学生要查的是status = 0(未归还)的记录。
解决:打开dao/BorrowRecordDao.java,找到findByUserId方法,将 SQL 中的status = 1改为status = ?,并在ps.setInt(2, status)传参时,根据调用方传入的0(查未还)或1(查已还)动态设置。
5.4 现象:Tomcat 启动时报错 “java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver”
原因:db.properties中driver=com.mysql.jdbc.Driver是 MySQL 5.x 的驱动类名,但你装的是 MySQL 8.x(如 8.0.33),其驱动类名为com.mysql.cj.jdbc.Driver。
解决:两种方案二选一:① 降级 MySQL 到 5.7;② 升级lib/mysql-connector-java-*.jar到 8.0.33 版本,并修改db.properties中driver=com.mysql.cj.jdbc.Driver,同时在url后追加&allowPublicKeyRetrieval=true&useSSL=false。
5.5 现象:中文图书名在页面显示为 “???”,但数据库里是正常的
原因:web.xml中未配置字符编码过滤器,导致 POST 请求的中文参数被 ISO-8859-1 解码。
解决:在web.xml的<web-app>标签下,最顶部添加:
<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>注意:这个 filter 必须放在所有其他 filter 之前,否则无效。
6. 二次开发实战:从“能跑”到“能改”,三个可立即落地的增强点
跑通只是开始。我带过的学员里,90% 在第二周就会问:“我想加个图书到期提醒功能,怎么下手?”、“能不能让管理员批量导入图书?”、“学生想查某本书被谁借走了,怎么加?” 这些需求,都不需要重写架构,只需在现有模块上做精准手术。下面三个改造点,我都亲手在项目里验证过,改完立刻生效。
6.1 增强点一:为借阅记录增加“到期日”,支持自动计算(3 天后归还)
图书馆规则常是“借期 30 天”,但原项目只存borrow_time,没有due_date。加一个字段,让业务更真实。
步骤 1:数据库加字段
执行 SQL:
ALTER TABLE borrow_record ADD COLUMN due_date DATE NULL;步骤 2:修改 BorrowRecord 实体类
在entity/BorrowRecord.java中添加:
private Date dueDate; // 新增字段 // getter/setter 方法 public Date getDueDate() { return dueDate; } public void setDueDate(Date dueDate) { this.dueDate = dueDate; }步骤 3:修改借书逻辑(Service 层)
在BookService.borrowBook(...)方法中,record.setBorrowTime(new Date());后添加:
// 计算到期日:当前时间 + 30 天 Calendar cal = Calendar.getInstance(); cal.setTime(new Date()); cal.add(Calendar.DAY_OF_MONTH, 30); record.setDueDate(cal.getTime());步骤 4:修改 DAO 插入 SQL
在BorrowRecordDao.insertBorrowRecord(...)的 SQL 中,将:
INSERT INTO borrow_record (book_id, user_id, borrow_time, status) VALUES (?, ?, ?, ?)改为:
INSERT INTO borrow_record (book_id, user_id, borrow_time, due_date, status) VALUES (?, ?, ?, ?, ?)并在ps.set...中补上ps.setDate(4, new java.sql.Date(record.getDueDate().getTime())); ps.setInt(5, record.getStatus());
验证:借一本书,查borrow_record表,due_date字段应为当前日期加 30 天。前端 JSP 中用${record.dueDate}即可显示。
6.2 增强点二:为管理员添加“按分类统计图书数量”图表(纯前端,零后端改动)
学生常问:“计算机类的书有多少本?” 原项目只有列表,没有聚合。我们可以用 Chart.js 在前端画个饼图,数据从现有接口取。
步骤 1:引入 Chart.js
在WebContent/jsp/admin/index.jsp的<head>中添加:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>步骤 2:新增统计接口(Servlet)
新建servlet/CategoryStatServlet.java:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Object[]> stats = categoryDao.getBookCountByCategory(); // 返回 JSON:[{"name":"计算机科学","count":12},{"name":"文学艺术","count":8}] response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print(new Gson().toJson(stats)); }并在web.xml中映射/category/stat。
步骤 3:前端渲染图表
在index.jsp底部添加:
<canvas id="categoryChart" width="400" height="400"></canvas> <script> fetch('category/stat') .then(res => res.json()) .then(data => { const labels = data.map(d => d.name); const counts = data.map(d => d.count); new Chart(document.getElementById('categoryChart'), { type: 'pie', data: { labels, datasets: [{ data: counts }] } }); }); </script>关键点:categoryDao.getBookCountByCategory()方法只需写一条 SQL:
SELECT c.name, COUNT(b.id) as count FROM category c LEFT JOIN book b ON c.id = b.category_id GROUP BY c.id, c.name无需改任何已有代码,新功能就跑起来了。
6.3 增强点三:为学生增加“查看某书借阅历史”功能(复用现有 BorrowRecordDao)
原项目只能查自己的借阅记录,现在让学生能查任意书的借阅流水(如查《算法导论》被哪些人借过)。
步骤 1:新增 ServletBookHistoryServlet.java:
int bookId = Integer.parseInt(request.getParameter("bookId")); List<BorrowRecord> records = borrowRecordDao.findByBookId(bookId); request.setAttribute("records", records); request.getRequestDispatcher("/jsp/student/book_history.jsp").forward(request, response);步骤 2:在 book_detail.jsp 加链接
<a href="book/history?bookId=${book.id}">查看借阅历史</a>步骤 3:DAO 层加方法BorrowRecordDao.java中:
public List<BorrowRecord> findByBookId(int bookId) throws SQLException { String sql = "SELECT br.*, u.username FROM borrow_record br " + "JOIN user u ON br.user_id = u.id " + "WHERE br.book_id = ? ORDER BY br.borrow_time DESC"; // 执行查询,封装为 BorrowRecord 列表(注意:br.* 和 u.username 字段需一一映射) }为什么这个改造最值得学?
因为它完美体现了“高内聚、低耦合”:只加了一个 DAO 方法、一个 Servlet、一个 JSP 页面,所有逻辑都在BorrowRecordDao内部,BookService和BookDao完全不用动。从那以后我每次接到类似需求,都强制走一遍“先查 DAO 有没有现成方法,没有就加 DAO,再加 Servlet,最后加 JSP”的三步流程——它让我在 30 分钟内就能交付一个可演示的功能点,而不是花半天纠结要不要重构 Service 层。
希望帮到你。
本文还有配套的精品资源,点击获取