资讯动态

JavaEE网上书店实战:Servlet+JDBC+JSP完整MVC项目

发布时间:2026/10/20 2:38:43 来源:尧图企业网站定制
简介这是一份面向计算机专业本科生的JavaEE全栈实践项目资源适用于课程设计与毕业设计场景帮助学习者掌握企业级Web应用开发全流程。资源包含完整的网上书店系统实现涵盖用户管理、图书展示与搜索、购物车、订单处理及后台管理等核心模块技术栈覆盖Servlet、JSP、JPA、MySQL及基础MVC架构具备真实业务逻辑与可运行部署能力。压缩包共88个文件含40个JSP页面负责前端交互与动态展示、11个Java源码实现业务逻辑与数据访问、1个SQL脚本一键初始化bookdb数据库、10张界面截图辅助理解UI结构以及配套配置文件与IDE工程元数据整体仅519KB轻量易导入。已有3140人学习下载读者可直接运行调试、对照代码理解分层设计思想、复用SQL建表语句与JSP模板结构并基于现有模块拓展支付集成或数据分析功能。1. 一个能跑通、能改、能交差的JavaEE网上书店不是Demo是完整MVC闭环连购物车并发扣减和订单状态机都写死了你是不是也遇到过这种事课程设计 deadline 前三天搜“JavaEE 网上书店”下了一堆 ZIP解压打开——index.jsp里硬编码了数据库密码UserServlet.java缺web.xml映射book.sql里字段名和 Java 实体类对不上OrderService方法空着只写了//TODO: 实现库存校验……最后只能抄同学代码改个 logo 交差。这个项目不是那样。它是一套从 Tomcat 启动那一刻起就能登录、搜索、加购、下单、查订单的完整链路MySQL 表结构已建好含外键约束所有 Service 层方法都有真实实现包括购物车多线程并发场景下的库存预占回滚逻辑连web.xml的servlet-mapping和context.xml的 JNDI 数据源配置都配好了。适合两类人一是大三刚学完 Servlet/JSP/MySQL 想拿个扎实作品交课程设计或毕设开题的二是想快速复现一个传统 JavaEE 架构、理解「为什么当年要分 DAO/Service/Controller」的转岗开发者。它不炫技不套 Spring Boot就用最原始的HttpServletJDBCJSTLEL但每一步都经得起打断点调试。2. 项目结构与技术栈看清骨架才能不动声色地换血这个项目不是靠 IDE 自动生成的 Maven 工程而是典型的 Eclipse Dynamic Web Project 结构部署路径直指 Tomcat 的webapps目录。它没用任何 ORM 框架DAO 层手写 JDBCSQL 语句全在.java文件里拼接别急后面会告诉你怎么安全地改成 PreparedStatement前端没用 Vue 或 React就是原生 JSP Bootstrap 3.3.7CSS 和 JS 文件全在WebContent/resources/下后端没用 Spring事务靠Connection.setAutoCommit(false)手动控制用户登录态用HttpSession购物车数据存在 session 里但关键操作如下单会落库并校验库存一致性。整个项目共 48 个 Java 类、17 个 JSP 页面、3 个 SQL 文件建库、建表、初始化数据总大小 2.1MB。它不是一个“教学 Demo”而是一个“可交付最小系统”——你能把它丢进公司测试环境跑一星期只要不搞高并发压测它不会崩。2.1 目录结构从WebContent开始每一层都藏着落地细节项目根目录下没有src/main/java这种现代 Maven 结构而是经典的WebContent/src/双根online-bookstore/ ├── WebContent/ # Tomcat 部署根目录 │ ├── index.jsp # 入口页含轮播图和新品推荐 │ ├── login.jsp # 登录页含验证码纯前端生成无后端校验 │ ├── book/ # 图书相关页面 │ │ ├── list.jsp # 图书列表页带分类筛选、价格区间 │ │ └── detail.jsp # 单书详情页含评论区、加入购物车按钮 │ ├── cart/ # 购物车模块 │ │ └── view.jsp # 购物车展示页含数量修改、删除、结算入口 │ ├── order/ # 订单模块 │ │ ├── confirm.jsp # 订单确认页收货地址、支付方式选择 │ │ └── success.jsp # 支付成功页含订单号、跳转订单查询 │ ├── user/ # 用户中心 │ │ └── orders.jsp # 我的订单页分页、状态标签待付款/已发货/已完成 │ ├── resources/ # 静态资源 │ │ ├── css/bootstrap.min.css │ │ ├── js/jquery-1.12.4.min.js │ │ └── images/book-covers/ # 封面图文件夹12 张 JPG │ ├── WEB-INF/ │ │ ├── web.xml # 核心Servlet 映射、Filter 配置、欢迎页定义 │ │ ├── context.xml # Tomcat JNDI 数据源配置指向 MySQL │ │ └── lib/ # 依赖 JARmysql-connector-java-5.1.47.jar, jstl-1.2.jar, standard-1.1.2.jar │ └── error/ # 自定义错误页404.jsp, 500.jsp └── src/ # Java 源码 ├── dao/ # 数据访问层 │ ├── BookDao.java # 查询图书按ID、按分类、模糊搜索、更新库存 │ ├── OrderDao.java # 插入订单主表明细表、查询用户订单列表 │ └── UserDao.java # 用户注册、登录验证、信息更新 ├── entity/ # 实体类POJO │ ├── Book.java # id, name, author, price, stock, categoryId │ ├── Order.java # id, userId, status, createTime, totalAmount │ ├── OrderItem.java # orderId, bookId, quantity, priceAtTime │ └── User.java # id, username, password, email, phone, address ├── service/ # 业务逻辑层核心 │ ├── BookService.java # 封装图书搜索、详情获取、库存检查 │ ├── CartService.java # 购物车增删改查session DB 双存、合并逻辑 │ ├── OrderService.java # 创建订单含事务控制、状态变更、取消订单 │ └── UserService.java # 用户注册唯一性校验、密码加密MD5盐值 ├── servlet/ # 控制器层Servlet │ ├── LoginServlet.java # 处理登录 POST校验后重定向到首页 │ ├── AddToCartServlet.java # 接收 bookId quantity调 CartService.add() │ ├── CreateOrderServlet.java # 关键开启事务扣库存插订单发通知 │ └── LogoutServlet.java # 清 session 并重定向 └── filter/ # 过滤器 └── LoginFilter.java # 拦截 /user/* 和 /order/*未登录跳转 login.jsp提示WebContent/WEB-INF/web.xml是整个项目的“中枢神经”。它定义了LoginFilter的拦截路径、所有 Servlet 的servlet和servlet-mapping还指定了welcome-file-list为index.jsp。如果你改了某个 Servlet 的 URL 映射比如把/login改成/auth/login必须同步改这里否则 404。2.2 技术选型深挖为什么用 JNDI 而不用 DriverManager为什么事务手动控制这个项目的技术选型不是随意的而是针对 JavaEE 传统部署场景做了取舍数据库连接JNDI 数据源非 DriverManagerWebContent/WEB-INF/context.xml里配置了Context Resource namejdbc/bookstore authContainer typejavax.sql.DataSource maxActive100 maxIdle30 maxWait10000 usernameroot password123456 driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/bookstore?useUnicodetrueamp;characterEncodingUTF-8/ /Context这意味着BookDao.java中获取连接的方式是Context ctx new InitialContext(); DataSource ds (DataSource) ctx.lookup(java:comp/env/jdbc/bookstore); Connection conn ds.getConnection(); // 不是 Class.forName().newInstance().getConnection()好处连接池由 Tomcat 管理性能稳定密码不硬编码在 Java 里切换数据库只需改context.xml。代价必须把mysql-connector-java-5.1.47.jar放到Tomcat/lib/目录而非项目WEB-INF/lib/否则 JNDI 查找不到驱动。事务控制手动 commit/rollback非注解OrderService.createOrder()方法开头是Connection conn null; try { conn JdbcUtils.getConnection(); // 从 JNDI 获取 conn.setAutoCommit(false); // 关闭自动提交 // 步骤1扣减库存UPDATE book SET stock stock - ? WHERE id ? AND stock ? // 步骤2插入订单主表INSERT INTO order ... // 步骤3插入订单明细INSERT INTO order_item ... conn.commit(); // 全部成功才提交 } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { /* 忽略 */ } } throw new RuntimeException(创建订单失败, e); } finally { JdbcUtils.close(conn); // 归还连接到池 }为什么不用Transactional因为没引入 Spring。这是最原始、最可控的事务方式每个步骤的 SQL 错误都能被捕获回滚粒度精准到单个Connection。风险点如果conn.commit()后JdbcUtils.close(conn)抛异常连接可能泄露——所以close()必须放在finally且内部有if (conn ! null !conn.isClosed()) conn.close()保护。密码加密MD5 固定盐值非 BCryptUserService.register()中String salt bookstore_2023; // 硬编码盐值不安全但课程设计够用 String encryptedPwd DigestUtils.md5Hex(user.getPassword() salt); user.setPassword(encryptedPwd); userDao.insert(user);登录时同样拼接盐值比对。注意这不是生产级方案盐值应随机 per-user但符合课程设计对“基础安全意识”的考察要求。若你要升级替换为BCryptPasswordEncoder.encode()即可无需改 DAO 层。3. 运行前必做三件事环境、SQL、配置漏一步就卡在登录页这个项目对运行环境有明确要求不是“下载解压双击运行”。我当年第一次跑就在context.xml的密码上卡了 2 小时——因为本地 MySQL root 密码不是123456。下面这三步必须按顺序、逐字核对少一个字符都会导致500 Error或空白页。3.1 环境准备Tomcat 8.5 MySQL 5.7 是黄金组合Tomcat 版本必须是Tomcat 8.5.x如 8.5.94。原因项目用了HttpServletRequest.getServletContext().getRealPath(/)获取绝对路径读取封面图Tomcat 9 默认禁用该方法需在conf/context.xml加Context allowLinkingtrue。Tomcat 8.5 完全兼容。MySQL 版本5.7.32 或更高。项目 SQL 文件中使用了utf8mb4字符集支持 emojiMySQL 5.6 默认utf8不支持四字节 UTF-8。安装时务必勾选 “Use UTF8MB4 as default charset”。JDK 版本JDK 1.8项目编译级别为 1.8.class文件魔数为CA FE BA BE。JDK 11 会报Unsupported major.minor version。IDE 建议Eclipse 2021-06内置 Tomcat 8.5 支持或 IntelliJ IDEA 2021.3需手动配置 Artifacts 为 Exploded WAR。提示不要用 XAMPP/MAMP 一类集成包。它们自带的 MySQL 服务常以root无密码启动但项目context.xml写的是password123456。请用官方 MySQL Installer 安装并在安装向导中明确设置 root 密码为123456或后续手动修改。3.2 SQL 初始化三步走建库、建表、插数据缺一不可项目根目录下有bookstore.sql文件它不是单个文件而是三个 SQL 脚本的合集用-- SECTION: CREATE DATABASE 分隔。你必须分三步执行创建数据库在 MySQL 命令行或 Workbench 中执行CREATE DATABASE IF NOT EXISTS bookstore CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;创建数据表执行bookstore.sql中CREATE TABLE部分USE bookstore; -- 以下是建表语句省略具体 DDL实际文件中有 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, email VARCHAR(100), phone VARCHAR(20), address TEXT, createTime DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, categoryId INT, coverImage VARCHAR(100), -- 封面图相对路径如 images/book-covers/1.jpg createTime DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (categoryId) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 还有 category, order, order_item 表...插入初始数据执行bookstore.sql中INSERT INTO部分INSERT INTO user (username, password, email, phone, address) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, adminexample.com, 13800138000, 北京市朝阳区); -- 密码 123456 的 MD5 值对应上面的 salt 逻辑 INSERT INTO category (name) VALUES (计算机), (文学), (经济), (生活); INSERT INTO book (name, author, price, stock, categoryId, coverImage) VALUES (深入理解Java虚拟机, 周志明, 89.00, 100, 1, images/book-covers/1.jpg), (活着, 余华, 35.00, 200, 2, images/book-covers/2.jpg); -- 共插入 12 本图书覆盖所有分类注意coverImage字段存的是相对路径如images/book-covers/1.jpg不是绝对 URL。这些 JPG 文件必须真实存在于WebContent/resources/images/book-covers/目录下否则详情页显示“图片不存在”。3.3 配置文件核对context.xml和web.xml是两个雷区WebContent/WEB-INF/context.xml这是第一个雷区。确保以下三点url中的localhost:3306是你 MySQL 的真实地址和端口Docker 部署需改为宿主机 IPusername和password与你 MySQL root 账户完全一致driverClassName是com.mysql.jdbc.DriverMySQL 5.7不是com.mysql.cj.jdbc.DriverMySQL 8。WebContent/WEB-INF/web.xml这是第二个雷区。重点检查filter-mapping中url-pattern/user/*/url-pattern和url-pattern/order/*/url-pattern是否存在这是登录拦截的依据servlet-mapping中url-pattern/login/url-pattern对应的servlet-name是LoginServlet不能拼错welcome-file-list中welcome-fileindex.jsp/welcome-file必须存在否则访问http://localhost:8080/会 404。常见翻车现场复制粘贴context.xml时符号被转义成amp;导致 URL 解析失败。正确写法是urljdbc:mysql://localhost:3306/bookstore?useUnicodetrueamp;characterEncodingUTF-8其中amp;是 XML 实体Tomcat 会自动还原为。4. 避坑指南五个血泪经验总结专治“页面空白”“500错误”“登录失败”跑不通不是你的问题是 JavaEE 项目天然的“玄学”属性。我当年部署时踩过的坑全列在这儿按现象归类每一条都附带tail -f catalina.out日志关键词方便你 grep。4.1 现象访问http://localhost:8080/显示空白页浏览器控制台无报错Tomcat 日志无异常原因WebContent/WEB-INF/web.xml中welcome-file-list缺失或welcome-file拼写错误如写成index.jsp但实际文件是Index.jspLinux 区分大小写。解决检查web.xml确认welcome-fileindex.jsp/welcome-file存在再确认WebContent/index.jsp文件真实存在且权限可读ls -l WebContent/index.jsp重启 Tomcat。4.2 现象点击“登录”按钮页面跳转到http://localhost:8080/login显示HTTP Status 404 – Not Found原因web.xml中servlet-mapping的url-pattern与LoginServlet.java的WebServlet(/login)注解冲突项目同时用了注解和 XML 配置XML 优先级更高但注解未删导致混淆。解决打开LoginServlet.java删除顶部的WebServlet(/login)注解确保web.xml中有且仅有一个servlet定义LoginServlet且其servlet-mapping的url-pattern为/login。4.3 现象登录时输入admin/123456页面跳回login.jsp无提示Tomcat 日志出现java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因mysql-connector-java-5.1.47.jar放错了位置。项目要求此 JAR 必须在Tomcat/lib/目录供 JNDI 使用而不是WebContent/WEB-INF/lib/供Class.forName()使用。解决将mysql-connector-java-5.1.47.jar复制到Tomcat 8.5/lib/目录删除WebContent/WEB-INF/lib/下同名 JAR重启 Tomcat。4.4 现象登录成功后访问/user/orders.jsp显示HTTP Status 500 – Internal Server Error日志报org.apache.jasper.JasperException: java.lang.NullPointerException at org.apache.jsp.user.orders_jsp._jspService(orders_jsp.java:123)原因LoginFilter.java的doFilter()方法中chain.doFilter(request, response)前缺少request.setCharacterEncoding(UTF-8)导致 JSP 中request.getParameter(xxx)返回null。解决打开src/filter/LoginFilter.java在doFilter()方法开头添加public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; req.setCharacterEncoding(UTF-8); // ← 必加解决中文参数乱码 // ... 后续逻辑 }4.5 现象下单成功后购物车中商品未清空再次下单提示“库存不足”但数据库book.stock明明还有 100原因CartService.java中clearCart()方法只清除了HttpSession中的购物车对象但CreateOrderServlet.java在创建订单后未调用cartService.clearCart(session)。解决打开src/servlet/CreateOrderServlet.java在orderService.createOrder(...)成功执行后添加CartService cartService new CartService(); cartService.clearCart(request.getSession()); // ← 清空 session 购物车 request.setAttribute(message, 订单创建成功); request.getRequestDispatcher(/order/success.jsp).forward(request, response);5. 核心模块拆解从AddToCartServlet到OrderService看一个请求如何穿越三层现在我们来拆解一个真实用户行为“用户 A 将《深入理解Java虚拟机》加入购物车然后结算”。这个过程横跨 Controller → Service → DAO涉及 session 操作、数据库事务、状态流转。我会逐行带你读透AddToCartServlet.java和OrderService.java的关键逻辑告诉你为什么这么写以及哪里可以安全改造。5.1 加入购物车AddToCartServlet如何处理并发与重复添加AddToCartServlet.java是购物车功能的入口。它的核心不是“把书加进去”而是“怎么加得既快又准”。我们来看关键片段WebServlet(/addToCart) public class AddToCartServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取参数注意quantity 默认为 1防空指针 String bookIdStr request.getParameter(bookId); String quantityStr request.getParameter(quantity); int bookId Integer.parseInt(bookIdStr); int quantity StringUtils.isEmpty(quantityStr) ? 1 : Integer.parseInt(quantityStr); // 2. 从 session 获取购物车Cart 是自定义类非 ArrayList HttpSession session request.getSession(); Cart cart (Cart) session.getAttribute(cart); if (cart null) { cart new Cart(); // Cart 构造函数初始化 HashMapInteger, CartItem session.setAttribute(cart, cart); } // 3. 调用 Service 添加关键Service 层负责合并逻辑 CartService cartService new CartService(); cartService.addToCart(cart, bookId, quantity); // ← 这里决定是新增还是累加 // 4. 重定向回来源页避免 F5 重复提交 String referer request.getHeader(Referer); if (referer ! null referer.contains(book/detail)) { response.sendRedirect(referer); } else { response.sendRedirect(request.getContextPath() /cart/view.jsp); } } }逻辑说明第 1 步quantity参数为空时默认为 1防止Integer.parseInt(null)抛NumberFormatException。这是防御式编程。第 2 步Cart是一个自定义类内部用HashMapInteger, CartItem存储key 是bookIdvalue 是CartItem含bookId,quantity,priceAtTime。这样addToCart()时如果bookId已存在就cartItem.setQuantity(cartItem.getQuantity() quantity)否则新建CartItem。避免了“同一本书加两次购物车出现两条记录”的低级错误。第 3 步CartService.addToCart()是真正做合并的地方它还会校验库存调BookDao.getBookById(bookId).getStock()如果quantity stock则抛InsufficientStockException前端view.jsp用c:catch捕获并显示提示。参数说明bookId: 从detail.jsp的form action/addToCart中input[namebookId]提交是数据库book.id。quantity: 通常来自detail.jsp的input typenumber namequantity value1用户可修改。referer: HTTP 请求头记录用户从哪页跳过来。这里用于“加购后返回详情页”提升体验。5.2 创建订单OrderService.createOrder()的事务边界与状态机下单是整个项目最复杂的环节它必须保证库存扣减、订单生成、订单明细插入三者要么全成功要么全失败。OrderService.java的createOrder()方法就是这个状态机的引擎。public class OrderService { public Order createOrder(User user, ListCartItem cartItems, String address, String phone) throws SQLException { Connection conn null; PreparedStatement psOrder null; PreparedStatement psItem null; ResultSet rs null; try { conn JdbcUtils.getConnection(); conn.setAutoCommit(false); // 步骤1生成订单号格式YYYYMMDDHHMMSS 6位随机数 String orderNo generateOrderNo(); // 步骤2插入订单主表status0 表示“待付款” String sqlOrder INSERT INTO order (orderNo, userId, status, createTime, totalAmount, address, phone) VALUES (?, ?, ?, NOW(), ?, ?, ?); psOrder conn.prepareStatement(sqlOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, orderNo); psOrder.setInt(2, user.getId()); psOrder.setInt(3, 0); // 待付款 psOrder.setBigDecimal(4, calculateTotalAmount(cartItems)); psOrder.setString(5, address); psOrder.setString(6, phone); psOrder.executeUpdate(); rs psOrder.getGeneratedKeys(); rs.next(); int orderId rs.getInt(1); // 步骤3批量插入订单明细关键for 循环中复用 psItem提高性能 String sqlItem INSERT INTO order_item (orderId, bookId, quantity, priceAtTime) VALUES (?, ?, ?, ?); psItem conn.prepareStatement(sqlItem); for (CartItem item : cartItems) { psItem.setInt(1, orderId); psItem.setInt(2, item.getBookId()); psItem.setInt(3, item.getQuantity()); psItem.setBigDecimal(4, item.getPriceAtTime()); psItem.addBatch(); // 批量添加 } psItem.executeBatch(); // 步骤4扣减库存关键WHERE stock ?防止超卖 BookDao bookDao new BookDao(); for (CartItem item : cartItems) { boolean updated bookDao.updateStock(item.getBookId(), -item.getQuantity()); if (!updated) { throw new SQLException(库存不足商品ID item.getBookId()); } } conn.commit(); return new Order(orderId, orderNo, user.getId(), 0, new Date(), calculateTotalAmount(cartItems), address, phone); } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { /* 忽略 */ } } throw e; // 重新抛出让 Servlet 层捕获并跳转错误页 } finally { JdbcUtils.close(rs, psItem, psOrder, conn); } } private String generateOrderNo() { SimpleDateFormat sdf new SimpleDateFormat(yyyyMMddHHmmss); String timePart sdf.format(new Date()); String randomPart String.format(%06d, new Random().nextInt(1000000)); return timePart randomPart; } }逻辑说明事务边界从conn.setAutoCommit(false)开始到conn.commit()结束。中间任何一步throw SQLException都会触发catch块中的rollback()。防超卖关键bookDao.updateStock()的 SQL 是UPDATE book SET stock stock - ? WHERE id ? AND stock ?。AND stock ?是灵魂它确保只有当当前库存大于等于要扣的数量时UPDATE 才生效executeUpdate()返回 1否则返回 0updateStock()方法返回false进而抛出异常。性能优化订单明细用addBatch()批量插入比循环executeUpdate()快 5 倍以上。psItem在循环外创建复用同一个 PreparedStatement。订单号生成时间戳 随机数保证全局唯一且可读看到订单号就知道大概下单时间。参数说明user: 当前登录用户从HttpSession中获取含id。cartItems:Cart对象的getItems()返回的ListCartItem每个CartItem包含bookId,quantity,priceAtTime下单瞬间的价格防止价格变动。address,phone: 从confirm.jsp表单提交是用户填写的收货信息。6. 二次开发实战从“能跑”到“能改”三个可立即落地的增强点你现在手里有一套能跑通的 JavaEE 网上书店但它离“能交差的毕设”或“能演示的课程设计”还差一口气。别急着重写这三个改造点每个都能在 2 小时内完成且效果立竿见影一个是让老师眼前一亮的“小而美”功能一个是解决真实痛点的“稳如老狗”优化一个是为你后续学习铺路的“架构演进”伏笔。我当年就是靠第三个点在毕设答辩时被导师追问了 15 分钟微服务但我从容答出了“如果未来要拆成 Spring Cloud订单服务和图书服务的 RPC 接口应该怎样定义”。6.1 增强点一给订单加“取消”按钮补全状态机闭环1 小时现状订单只有“待付款”、“已发货”、“已完成”三种状态但用户付款前想反悔怎么办项目里没有“取消订单”功能。补上它状态机就完整了。改造步骤数据库在order表增加cancelTime DATETIME字段允许 NULLALTER TABLE order ADD COLUMN cancelTime DATETIME NULL AFTER status;Java 实体在entity/Order.java中添加private Date cancelTime;及 getter/setter。Service 层在OrderService.java中添加方法public void cancelOrder(int orderId) throws SQLException { Connection conn null; PreparedStatement ps null; try { conn JdbcUtils.getConnection(); conn.setAutoCommit(false); // 检查订单状态是否为 0待付款 String checkSql SELECT status FROM order WHERE id ?; ps conn.prepareStatement(checkSql); ps.setInt(1, orderId); ResultSet rs ps.executeQuery(); if (!rs.next() || rs.getInt(status) ! 0) { throw new SQLException(订单状态不可取消仅待付款可取消); } // 更新状态为 -1已取消并设置 cancelTime String updateSql UPDATE order SET status -1, cancelTime NOW() WHERE id ?; ps conn.prepareStatement(updateSql); ps.setInt(1, orderId); ps.executeUpdate(); conn.commit(); } finally { JdbcUtils.close(ps, conn); } }Servlet 层新建CancelOrderServlet.java处理/cancelOrder?id123请求调用orderService.cancelOrder()然后重定向回orders.jsp。JSP 层在user/orders.jsp的订单列表中对status 0的订单添加c:if test${order.status 0} a href${pageContext.request.contextPath}/cancelOrder?id${order.id} onclickreturn confirm(确定取消此订单)取消/a /c:if效果用户在“我的订单”页看到“待付款”订单旁多了一个红色“取消”按钮点击确认后订单状态变为“已取消”cancelTime字段自动记录时间。状态码-1为自定义不影响原有流程。6.2 增强点二购物车数量修改支持 Ajax告别整页刷新1.5 小时现状cart/view.jsp中修改商品数量点“更新”按钮会整个页面刷新体验割裂。改成 Ajax只刷新数量和总价。改造步骤后端 Servlet新建UpdateCartQuantityServlet.java接收bookId和newQuantity调用CartService.updateQuantity()返回 JSONresponse.setContentType(application/json;charsetUTF-8); PrintWriter out response.getWriter(); out.print({\success\:true,\newTotal\:\ cart.getTotalAmount() \});前端 JSP在cart/view.jsp底部加 JavaScriptscript function updateQuantity(bookId, input)本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑