尧图精选

JSP网上书店系统实战:Servlet+JavaBean架构与购物车订单实现

🕒 发布时间:2026/9/17 19:45:51 📁 来源:尧图网络
简介一份计算机相关专业毕业设计论文文档主题为基于JSP的网上书店系统的设计与实现适合用于JSP/Java Web方向的毕业设计、课程设计以及论文写作参考。文档完整涵盖中英文摘要、关键词、目录、概述、系统可行性研究、需求分析、系统设计、数据库设计及实现等核心章节基于JSPMySQL技术方案通过与ASP对比详述网上书店的图书展示、在线下单、网上支付、后台图书管理、订单处理等功能模块。资源为单个DOC文档共1个文件大小1.09MB可直接打开阅读。文档中关于系统可行性分析和数据库模式设计的章节能够帮助读者理解从需求到实现的完整过程尤其适合作为毕业设计开题与论文撰写的参考资料。当前已有352人学习对于正在构思网上书店类课题、需要借鉴JSP项目论文结构或学习电子商务系统设计思路的读者而言这份资源具有较高参考价值。1. JSP 网上书店系统毕业设计里最稳的 Java Web 练手题网上书店这个题目在毕业设计里的出现频率几乎等同于 Java Web 课程里的 Hello World。原因很简单它覆盖了 Servlet、JSP、JavaBean、JDBC、Session、Cookie 这几个核心知识点业务模型又足够直观——用户注册登录、图书展示检索、购物车、下单、订单管理每一块都能对应到真实电商系统的简化版本。相比学生管理系统或图书管理系统网上书店多了一层“选品—加购—结算”的状态流转做起来有区分度答辩时有话可讲。但这道题也是翻车重灾区。很多人的实现停留在“JSP 里写 Java 代码、JDBC 查询直接拼 SQL”的阶段系统能跑但问几个问题就露馅JSP 本质是什么为什么页面里写 % % 不好购物车存 Session 还是存数据库分页为什么越翻越慢这些恰恰是面试官和答辩老师最爱追问的。这篇博文就以“基于 JSP 的网上书店系统”为骨架完整拆解一套可以直接落地的小型网上书店系统从 JSPServletJavaBean 的三层结构到用户模块、图书检索、购物车与订单的实现细节再到部署调试时的常见坑。适合正在做毕业设计的学生也适合想快速捡起传统 JSP 开发的在职工程师。2. JSP 网上书店的系统架构与核心机制2.1 为什么是 JSPServletJavaBean而不是纯 JSP网上书店系统的数据流是这样的浏览器发起请求服务器需要查询数据库中的图书信息处理用户登录状态计算购物车金额最后把结果渲染成 HTML 返回。如果全部用 JSP 实现等于把业务逻辑和页面展示揉在一起一个点改动就会牵连整个页面。常见的做法是 JSPServletJavaBean 三层协作对应 MVC 中的 View、Controller、Model。浏览器 → Servlet控制器接收请求、调用业务逻辑 → JavaBean模型封装数据、访问数据库 ↑ | └──────────── JSP视图只做展示 ←────────────────────┘这个结构下JSP 页面里不应该出现import java.sql.*或new UserDao()这样的代码。它只负责从 request 或 session 里取数据、用 JSTL 和 EL 表达式输出内容。Servlet 做的是接收参数、调用 DAO、把结果放进 request 域、forward 到 JSP。JavaBean准确说是 DAO 类负责 JDBC 操作和业务校验。一个很容易被忽略的点是JSP 本质上会被容器编译成 Servlet。Tomcat 会把 JSP 先翻译成.java文件再编译成.class。所以“JSP 比 Servlet 性能差”这种说法并不准确真正的差异在于——JSP 适合做展示层Servlet 适合做控制层把逻辑写进 JSP 会拖慢首次编译速度也让维护变得困难。2.2 项目目录结构与对应职责一个标准 JSP 网上书店项目的 Web 应用目录长这样bookstore/ ├── src/ │ ├── com/bookstore/bean/ # 实体类Book, User, Order, CartItem │ ├── com/bookstore/dao/ # 数据访问对象BookDao, UserDao, OrderDao │ ├── com/bookstore/servlet/ # 控制器LoginServlet, RegServlet, CartServlet... │ └── com/bookstore/util/ # 工具类DBUtil, PageUtil ├── WebContent/ │ ├── index.jsp # 首页图书列表 │ ├── login.jsp # 登录页 │ ├── register.jsp # 注册页 │ ├── bookDetail.jsp # 图书详情 │ ├── cart.jsp # 购物车页面 │ ├── orderConfirm.jsp # 订单确认页 │ ├── orderList.jsp # 我的订单 │ ├── admin/ # 后台管理页面可选 │ ├── css/ js/ images/ │ └── WEB-INF/ │ ├── web.xml # Servlet 映射、欢迎页配置 │ └── lib/ # 依赖 jar 包实体类JavaBean负责在页面和业务逻辑之间搬运数据。比如 Book 类有 id、title、author、price、stock 这些字段用 private 修饰提供 getter/setter。DAO 类是直接和数据库打交道的层它的方法签名要尽量靠近业务语义——findBookByKeyword(String keyword)而不是query(String sql)。2.3 JDBC 连接与连接池选型网上书店系统必然涉及数据库操作JDBC 是基础但直接在每个 DAO 方法里DriverManager.getConnection()是不可取的。频繁创建连接的开销很大而且一旦数据库连接数被耗尽系统直接挂掉。常见做法是引入连接池。如果你的项目跑在 Tomcat 8/9 上用容器自带的 JNDI 数据源是最省事的如果不想碰 JNDI用 Apache DBCP2 或 HikariCP 也都可以。以 DBCP2 为例在src下放一个dbcp.properties# 数据库连接配置 driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/bookstore?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf-8 usernameroot password123456 initialSize5 maxTotal20 maxIdle10 minIdle5 maxWaitMillis3000后面写一个静态工具类 DBUtil代码如下// DBUtil.java — 统一管理连接池与连接获取 import org.apache.commons.dbcp2.BasicDataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static BasicDataSource ds; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(dbcp.properties)) { Properties props new Properties(); props.load(in); ds new BasicDataSource(); ds.setDriverClassName(props.getProperty(driverClassName)); ds.setUrl(props.getProperty(url)); ds.setUsername(props.getProperty(username)); ds.setPassword(props.getProperty(password)); ds.setInitialSize(Integer.parseInt(props.getProperty(initialSize))); ds.setMaxTotal(Integer.parseInt(props.getProperty(maxTotal))); ds.setMaxWaitMillis(Long.parseLong(props.getProperty(maxWaitMillis))); } catch (Exception e) { throw new ExceptionInInitializerError(数据库连接池初始化失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return ds.getConnection(); // 从连接池取连接 } }这段代码的逻辑静态代码块在类加载时执行一次创建 BasicDataSource 并设置连接参数。后续所有 DAO 都调用DBUtil.getConnection()获取连接。注意maxWaitMillis3000表示获取连接的最长等待时间超过直接抛异常避免线程无限阻塞。maxTotal20是连接池最大连接数小型网上书店系统 20 个连接完全够用。提示不要把连接池参数写死在代码里。改成 properties 文件部署时只要改配置文件就行不用重新编译。答辩时这个点很加分。3. 核心功能落地用户模块与图书检索的分页实现3.1 登录、注册与个人信息展示页面的 Session 处理用户模块是网上书店的门面它包含注册、登录、退出和个人信息展示。先看登录流程的 Servlet 代码// LoginServlet.java — 处理登录请求 WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); UserDao dao new UserDao(); User user dao.findByUsernameAndPassword(username, password); if (user null) { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } else { req.getSession().setAttribute(currentUser, user); resp.sendRedirect(req.getContextPath() /index); // 重定向到首页避免表单重复提交 } } }这里有个细节值得注意登录成功用的是sendRedirect而不是forward。原因在于——如果是 forward用户刷新页面时会把上次的 POST 请求再发一次造成重复登录操作。重定向让浏览器重新发起 GET 请求符合 PRG 模式Post-Redirect-Get。登录之后个人信息展示页面对应热搜词“jsp个人信息展示页面”是怎么取数据的用户在login.jsp输入账号密码后登录信息被放进 Session之后每个页面需要展示用户名时直接用 EL 表达式%-- header.jsp 片段 — 展示当前登录用户 --% c:choose c:when test${not empty sessionScope.currentUser} span欢迎${sessionScope.currentUser.username}/span a hreflogout退出/a /c:when c:otherwise a hreflogin.jsp请登录/a /c:otherwise /c:choose为什么从 sessionScope 取而不是 request因为 request 是一次请求内有效而登录状态需要在多个页面间持续保持。Session 的生命周期默认是 30 分钟无操作失效这一点在答辩时经常被问到。如果需要更长的保持时间可以调session.setMaxInactiveInterval(3600)或在 Cookie 上设置持久化时间。注册模块的逻辑和登录类似但多一个“校验用户名是否已存在”的步骤。密码存储建议做 MD5 加盐哈希后再入库不要明文存储——这也是答辩时老师喜欢的提问点之一。3.2 图书检索的 SQL 写法与分页参数传递网上书店的图书列表页是最核心的页面。如果图书数据量不大几千条以内一次性查出所有记录也能接受但一旦数据量到万级全表查询就会拖垮页面。小型网上书店系统一般用 MySQL 的LIMIT offset, size做物理分页。分页的核心逻辑在 DAO 中封装// BookDao.java — 分页查询方法 public ListBook findBooksByPage(String keyword, int page, int pageSize) { ListBook list new ArrayList(); String sql SELECT id, title, author, price, cover_path, stock FROM book WHERE title LIKE ? OR author LIKE ? ORDER BY id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String kw % keyword %; ps.setString(1, kw); ps.setString(2, kw); ps.setInt(3, (page - 1) * pageSize); ps.setInt(4, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book b new Book(); b.setId(rs.getInt(id)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setPrice(rs.getDouble(price)); b.setCoverPath(rs.getString(cover_path)); b.setStock(rs.getInt(stock)); list.add(b); } } } catch (SQLException e) { e.printStackTrace(); } return list; }LIMIT 的两个参数要重点说明第一个是偏移量offset从第几条开始取计算方式是(当前页-1) × 每页条数第二个是返回的最大行数。如果用户要第 3 页、每页 10 条那么 SQL 就是LIMIT 20, 10跳过前 20 条取第 21 到 30 条。前端 JSP 页面需要同时知道当前页、总页数和总记录数。所以分页查询往往是两个方法配合一个查数据列表另一个SELECT COUNT(*)查总条数。把总条数存进 request算总页数就用简单公式totalPages (totalCount pageSize - 1) / pageSize。3.3 JSP 页面循环输出与 JSTL 替代 Scriptlet很多初学者写图书列表页时习惯在 JSP 里用% for (...) { %循环输出这虽然能跑但 JSP 页面会越来越臃肿。标准做法是用 JSTL 和 EL%-- index.jsp — 图书列表展示使用 JSTL 标签 --% % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body div classbook-grid c:forEach items${requestScope.bookList} varbook div classbook-card img src${book.coverPath} alt${book.title} / h4a hrefbookDetail?id${book.id}${book.title}/a/h4 p作者${book.author}/p p classprice${book.price}/p a hrefcart?actionaddbookId${book.id}加入购物车/a /div /c:forEach /div /body /htmlJSTL 的c:forEach和c:if、c:choose基本覆盖了 JSP 页面 90% 的流程控制需求。EL 表达式${book.price}等价于book.getPrice()底层是通过反射调用 getter 方法实现的。页面不出现 Java import 语句只用标签和表达式后期让前端改页面时对方不用懂 Java 也能改。分页条用 JSTL 生成也简单c:if test${page 1} a hrefindex?page${page - 1}keyword${keyword}上一页/a /c:if span第 ${page} / ${totalPages} 页/span c:if test${page totalPages} a hrefindex?page${page 1}keyword${keyword}下一页/a /c:if4. 购物车与订单模块的 Session 设计及事务处理4.1 购物车的三种存储方案对比购物车是网上书店系统里最能体现设计水平的部分。常见的存储方案有三种方案优点缺点适用场景Session 存储实现简单不占数据库空间服务器重启丢失集群环境不可用单机部署、毕业设计Cookie 存储跨请求保持服务端无状态容量 4KB 限制用户可以篡改游客购物车数据库存储永久保存可以在多设备间同步要建表每次操作多一次 IO真实电商系统毕业设计里用 Session 存储是最常见的做法也是老师默认接受的方案。好处在于——购物车本质是“当前用户在一段会话内的临时选择”不需要持久化Session 键值对结构天然适合。购物车的 Session 结构设计// 购物车条目一个图书ID 数量 public class CartItem { private int bookId; private String title; private double price; private int quantity; // 省略 getter/setter public double getSubTotal() { return price * quantity; // 单条小计 } }在 Servlet 中购物车本体是一个MapInteger, CartItem或者ListCartItem以 bookId 为 key。加购的逻辑先看 Session 里有没有购物车没有就创建再看购物车里有没有这本书有就数量 1没有就新增条目。// CartServlet.java — 添加图书到购物车 public void addBook(HttpServletRequest req, HttpServletResponse resp) throws IOException { int bookId Integer.parseInt(req.getParameter(bookId)); HttpSession session req.getSession(); // 从 Session 取出购物车没有就新建一个 MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } // 判断是否已存在同书条目存在则加数量 if (cart.containsKey(bookId)) { cart.get(bookId).setQuantity(cart.get(bookId).getQuantity() 1); } else { // 查图书信息构造新购物车条目 BookDao dao new BookDao(); Book book dao.findById(bookId); CartItem item new CartItem(); item.setBookId(book.getId()); item.setTitle(book.getTitle()); item.setPrice(book.getPrice()); item.setQuantity(1); cart.put(bookId, item); } resp.sendRedirect(req.getContextPath() /cart.jsp); }这个实现里最容易出问题的地方是 Session 中的对象类型。从session.getAttribute(cart)取出来的对象是Object类型强转到MapInteger, CartItem时会有 unchecked 警告但不影响运行。需要注意的是——Session 中的对象必须实现Serializable接口否则 Tomcat 做 Session 持久化时可能抛异常。4.2 订单生成的数据库事务边界从购物车结算到生成订单这个动作涉及多张表的写操作在 order 表插入订单主记录在 order_item 表插入订单明细每本书一条记录在 book 表扣减库存。任何一个步骤失败都会破坏数据一致性。所以这一步必须用事务包裹。先看订单表的 SQL 设计-- 订单主表 CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL COMMENT 订单号, user_id INT NOT NULL COMMENT 下单用户, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细表 CREATE TABLE order_item ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL COMMENT 所属订单ID, book_id INT NOT NULL COMMENT 图书ID, book_title VARCHAR(128) NOT NULL, price DECIMAL(10,2) NOT NULL COMMENT 下单时的价格快照, quantity INT NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意order_book表中的price存的是下单时的价格快照而不是关联 book 表实时查价格。因为图书价格可能会调整订单历史记录不能跟着变。这是一个很容易被忽略但极其重要的设计细节。订单生成的 DAO 代码// OrderDao.java — 创建订单事务版 public boolean createOrder(int userId, MapInteger, CartItem cart) { String insertOrder INSERT INTO orders(order_no, user_id, total_amount) VALUES(?, ?, ?); String insertItem INSERT INTO order_item(order_id, book_id, book_title, price, quantity) VALUES(?, ?, ?, ?, ?); String deductStock UPDATE book SET stock stock - ? WHERE id ? AND stock ?; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务关闭自动提交 // 1. 生成订单号并插入主表 String orderNo String.valueOf(System.currentTimeMillis()) userId; double total cart.values().stream().mapToDouble(CartItem::getSubTotal).sum(); try (PreparedStatement ps conn.prepareStatement(insertOrder, PreparedStatement.RETURN_GENERATED_KEYS)) { ps.setString(1, orderNo); ps.setInt(2, userId); ps.setDouble(3, total); ps.executeUpdate(); ResultSet keys ps.getGeneratedKeys(); int orderId 0; if (keys.next()) orderId keys.getInt(1); // 2. 插入明细并对每本书扣减库存 try (PreparedStatement psItem conn.prepareStatement(insertItem); PreparedStatement psStock conn.prepareStatement(deductStock)) { for (CartItem item : cart.values()) { psItem.setInt(1, orderId); psItem.setInt(2, item.getBookId()); psItem.setString(3, item.getTitle()); psItem.setDouble(4, item.getPrice()); psItem.setInt(5, item.getQuantity()); psItem.addBatch(); // 批量插入明细 psStock.setInt(1, item.getQuantity()); psStock.setInt(2, item.getBookId()); psStock.setInt(3, item.getQuantity()); psStock.addBatch(); } psItem.executeBatch(); int[] stocks psStock.executeBatch(); for (int affected : stocks) { if (affected 0) { throw new SQLException(库存不足图书ID: ???); // 实际开发中应包含更多上下文 } } } } conn.commit(); // 全部成功提交事务 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { e.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这段代码的逻辑分层很清晰先关自动提交插入订单主表拿到自增 ID再插入明细表和扣库存用 batch 批量执行减少与数据库的往返次数。扣库存的 SQL 里带着AND stock ?条件这一步是关键的防超卖手段——如果库存不够受影响行数为 0当场抛出异常整个事务回滚订单不会生成。4.3 清空购物车与订单列表展示下单成功后不要忘记清空 Session 中的购物车session.removeAttribute(cart)。如果不清用户会疑惑为什么下单后购物车里还有数据。订单列表展示则简单得多——查 orders 表关联用户 ID 排序再按 order_id 去 order_item 表查明细。这里有一个 N1 查询问题如果在循环里逐条查明细订单多的时候性能很差。解决方案是查完订单列表后用一条WHERE order_id IN (...)查出所有明细再在 Java 代码里按 orderId 分组。JSP 页面上展示订单状态建议用c:choose做文本映射c:choose c:when test${order.status 0}待付款/c:when c:when test${order.status 1}已付款/c:when c:when test${order.status 2}已发货/c:when c:otherwise已完成/c:otherwise /c:choose5. JSP 页面调试技巧编译定位、离开提示与 Tomcat 部署验证5.1 修改 JSP 不生效时先查 Tomcat 的 work 目录开发中最高频的困惑是改了 JSP 文件刷新浏览器却看不到变化。第一次遇到这种问题的人往往怀疑浏览器缓存换隐身窗口、清缓存折腾半天可能还是一样。问题很可能出在 Tomcat 的 work 目录——JSP 被编译成的.java和.class文件保存在这里Tomcat 检测 JSP 文件是否被修改依赖的是文件时间戳。如果 IDE 保存文件时没有正确更新时间戳或者复制文件时保留了旧的修改时间Tomcat 就不会触发重新编译。排查方法分两步。第一步直接删除 work 目录下的缓存# 以 Tomcat 9 为例默认目录结构下的清理命令 rm -rf /path/to/tomcat/work/Catalina/localhost/你的应用名删掉后重启 Tomcat访问那个 JSP 页面观察 work 目录下是否重新生成了.java文件。第二步如果你像我一样想确认 Tomcat 到底编译了什么可以直接打开这个文件# 查看 JSP 被翻译成的 Java 文件 find /path/to/tomcat/work -name index_jsp.java -type f | xargs grep -n out.writeindex_jsp.java是index.jsp编译后的 Servlet 源码。打开它能看到每个 JSP 标签和 Java 代码被翻译成什么样的out.write()调用。如果页面渲染结果不正常比如多了一段空白、少了一段内容在这个文件里搜索对应的 HTML 片段通常能定位到具体是 JSP 里哪一段代码的问题。对应热搜词里“web项目配置tomcat后查看jsp编译后的java类”说的就是这个操作。5.2 idea 创建 JSP 文件后报错的常见解决路径IntelliJ IDEA 里创建 JSP 文件比较简单——在 Web 目录上右键 New → JSP File。但很多人在创建后遇到红波浪线Cannot resolve taglib ...或者Package javax.servlet does not exist。这不是代码问题而是项目缺少 Servlet API 依赖。如果你用的是 Tomcat最简单的做法是把 Tomcat 关联到项目上File → Project Structure → Modules → 选择你的模块Dependencies 选项卡 → 右侧 号 → Library → 选择 Tomcat如果列表里没有点 Application Server Libraries → Add → 找到本机 Tomcat 安装目录选中servlet-api.jar和jsp-api.jarAdd Selected关联完成后JSP 的编译错误会消失WebServlet注解也能正常识别。还有一种情况是 IDLE 颜色方案里没有 JSP 语法高亮对应热搜词“intellij idea中color scheme中没有jsp”——这种只是配色问题到 Settings → Editor → Color Scheme → File Types 里把 .jsp 和 HTML/Java 关联一下就行不影响编译。真正影响运行的是依赖缺失优先级先解决这个。5.3 屏蔽 JSP 离开页面的重复提交提示表单提交后点击浏览器的“返回”按钮经常弹出一个“页面已过期”或“是否重新提交表单”的提示。这个提示本质是浏览器在告诉你——你刚才提交了一个 POST 请求现在你要回到的页面可能依赖那个请求。网上书店注册和登录都会遇到。正确解法是 PRG 模式前面登录代码里已经演示过POST 处理完后用sendRedirect让浏览器发起新的 GET 请求历史记录中的 POST 就被新的 GET 覆盖了。如果你在别人的代码里看到提交成功后还是forward到 JSP那个系统大概率就有这个弹窗问题。另外JSP 页面顶部还可以设置禁用缓存% response.setHeader(Cache-Control, no-store); response.setHeader(Pragma, no-cache); response.setDateHeader(Expires, 0); %这段代码的意义是告诉浏览器不要缓存当前页面防止用户点击后退时看到的是旧的页面快照。但注意——它并不能替代 PRG 模式两个配合使用效果才完整。5.4 验证订单模块正确性的手工测试清单订单模块涉及事务和库存扣减部署后需要走一遍完整的手工测试流程。我的习惯是至少覆盖这五条路径测试场景操作步骤预期结果正常下单加购 2 本书→结算→确认订单订单生成库存减少购物车清空库存不足把某本书数量改成超过库存→提交下单失败提示库存不足库存数据不变事务回滚连续提交两次相同订单并发第二次扣库存失败整体回滚订单表不新增记录数据库断连停掉 MySQL→提交订单系统不崩溃提示下单失败Session 过期清掉 Cookie→购物车加购购物车为空或跳转登录页测试库存不足时最好在数据库里开两个终端分别执行下单 SQL同时提交观察是否产生超卖。如果两个事务都成功了说明扣库存的 SQL 没有加stock ?条件需要回去补上。测试完这个清单答辩时被问“你怎么验证系统的正确性”就有话说了——你可以说用事务回滚测试验证了库存一致性用 PRG 模式验证了表单重复提交问题这些都是实打实的工程经验。5.4.1 用 JSP 页面输出分页耗时辅助定位慢查询如果分页页面翻到后面越来越慢先别急着优化代码。在 DAO 方法加上执行时间输出// BookDao.java — 打印 SQL 执行时间 long start System.currentTimeMillis(); // ... 执行查询代码 ... long cost System.currentTimeMillis() - start; System.out.println(分页查询耗时: cost ms, 页码: page);翻到第 1 页和第 50 页分别看耗时。如果耗时差好几倍说明 LIMIT 的偏移量太大MySQL 需要扫描并丢弃前面的行。优化手段是延迟关联-- 先用子查询定位 ID再关联原表取数据减少回表 SELECT b.* FROM book b INNER JOIN (SELECT id FROM book WHERE title LIKE ? ORDER BY id DESC LIMIT ?, ?) tmp ON b.id tmp.id ORDER BY b.id DESC;这种做法在小数据量下看不出差异但到 10 万条以上效果明显。毕业设计的数据量可能碰不到这个瓶颈但把这段优化写进论文的设计部分会让系统设计显得更有深度。JSP 开发虽然被视为“传统技术”但分页优化、事务边界、Session 生命周期这些东西放到现在的分布式系统里依然是同一套底层思维。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →