尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
JavaEE二手图书交易平台实战拆解:从JSP+Servlet+JDBC到部署避坑
简介基于JavaEE的二手图书交易平台设计源码与配套高分报告是一套面向Java Web课程设计、期末大作业的完整可运行项目。平台覆盖二手图书发布、浏览检索、下单购买、订单管理、后台数据维护等典型业务界面采用Bootstrap响应式布局整体风格清爽操作流畅管理便捷适合缺少实战经验的学生作为学习模板或直接提交的高分作业。压缩包共173个文件约25.68MB主要包括21个Java核心类、7个JSP动态页面、4个SQL建库脚本以及大量CSS、JS、字体图标等前端静态资源另附docx格式的设计报告涵盖系统架构、功能模块与数据库设计可配合代码一起阅读。代码中标有清晰注释关键逻辑易读部署步骤简单下载后导入SQL脚本、配置数据库并启动项目即可运行能快速支撑课程验收或答辩展示。目前已有132人学习下载不少同学借助它完成了高分大作业是Java Web入门者省时省力的实用选择。1. 这个 javaEE 二手图书交易平台到底能干什么不是页面 Demo而是一套能跑的交易闭环如果你正被期末课程设计逼到墙角想找一个能直接跑起来、又能在答辩时讲清楚逻辑的 JavaEE 项目这篇拆解值得你读完。它不是我见过的那种只有静态页面、点完按钮毫无反应的教学 Demo而是一整套交易闭环用户注册登录、图书发布、分类浏览、下单购买、订单管理、后台维护全部基于 JSP Servlet JDBC 的传统三层架构实现前端用 Bootstrap 和 SweetAlert 撑住交互体验。拆完这个项目我最深的印象是它的代码注释真的在讲「为什么这样做」而不是流水账式套话。下面按「设计 → 源码 → 部署 → 改造」的顺序把这套系统的底细一层层扒给你看。2. 技术栈与整体设计传统 JavaEE 三层架构 Bootstrap 前端的组合2.1 技术选型为什么是 JSP Servlet JDBC 而不是 Spring Boot这个项目的技术栈属于「JavaEE 必修课」式的组合表示层用 JSP 渲染页面控制层用 Servlet 接收请求数据访问层用 JDBC 直连 MySQL中间夹一个 Service 层处理业务逻辑。为什么课程设计大多这样选型因为绝大多数高校的 JavaEE 课程主线就是这一套答辩时老师默认你会被追问「JDBC 连接是怎么管理的」「Session 怎么保证登录状态」如果你把 Spring Boot 全家桶扔上来反而容易被怀疑不是自己写的。用传统 Servlet 能把请求到响应的每一环讲清楚这是课程设计的核心竞争力。从项目正文给出的静态资源能看到更具体的前端路线bootstrap.css、bootstrap.min.css、bootstrap-theme.css、sweetalert.css 这一串文件说明页面骨架用的是 Bootstrap 3 的栅格和组件弹窗提示用 SweetAlert 实现。bootstrap.css 和 bootstrap.min.css 同时存在是源码包里的常见做法开发时引用未压缩版方便调试部署时换压缩版减小体积。这种前端方案在课程设计里非常成熟你不必手写复杂 CSS能把精力聚焦在后端交易逻辑上。前端还有个细节值得注意项目里一般会把导航栏和页脚抽成公共 JSP 片段用 % include % 引入避免每个页面重复写同一段 HTML这种「去重」意识也是答辩可以讲的点。项目的持久层没有引入 MyBatis 或 Hibernate所有 SQL 都写在 DAO 类里。这对初学者反而是优势你能亲眼看一条 SQL 从 Java 字符串拼接到 PreparedStatement 预编译再进入 MySQL 执行的全过程。等以后学了 ORM 框架再回头看这种写法对「框架到底替你做了什么」的理解会深很多。我把整套分层结构整理成一张表后文讲代码时会反复用到这几层。层载体职责表示层JSP Bootstrap jQuery SweetAlert渲染页面、收集表单、弹出提示控制层Servlet接收请求、调用 Service、控制跳转业务层Service 类参数校验、组合 DAO、控制事务数据访问层DAO 类 JDBC DBUtil执行 SQL、封装结果集、管理连接基础设施Tomcat MySQL运行 Servlet 容器、持久化数据2.2 功能模块划分前台交易与后台管理两条业务线整个平台的功能可以拆成两条业务线来理解。前台是面向普通用户的交易闭环注册登录后用户可以发布闲置旧书填写书名、作者、出版社、原价、售价、分类、封面图片和成色描述也可以在首页或分类页按分页浏览所有在售图书点进详情看卖家的描述和书的完整信息。下单时支持「直接购买」和「加入购物车」两条路径订单生成后买家可以付款、确认收货卖家可以发货双方都能在自己的订单列表里看到进度。购物车的细节是点击「加入购物车」会把 book_id 和 user_id 插入 cart_item 表页面右上角显示购物车数量点击「结算」进入确认订单页生成订单后从购物车删除对应记录。后台是面向管理员的维护线。管理员登录后进入管理界面可以对用户做启用和禁用操作对违规图书做下架处理对订单状态做人工修正维护图书分类的增删改查。这里的权限控制不复杂就是通过 Session 里的登录用户角色判断管理员和普通用户在同一个 user 表里靠 role 字段区分。很多答辩的高分点都在这条线上——前台交易谁都能写但能把后台管理和前台流程的关联讲明白说明你对整个系统的数据流是真正理解的。整个系统落到页面大概是这些注册页、登录页、图书列表页带分类筛选和分页、图书详情页、发布图书页、购物车页、确认订单页、我的订单页、我卖出的页、后台用户管理页、后台图书管理页、后台订单管理页、后台分类管理页。2.3 数据库设计五张核心表如何支撑交易闭环二手图书交易的核心实体是用户、图书、订单和分类。这个项目的表结构不花哨但每张表的存在都有理由。user 表除了常规的用户名、密码、邮箱、电话设计了 role 字段区分普通用户和管理员省去了单独建管理员表的成本book 表通过 seller_id 关联 user 表记录卖家通过 category_id 关联分类表还有一个 status 字段表示图书状态——1 在售、2 已售出、3 已下架这三个状态在订单流程中起着关键作用。orders 表是整个交易的枢纽它同时记录 buyer_id 和 seller_id 两个外键一笔订单天然涉及买卖双方book_id 锁定成交的具体图书price 字段在订单生成那一刻从 book 表冗余过来。为什么冗余因为历史订单必须保留成交时的价格快照图书之后涨价或降价都不能影响已生成的订单这在数据建模里叫「快照字段」答辩讲到这能加分不少。订单状态在数据库里就是 orders.status 一个 int 字段在不同 UPDATE 语句里变化1 待付款、2 待发货、3 已发货、4 已完成、5 已取消。从设计角度把状态数字定义成常量类 OrderStatus 能避免魔法数字散落在代码里就算只写在注释里也算是状态机意识的体现。很多同学答辩被问「怎么防止一本书被两个人同时下单」思路就是事务加状态锁——同一本书只有 status1 时能被下单下单后立刻置为 2并发场景下数据库行锁保证只有一个事务成功这个逻辑要在报告里重点写。最后加上 category 分类表和 cart_item 购物车表整个数据模型就完整了。表名关键字段作用userid, username, password, role, status用户信息与权限标识bookid, title, author, price, cover, category_id, seller_id, status图书信息与在售状态categoryid, name图书分类ordersid, order_no, book_id, buyer_id, seller_id, price, status交易订单与状态流转cart_itemid, user_id, book_id, quantity购物车临时数据五张表之间有外键关系的只有 book→user、book→category、orders→book、orders→user 这几条整体复杂度非常适合画 E-R 图。接下来进入源码拆解环节这是最硬核的部分。3. 核心代码拆解一个请求从进入到返回的完整链路3.1 登录认证链路从页面表单到 Session 放行先看登录这个最高频的功能。JSP 页面里是一个 Bootstrap 样式的表单用户名和密码两个输入框提交到 /user/login。这个 Servlet 做的事情可以概括为三步取参数、调 Service 校验、把用户信息写入 Session。核心代码不长但每行都有讲究WebServlet(/user/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); 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 userService.login(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /book/list); } else { request.setAttribute(msg, 用户名或密码不对请重新输入); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }第一行 setCharacterEncoding(UTF-8) 作用于请求体必须写在读取参数之前否则 POST 提交的中文在 Servlet 里就是乱码。UserService 的 login 方法返回设计得很直接查不到用户返回 null查到了返回完整 User 对象调用方一次判断就够不用再拆成 boolean 加 user 的两个方法。登录成功用的是 sendRedirect 重定向到图书列表而不是 forward 转发——这会让浏览器地址栏变成 /book/list用户刷新页面不会重复触发登录请求这种细节是答辩时能讲出话的地方。登录校验的拦截逻辑独立在一个 Filter 中。因为发布图书、加入购物车、下单这些操作都要求用户先登录项目用一个过滤器统一拦截避免了在每个 Servlet 里重复写登录判断代码WebFilter(urlPatterns {/book/add, /cart/*, /order/*}) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpSession session req.getSession(false); if (session null || session.getAttribute(loginUser) null) { ((HttpServletResponse) response).sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }urlPatterns 用通配符覆盖了需要登录的路径范围/cart/* 和 /order/* 把所有交易相关操作全部拦住。注意 getSession(false) 和 getSession() 的区别false 表示只在 Session 已存在时返回不会强制创建一个新 Session未登录用户不会被白白塞一个 Session 对象减少内存残留。拦截范围内没登录就重定向回登录页已登录则调用 chain.doFilter 继续向后传递。登录放行后JSP 页面里通过 ${sessionScope.loginUser.username} 可以直接显示当前用户名EL 表达式取 Session 值非常方便。3.2 图书发布与分页列表文件上传和 LIMIT 查询图书发布是这个项目里最有技术含量的功能因为它要处理封面图片上传。项目用的是 Servlet 3.0 标准的文件上传方式不需要额外引 commons-fileupload 依赖只要给 Servlet 加上 MultipartConfig 注解再通过 request.getPart(cover) 拿到上传文件。代码拆开看是这样MultipartConfig(maxFileSize 1024 * 1024 * 5) WebServlet(/book/add) public class BookAddServlet extends HttpServlet { private BookService bookService new BookService(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); Part coverPart request.getPart(cover); String fileName System.currentTimeMillis() _ extractFileName(coverPart); String uploadPath request.getServletContext().getRealPath(/upload); File uploadDir new File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdirs(); } coverPart.write(uploadPath File.separator fileName); Book book new Book(); book.setTitle(request.getParameter(title)); book.setAuthor(request.getParameter(author)); book.setPrice(Double.parseDouble(request.getParameter(price))); book.setCover(/upload/ fileName); book.setCategoryId(Integer.parseInt(request.getParameter(categoryId))); // 其他字段赋值省略如出版社、成色描述等 bookService.addBook(book, (User) request.getSession().getAttribute(loginUser)); response.sendRedirect(request.getContextPath() /book/list); } private String extractFileName(Part part) { String header part.getHeader(Content-Disposition); for (String content : header.split(;)) { if (content.trim().startsWith(filename)) { return content.substring(content.indexOf() 1).trim().replace(\, ); } } return null; } }三个参数值得注意MultipartConfig 的 maxFileSize 限制单文件最大 5MB课程设计场景足够还能防止有人传大图拖慢 Tomcat存文件名时用 System.currentTimeMillis() 加下划线前缀避免不同用户上传同名的 cover.jpg 互相覆盖getRealPath(/upload) 拿到的是 Tomcat 实际部署目录下的物理路径这个路径在 IDEA 集成部署和手动部署时指向不同目录第 5 章我会专门讲这个坑。extractFileName 方法是从 Content-Disposition 请求头里解析浏览器传来的原始文件名因为上传文件的原名是编码状态随请求头到达的不能直接拿来写盘。图书发布成功后的数据展示靠的是列表页的分页查询。这个项目用的是最朴素的 LIMIT ?, ? 分页没有引入 PageHelper 插件对你理解分页原理反而更友好。DAO 层的关键代码public ListBook findPage(int categoryId, int pageNum, int pageSize) { StringBuilder sql new StringBuilder(SELECT * FROM book WHERE status 1); ListObject params new ArrayList(); if (categoryId 0) { sql.append( AND category_id ?); params.add(categoryId); } sql.append( ORDER BY create_time DESC LIMIT ?, ?); params.add((pageNum - 1) * pageSize); params.add(pageSize); // 通过 PreparedStatement 依次设置 params执行查询并封装结果集 }LIMIT 的下标必须用 (pageNum - 1) * pageSize这个公式很容易记错。MySQL 的 LIMIT 偏移量从 0 开始页码从 1 开始时第一页偏移量是 0第二页才是 pageSize第三页是 2 * pageSize。如果直接传 pageNum第一页会从第一条数据往后数漏掉第一条记录。categoryId 为 0 表示全部分类用条件拼接实现首页和分类页共用一套查询。每页条数 pageSize 在 Service 层由常量控制比如 12列表页底部再渲染一个带页码的分页导航条点击页码就带着 pageNum 参数重新请求这个 Servlet。3.3 下单与订单状态流转卖家、买家和状态机的设计二手交易和普通电商最大的差异是订单状态多了一个「卖家视角」。项目里订单状态是1 待付款、2 待发货、3 已发货、4 已完成、5 已取消。买家下单时创建订单同时把 book 表的 status 从 1 在售改成 2 已售出避免同一本书被两个人同时下单。这段逻辑的核心在 OrderService 里是事务编程的典型示范public boolean createOrder(int bookId, int buyerId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); Book book bookDao.findById(conn, bookId); if (book null || book.getStatus() ! 1) { conn.rollback(); return false; } Order order new Order(); order.setOrderNo(B System.currentTimeMillis()); order.setBookId(bookId); order.setBuyerId(buyerId); order.setSellerId(book.getSellerId()); order.setPrice(book.getPrice()); order.setStatus(1); orderDao.insert(conn, order); bookDao.updateStatus(conn, bookId, 2); conn.commit(); return true; } catch (Exception e) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { DBUtil.close(conn); } }事务是这段代码的灵魂。创建订单和把图书标记为已售出是两个独立的写操作任何一个失败都会造成数据不一致——订单建了但书还能被别人买或者书被锁死但订单没生成。所以这里手动 setAutoCommit(false)全部成功才 commit任何一步异常都 rollback。orderNo 用时间戳加字母前缀生成课程设计够用真实项目里并发下单可能撞号可以加上用户 id 或随机数。买家侧看到的「付款」「取消」卖家侧看到的「发货」「确认」本质都是更新 orders.status 一个字段一个 UPDATE 语句就能完成状态流转。订单列表查询则通过 buyer_id 或 seller_id 分别查「我买到的」和「我卖出的」两张列表共用 orders 表只是 where 条件不同这也是这个项目代码里利用率最高的一张表。4. 部署与运行从导入到跑通的全流程4.1 环境准备JDK、Tomcat、MySQL、IDE 的版本组合跑这个项目需要四样东西JDK 8 及以上、Tomcat 8.5 或 9、MySQL 5.7 或 8.0、一个支持 JavaWeb 开发的 IDEIDEA 社区版或 Eclipse。版本组合有个经验值JDK 8 配 Tomcat 8.5 最稳这是 JavaEE 课程设计最常见的底子。如果你机器上只有 Tomcat 10 要特别注意Tomcat 10 起把包名从 javax 换成了 jakarta传统 servlet-api 直接部署会编译不过要么换回 Tomcat 9要么全局替换包名后者工作量大且容易改出隐藏问题。提示如果本地用的是 Tomcat 10建议直接下载 Tomcat 9 版本对应项目省去一边部署一边报错的折腾。MySQL 版本的分水岭在 8.0。5.7 用旧版驱动 com.mysql.jdbc.Driver 没问题8.0 必须用 com.mysql.cj.jdbc.Driver并且连接字符串里要带 serverTimezone 参数。数据库建议新建一个独立库字符集用 utf8mb4再导入项目自带的建表脚本。IDE 方面IDEA 社区版完全够用导入传统 Web 工程时选 New → Project from Existing Sources一路找到项目的 Web 根目录Eclipse 则用 Import → Existing Projects into Workspace。另外JDBC 驱动 jar 要放在 WEB-INF/lib 下而不是随便建一个 lib 文件夹在 IDEA 里还要确认这个目录被加入了 Artifact否则启动后部署产物里没有驱动运行必报 ClassNotFoundException。4.2 导入工程与修改配置三处必须核对的地方把代码导入 IDE 之后先别急着启动按顺序核对三个地方。第一处是数据库初始化脚本。项目里一般带一个 .sql 文件或在报告附录里先执行 create database second_book default charset utf8mb4再导入脚本脚本里会有建表语句和初始管理员账号。导入后可以用客户端工具连上去看一眼确认五张表都在管理员账号能查出来。第二处是数据库连接配置常见位置是 src 下的 db.properties 文件或 DBUtil.java 里的常量重点改 url、username、password 三个值。下面是典型配置jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/second_book?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingUTF-8 jdbc.usernameroot jdbc.password123456connection 串里 useSSLfalse 是为了省掉 SSL 握手开销serverTimezoneAsia/Shanghai 解决 8.0 的时区报错characterEncodingUTF-8 配合建库时的 utf8mb4 保证中文存取不乱码。第三处是 web.xml 和部署配置。传统 JavaEE 项目里 web.xml 虽然被 WebServlet 注解部分替代但欢迎页配置通常还留在里面比如 指定 index.jsp 为默认入口。你还要检查 Servlet 映射是否和代码注解重复——如果同一个 URL 既在 web.xml 里配了又在注解里写了一遍Tomcat 启动时可能冲突。三处核对完建议单独写一个 main 方法测试 DBUtil 能否拿到连接确认数据库链路通了再动 Tomcat。这个习惯能把「数据库连接失败」和「项目启动失败」分开排查节省大量时间。4.3 按业务流跑通一遍用真实数据验证功能项目启动后别漫无目的地乱点按一条用户路径走一遍哪个环节断了就修哪块。我的建议路线是注册一个新账号登录用管理员账号登录后台确认新账号存在用新账号发布一本测试图书并上传封面回到首页验证这本书出现在列表和对应分类里再注册另一个账号搜索这本书、直接下单切回卖家账号发货买家账号确认收货最后去后台看一眼订单数据的落库情况。这一趟走完你的验证清单应该覆盖这些点注册时用户名重复有没有提示、图书发布后封面是否正常显示、订单生成后图书是否从首页撤下、卖家能否在「我卖出的」里看到这笔订单、管理员能否下架违规图书。如果流程走到某一步报错排查顺序很固定第一步看 Tomcat 的 catalina 日志和 localhost 日志定位是哪个 Servlet 抛的异常第二步看浏览器地址栏 URL确定请求确实发到了预期的 Servlet第三步查数据库对应表的数据看状态字段是否按预期变化。三步下来绝大多数问题都能定位到具体层。全部流程通过说明这个项目在你本机是真正完整的答辩时可以放心展示。5. 常见问题与避坑指南课程设计项目最容易翻车的几个地方5.1 数据库连接失败驱动类名和时区设置现象启动 Tomcat 后所有涉及数据库的页面都报 500控制台出现 ClassNotFoundException: com.mysql.jdbc.Driver 或 Communications link failure。原因九成是两种。一是 MySQL 驱动 jar 没有放进 WEB-INF/lib传统 JavaEE 工程不自动管理依赖jar 必须手动拷贝到部署目录下二是 MySQL 8.0 的驱动类名和 5.7 不一致连接字符串没带 serverTimezone 还会报时区无法识别那个报错信息里是乱码第一次见很容易懵。驱动 jar 版本也要匹配——用 5.7 的驱动跑 8.0 数据库会出现各种奇怪连接异常。解决把 mysql-connector-java-8.0.x.jar 放入 WEB-INF/lib 并重新部署连接串改成 jdbc:mysql://localhost:3306/second_book?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingUTF-8。改完先写个 main 方法走一遍 DBUtil确认能查出数据再启动 Web 项目。这条避坑经验值得刻在脑门上因为十个 JavaEE 课程设计至少三个人倒在这里。5.2 中文乱码三处编码要统一现象表单提交的中文在 Servlet 或页面里变成 ?? 或乱码或者数据库里显示的中文是问号。原因JavaWeb 中文编码要经过请求编码、响应编码、数据库编码三关任何一处有缺口都会乱。最常见的是 setCharacterEncoding 写在 getParameter 之后参数已经按错误编码解析完了JSP 页面没声明 UTF-8数据库表建的不是 utf8mb4。还有一个隐蔽点连接串里的 characterEncodingUTF-8 和 useUnicodetrue 最好成对出现有些驱动版本对 useUnicode 有隐式要求。解决每个 JSP 页头写 % page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %处理参数的 Servlet 在 doPost 第一行写 request.setCharacterEncoding(UTF-8)建库建表统一用 utf8mb4。三处都对了中文基本绝迹。Tomcat 8 起 GET 请求编码默认就是 UTF-8不需要再去改 server.xml 的 URIEncoding网上很多老教程还在教这个照做反而可能引入别的问题。5.3 上传图片重启就丢文件存储路径的误区现象封面图片上传后显示正常重启 Tomcat 或重新部署后全部 404 丢失。原因getRealPath(/upload) 返回的是当前部署目录下的物理路径IDEA 里部署会指向 target 目录而每次重新构建都会清理这个目录。你本地看到的 upload 文件夹只是编译产物的一个角落不是源工程的目录所以图片跟着构建一起被清掉了。这不是代码逻辑问题是「部署目录」与「源码目录」混淆造成的。很多同学在这里反复 Debug 怀疑人生其实方向就搞错了。解决两个方向。要么把上传目录改成项目外的固定路径比如 /data/upload 或 D:/upload写成常量在代码里引用不走 getRealPath——这样图片不会因为重新部署消失如果走外部路径还要在 Tomcat 的 server.xml 里加一个 Context 映射把 /upload 虚拟路径指向外部目录否则图片能存下来但访问不到。这个配置虽然麻烦但一劳永逸而且答辩时能讲出「生产环境」的思路。要么接受「演示环境不长期保存」的现实但必须能解释为什么刷新后 404。我一般强烈建议选择固定外部路径现场传一张图再刷新它还活着比解释源码有说服力得多。5.4 报告与代码对不上高分答辩的隐形杀手现象报告里写了「支持关键词搜索功能」源码里找不到对应的 Servlet报告 E-R 图画了六张表实际数据库脚本里只有五张答辩时老师随手翻开报告的某一个功能让你指代码在哪你圆不回来。原因很多下载资源的报告和源码不是同一个人维护的。报告描述的是「理想功能」代码实现的是「已实现功能」两者天然不同步。拿来做课程设计如果直接交被老师点破就是大型翻车现场。解决拿到资源后第一件事不是跑代码而是把报告目录和代码结构对一遍。报告里每个功能模块要去代码里找到对应 Servlet 和 Service 方法报告里每张表去数据库脚本里数一遍字段。有出入的地方要么改代码补齐功能要么改报告删掉不存在的描述。这个动作大概 40 分钟能让你从「拿着别人的代码硬讲」变成「真正掌控整个项目」答辩底气和效果完全不一样。从答辩角度看这份对账工作甚至比多跑几个功能更有价值。6. 二次开发思路把这个课程设计改造成拿得出手的作品6.1 加一个带分类过滤的关键词搜索如果原项目只有列表没有关键词搜索你可以很快补上。在 book 的查询 SQL 里加一个 LIKE 条件参数用「%关键词%」再搭一个带分类下拉框和搜索输入框的页面把 categoryId 和 keyword 一起提交到 /book/search就能得到一个完整的搜索功能。改造成本大约半小时但答辩演示效果立竿见影。if (keyword ! null !keyword.trim().isEmpty()) { sql.append( AND (title LIKE ? OR author LIKE ?)); params.add(% keyword %); params.add(% keyword %); }注意通配符拼接是在参数里做的不能拼进 SQL 字符串否则用户输入 % 或 _ 会被当成通配符产生意料之外的查询结果这也是 SQL 注入的入门级了解。LIKE 同时匹配书名和作者能覆盖二手书搜索的实际需求。6.2 用 MyBatis 替换 DAO 层的第一步如果你想让简历里能写「熟悉 MyBatis」可以用这个项目练手但别一口气全换。我建议只把 book 表的查询换成 MyBatis引入 mybatis 的 jar 和配置文件写一个 BookMapper.xml把原来 BookDao 里的 findPage、findById 两个方法搬进去Service 层照常调用。因为 Service 的方法签名没变Controller 层一行都不用动验证时只要原有分页和详情功能正常就说明替换成功。这种渐进式重构是真实项目里最常用的手法比推倒重来稳得多。6.3 验证改造的固定套路每次改完代码我强制自己走一遍那条业务流注册 → 登录 → 发布图书 → 列表可见 → 下单 → 发货 → 收货 → 后台核验。这条链路覆盖用户、图书、订单、权限四组表任何一步崩了都能立刻定位是哪一层出了问题。从那以后我每次拿到别人的 JavaWeb 课程设计都会先按这个动作过一遍再动手改代码少踩了非常多坑。这份源码和报告下载下来也建议你先用第 4 章的步骤跑通一遍再对照第 3 章的代码注释去读会比只看教程有用得多。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

硬盘分区合并全攻略:C盘扩容与无损合并方法详解

硬盘分区合并全攻略:C盘扩容与无损合并方法详解

1. 先说清楚:合并分区到底是怎么回事作为一个折腾电脑十几年的人,我隔三差五就会碰到有人来问:“我C盘又红了,能不能把D盘的空间匀给它?”或者“当时装系统分区没规划好,现在盘符又多又乱,能不能…

📅 2026/10/18 4:22:12
Django+Vue构建茶叶电商系统:从商品建模到订单全栈实战

Django+Vue构建茶叶电商系统:从商品建模到订单全栈实战

接到一个茶叶销售系统的开发需求时,我的第一反应是:这不就是个电商系统嘛。可等到把需求文档一条条过完,才发现茶叶这类目背后藏着一堆商品逻辑——品类分级、产地、工艺、年份、批次口感,甚至同一款茶换个山头味道就完全不一样。…

📅 2026/10/18 4:22:12
Milvus向量数据库实战:为AI助手打造长期记忆库

Milvus向量数据库实战:为AI助手打造长期记忆库

不知道你有没有遇到过这种情况:明明前一天跟AI助手聊过某件事,第二天再问它,它却跟失忆了一样,完全不记得对话内容。这种感觉就像在养一条金鱼——七秒的记忆,鱼缸转一圈就忘了自己刚才游到过哪。给AI加“长期记忆”&a…

📅 2026/10/18 4:22:12
MORE NEWS

更多资讯

📰

CSP期末预测之最佳阈值:从暴力枚举到排序递推的O(n log n)优化

1. 从"暴力枚举"到"排序递推":这道题到底在考什么如果你刷过CCF-CSP的历年真题,会发现一个很有意思的规律:第一题通常是"送分题",第二题开始上强度,而有些第二题看似简单,实…

📰

从TiDB 5.0看分布式数据库演进:HTAP、云原生与智能化运维

数据库领域正在发生巨变,从 TiDB 5.0 发布会看未来的数据库发展趋势。说实话,这几年分布式数据库已经从"要不要上"变成了"怎么上、上了之后怎么用好"的阶段。我自己是从 MySQL 分库分表一路折腾过来的,中间踩过不少坑&am…

📰

基于FOMIAUKF与分数阶模型的锂电池SOC估计研究与Matlab实现

做电池SOC估计的人,基本都绕不开一个问题:模型建得不够准,滤波算法再花哨也白搭。我早几年用整数阶等效电路模型配合扩展卡尔曼滤波(EKF)的时候,经常被低温和动态工况下的误差搞到头大——明明OCV曲线拟合得…

📰

直播CDN分发架构设计(进阶篇)最佳实践与踩坑记录

本文深入探讨直播CDN分发架构设计(进阶篇),涵盖背景分析、原理剖析、实战步骤、配置示例、优化建议和避坑指南。随着业务规模增长,直播CDN分发架构设计(进阶篇)的重要性日益凸显。无论你是刚入门还是资深工…

📰

Docker部署SRS直播集群:从单点到源站边缘架构实战

做直播服务的同学应该都听过SRS,这套开源流媒体服务器在RTMP、WebRTC、SRT这些协议上表现一直很稳。但不少朋友有个误区:觉得Docker里跑一个SRS容器,能推流能拉流就算完事了。真到了线上,单节点扛不住并发,推流源一挂整…

📰

异常的实现与代价:零成本异常、栈展开与 -fno-exceptions 什么时候用

C 异常常被贴上「零成本」的标签,可真正在生产里用它的人又总在担心性能。矛盾其实很好解释:「零成本」指的是不抛异常时的正常路径(happy path)不额外付代价,而一旦真的抛出,代价是巨大的:编译…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬