资讯动态

JSP购物车实战:用Session实现用户私有数据持久化

发布时间:2026/11/6 3:52:13 来源:尧图企业网站定制
简介这是一份面向Java Web初学者的轻量级购物车系统实战案例聚焦JSPServletTomcatSession核心技术栈帮助开发者快速掌握电商基础功能的实现逻辑与前后端协同机制。资源共62个文件包含13个可读性良好的Java源码覆盖Entity、DAO、Service、Servlet等分层结构、6个JSP页面如listBook.jsp、listCart.jsp、index.jsp实现动态交互视图、26个编译后class文件及5个关键XML配置文件含web.xml和数据库连接配置辅以HTML、JS前端脚本和2个JAR依赖库整体包体仅916KB结构清晰、开箱即用。已有671人学习下载适合课堂实验、课程设计或自学练手。读者可直接部署到Tomcat运行完整观察商品浏览→添加→展示→清空的全流程深入理解Session状态管理、请求转发与MVC分层思想并通过源码目录结构直观掌握Java Web项目标准组织方式。1. 购物车案例【简单版】一个能跑通、能改、能讲清 session 作用域的 JSP 入门实战项目你有没有试过在 Tomcat 里部署一个 JSP 页面点“加入购物车”后刷新页面就丢数据不是代码写错了而是没搞懂session在哪存、怎么取、什么时候失效——这恰恰是初学 Web 开发最常卡住的黑匣子。这个「购物车案例【简单版】」就是专为捅破这层窗户纸设计的它不堆功能不加 Spring不用数据库纯 JSP Java Servlet 内存 Session三页 HTML 两个 Java 类 一个 web.xml 配置就能完整演示用户登录态维持、商品添加/删除、数量修改、总价实时计算全过程。适合刚学完 HTTP 协议、正要接触 Web 容器作用域的 Java 新手也适合需要快速搭个教学 Demo 给学生看 session 生命周期的某高校实训导师。它不是玩具而是把 session 这个抽象概念变成你能在浏览器里点、删、改、看得到变化的真实对象。2. 环境准备与项目结构解析Tomcat 9 JDK 8 是当前最稳的组合这个项目对环境要求极低但版本错配会直接导致HttpSession不可用或getServletContext()报空指针——我踩过三次坑最后一次才意识到是 Tomcat 版本和 web.xml 的 schema 不匹配。下面按真实部署顺序拆解。2.1 运行环境最低配置清单实测通过组件推荐版本为什么不是更高JDK1.8.0_361Tomcat 9 默认编译级别为 1.8用 JDK 17 会导致javac编译.java文件失败报Unsupported class file major version 61Tomcat9.0.85支持web.xml中web-app的version3.1且对session.setAttribute()的线程安全处理更成熟Tomcat 10 强制使用 Jakarta EE 命名空间所有javax.servlet.*包全变jakarta.servlet.*本项目未适配浏览器Chrome 115 或 Edge 115需支持SameSiteLax的 Cookie 策略否则部分场景下 session ID 不会随请求自动携带提示不要用 IDE 自带的内嵌 Tomcat如 IntelliJ 的 Tomcat Server 插件它常默认启用Use war exploded模式但忽略WEB-INF/classes下的编译结果。务必手动解压.rar后将整个shoppingcart目录拷贝到$TOMCAT_HOME/webapps/下用bin/startup.batWindows或bin/startup.shLinux/macOS启动。2.2 项目目录结构逐层说明共 12 个文件无冗余shoppingcart/ ├── index.jsp ← 首页展示商品列表 “加入购物车”按钮 ├── cart.jsp ← 购物车页显示已选商品、数量输入框、删除链接、结算按钮 ├── checkout.jsp ← 结算页仅显示“下单成功”模拟跳转 ├── WEB-INF/ │ ├── web.xml ← 核心配置servlet 映射 session 超时设为 30 分钟 │ └── classes/ │ └── CartItem.class ← 编译后的商品项类对应 CartItem.java │ └── ShoppingCart.class ← 编译后的购物车类对应 ShoppingCart.java └── images/ └── apple.jpg ← 示例商品图实际可删不影响逻辑注意.rar包中不含.java源文件但Cart.java和ShoppingCart.java的源码逻辑完全内嵌在cart.jsp和index.jsp的% %脚本块中——这是本项目“简单版”的关键设计所有业务逻辑用脚本写死不分离 Java 类降低新手理解门槛。你后续要改造第一件事就是把它们抽成独立.java文件。2.3 web.xml 关键配置解读3 行决定 session 生效与否?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 session-config session-timeout30/session-timeout !-- 单位分钟 -- /session-config /web-app这段配置的作用远不止“设超时”。它强制 Tomcat 启用标准 session 机制若删掉session-configTomcat 仍会创建 session但session.isNew()永远返回truesession.getId()每次刷新都变若version2.5老式 DTDsession-config可能被忽略导致setMaxInactiveInterval(1800)在 Java 代码中无效schemaLocation必须严格匹配 Tomcat 9 的 XSD 地址否则启动时报cvc-complex-type.2.4.a: Invalid content was found starting with element session-config。3. 核心模块拆解从 index.jsp 到 cart.jsp 的 session 数据流转链路购物车本质是“用户私有数据在多次 HTTP 请求间的延续”。本项目用最直白的方式实现所有数据存在HttpSession对象里key 固定为cartvalue 是一个ArrayListCartItem。下面从点击“加入”开始走一遍完整链路。3.1 index.jsp商品列表渲染与添加入口含防重复提交逻辑% page contentTypetext/html;charsetUTF-8 languagejava % % page importjava.util.* % html headtitle商品首页/title/head body h2欢迎来到商城/h2 % // 1. 从 session 获取购物车若无则新建 HttpSession session request.getSession(true); ListMapString, Object cart (ListMapString, Object) session.getAttribute(cart); if (cart null) { cart new ArrayList(); session.setAttribute(cart, cart); } // 2. 模拟商品库实际应从 DB 或缓存读 ListMapString, Object products new ArrayList(); MapString, Object apple new HashMap(); apple.put(id, p001); apple.put(name, 红富士苹果); apple.put(price, 5.8); products.add(apple); MapString, Object banana new HashMap(); banana.put(id, p002); banana.put(name, 进口香蕉); banana.put(price, 12.5); products.add(banana); % table border1 trth商品/thth单价/thth操作/th/tr % for (MapString, Object p : products) { % tr td% p.get(name) %/td td¥% p.get(price) %/td td !-- 3. 关键用 GET 提交带 product_id 参数 -- a hrefcart.jsp?actionaddid% p.get(id) %name% p.get(name) %price% p.get(price) % 加入购物车 /a /td /tr % } % /table pa hrefcart.jsp查看购物车 (% cart.size() %)/a/p /body /html逻辑说明request.getSession(true)是关键true表示“如果 session 不存在就创建一个”确保后续setAttribute有地方存商品数据硬编码在 JSP 里是为了避免引入 JDBC 或 JSON 解析等干扰项a hrefcart.jsp?...是故意不用 form 提交——因为 GET 最直观参数明文可见方便调试cart.size()实时显示购物车数量证明session.getAttribute(cart)已生效。3.2 cart.jsp增删改查四合一页面session 数据的 CRUD 全覆盖% page contentTypetext/html;charsetUTF-8 languagejava % % page importjava.util.* % % HttpSession session request.getSession(false); // false不自动创建新 session if (session null) { response.sendRedirect(index.jsp); return; } ListMapString, Object cart (ListMapString, Object) session.getAttribute(cart); if (cart null) { cart new ArrayList(); session.setAttribute(cart, cart); } // 1. 处理 action 参数 String action request.getParameter(action); if (add.equals(action)) { String id request.getParameter(id); String name request.getParameter(name); String priceStr request.getParameter(price); double price Double.parseDouble(priceStr); // 查重同 id 商品只增加数量不重复添加 boolean found false; for (MapString, Object item : cart) { if (id.equals(item.get(id))) { int qty ((Integer) item.get(qty)).intValue(); item.put(qty, qty 1); found true; break; } } if (!found) { MapString, Object newItem new HashMap(); newItem.put(id, id); newItem.put(name, name); newItem.put(price, price); newItem.put(qty, 1); cart.add(newItem); } } else if (remove.equals(action)) { String id request.getParameter(id); cart.removeIf(item - id.equals(item.get(id))); } else if (update.equals(action)) { String id request.getParameter(id); String qtyStr request.getParameter(qty); int qty Integer.parseInt(qtyStr); if (qty 0) { for (MapString, Object item : cart) { if (id.equals(item.get(id))) { item.put(qty, qty); break; } } } else { // qty0 视为删除 cart.removeIf(item - id.equals(item.get(id))); } } // 2. 计算总价 double total 0.0; for (MapString, Object item : cart) { double price ((Double) item.get(price)).doubleValue(); int qty ((Integer) item.get(qty)).intValue(); total price * qty; } % html headtitle购物车/title/head body h2我的购物车/h2 % if (cart.isEmpty()) { % p购物车为空请先去 a hrefindex.jsp首页/a 添加商品/p % } else { % table border1 trth商品/thth单价/thth数量/thth小计/thth操作/th/tr % for (MapString, Object item : cart) { % tr td% item.get(name) %/td td¥% item.get(price) %/td td form methodpost actioncart.jsp input typehidden nameaction valueupdate/ input typehidden nameid value% item.get(id) %/ input typenumber nameqty value% item.get(qty) % min1 stylewidth:50px/ input typesubmit value更新/ /form /td td¥% ((Double)item.get(price)) * ((Integer)item.get(qty)) %/td td a hrefcart.jsp?actionremoveid% item.get(id) %删除/a /td /tr % } % tr td colspan3 alignrightstrong总计/strong/td tdstrong¥% total %/strong/td td/td /tr /table pa hrefcheckout.jsp去结算/a | a hrefindex.jsp继续购物/a/p % } % /body /html参数说明request.getSession(false)此处必须用false因为用户可能直接访问cart.jsp而未经过index.jsp若用true会创建空 session导致后续逻辑混乱cart.removeIf(...)是 Java 8 的简洁写法比传统 for 循环少写 5 行数量输入框用form methodpost是为了防止用户狂点“更新”导致 URL 参数过长GET 有长度限制小计列用((Double)item.get(price)) * ((Integer)item.get(qty))强制类型转换避免null导致NullPointerException。4. 常见问题排查5 个真实翻车现场与血泪修复方案开发这类 JSP 小项目80% 的时间花在解决“明明代码没错但就是不生效”的玄学问题上。以下是我在某公司内部培训中收集的 5 个高频故障按现象→原因→解决三步法整理4.1 现象点击“加入购物车”后跳转到 cart.jsp但购物车始终为空原因index.jsp中request.getSession(true)创建了 session但cart.jsp里request.getSession(false)返回null导致cart被重置为空ArrayList。根本原因是浏览器禁用了 Cookiesession ID 无法通过 Cookie 传递每次请求都是新 session。解决Chrome 地址栏左端点锁图标 → “网站设置” → “Cookie” → 改为“允许”或在web.xml中添加cookie-confighttp-onlyfalse/http-only/cookie-config仅测试用生产禁用终极验证打开浏览器开发者工具F12→ Application → Cookies确认JSESSIONID存在且值不变。4.2 现象修改商品数量后页面刷新数量又变回 1原因cart.jsp中update逻辑里item.put(qty, qty)执行了但session.setAttribute(cart, cart)没调用JSP 脚本块执行完后cart变量只是局部引用session里的原始ArrayList并未更新。解决在update分支末尾强制刷新 sessionsession.setAttribute(cart, cart); // 必加否则修改不持久4.3 现象中文商品名显示为乱码如“红富士苹果”原因Tomcat 默认用ISO-8859-1解码 URL 参数而index.jsp中a hrefcart.jsp?name红富士苹果的中文未编码。解决方案一推荐在index.jsp中用URLEncoder.encode()编码a hrefcart.jsp?actionaddid% p.get(id) %name% java.net.URLEncoder.encode((String)p.get(name), UTF-8) %price% p.get(price) %方案二在cart.jsp开头加request.setCharacterEncoding(UTF-8)仅对 POST 有效GET 无效。4.4 现象Tomcat 启动报错java.lang.ClassNotFoundException: CartItem原因.rar包中WEB-INF/classes/下只有.class文件但cart.jsp脚本里写了new CartItem()而CartItem类未定义。实际项目中该类并不存在——所有商品数据用MapString, Object代替。解决删掉cart.jsp中所有new CartItem()相关代码统一用HashMap构造或补全CartItem.java并重新编译见第 5 章。4.5 现象同一浏览器开两个 tabA tab 加商品B tab 刷新后看不到原因HttpSession是以JSESSIONIDCookie 为标识的两个 tab 共享同一个 Cookie所以本应同步。若不同步说明某个 tab 的 session 已过期被服务器销毁而另一个 tab 还拿着旧 ID 发请求Tomcat 返回 404 或空白页。解决检查web.xml中session-timeout是否设得太小如 1在cart.jsp开头加 session 有效性判断if (session null || session.getAttribute(cart) null) { session request.getSession(true); session.setAttribute(cart, new ArrayList()); }5. 二次开发实战从脚本驱动到面向对象重构附可直接运行的 CartItem.java现在你已经跑通了基础版下一步必然是把它变成可维护、可测试、可扩展的工程。核心动作就一个把散落在 JSP 里的 Map 操作封装成真正的 Java Bean。我一般会分三步走每步都有明确验证方式。5.1 第一步补全 CartItem.java 并编译让 IDE 不再标红在WEB-INF/classes/同级新建src/目录放入CartItem.java// src/CartItem.java public class CartItem { private String id; private String name; private double price; private int qty; public CartItem(String id, String name, double price, int qty) { this.id id; this.name name; this.price price; this.qty qty; } // getter/setter 省略IDE 自动生成即可 public String getId() { return id; } public void setId(String id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } public double getPrice() { return price; } public void setPrice(double price) { this.price price; } public int getQty() { return qty; } public void setQty(int qty) { this.qty qty; } public double getSubtotal() { return price * qty; } }编译命令在src/目录下执行javac -d ../WEB-INF/classes CartItem.java验证重启 Tomcat访问cart.jsp不再报ClassNotFoundException。5.2 第二步替换 cart.jsp 中所有 Map 操作为 CartItem 实例原代码cart.jsp 片段MapString, Object newItem new HashMap(); newItem.put(id, id); newItem.put(name, name); newItem.put(price, price); newItem.put(qty, 1); cart.add(newItem);改为CartItem newItem new CartItem(id, name, price, 1); cart.add(newItem);同时遍历逻辑也要改% for (CartItem item : cart) { % tr td% item.getName() %/td td¥% item.getPrice() %/td td form methodpost actioncart.jsp input typehidden nameaction valueupdate/ input typehidden nameid value% item.getId() %/ input typenumber nameqty value% item.getQty() % min1 stylewidth:50px/ input typesubmit value更新/ /form /td td¥% item.getSubtotal() %/td tda hrefcart.jsp?actionremoveid% item.getId() %删除/a/td /tr % } %注意此时cart的类型必须是ListCartItem所以session.getAttribute(cart)的强转要同步改ListCartItem cart (ListCartItem) session.getAttribute(cart); if (cart null) { cart new ArrayList(); session.setAttribute(cart, cart); }5.3 第三步抽取 ShoppingCart.java 作为业务门面解耦核心逻辑新建src/ShoppingCart.javaimport java.util.*; public class ShoppingCart { private ListCartItem items; public ShoppingCart() { this.items new ArrayList(); } public void addItem(CartItem item) { OptionalCartItem existing items.stream() .filter(i - i.getId().equals(item.getId())) .findFirst(); if (existing.isPresent()) { existing.get().setQty(existing.get().getQty() item.getQty()); } else { items.add(item); } } public void removeItem(String id) { items.removeIf(item - item.getId().equals(id)); } public void updateQuantity(String id, int qty) { if (qty 0) { removeItem(id); } else { items.stream() .filter(item - item.getId().equals(id)) .findFirst() .ifPresent(item - item.setQty(qty)); } } public double getTotal() { return items.stream() .mapToDouble(CartItem::getSubtotal) .sum(); } public ListCartItem getItems() { return new ArrayList(items); // 返回副本防外部修改 } public int getItemCount() { return items.size(); } }然后在cart.jsp中用它替代裸List% HttpSession session request.getSession(false); ShoppingCart cart (ShoppingCart) session.getAttribute(cart); if (cart null) { cart new ShoppingCart(); session.setAttribute(cart, cart); } String action request.getParameter(action); if (add.equals(action)) { String id request.getParameter(id); String name request.getParameter(name); double price Double.parseDouble(request.getParameter(price)); cart.addItem(new CartItem(id, name, price, 1)); } else if (remove.equals(action)) { cart.removeItem(request.getParameter(id)); } else if (update.equals(action)) { String id request.getParameter(id); int qty Integer.parseInt(request.getParameter(qty)); cart.updateQuantity(id, qty); } %验证方式修改商品数量后cart.getTotal()返回值实时变化在index.jsp中加% cart.getItemCount() %数字与 cart.jsp 一致关闭浏览器再打开session.getAttribute(cart)仍不为 null证明对象序列化正常。从那以后我每次接手 JSP 教学项目都强制走一遍“脚本 → Java Bean → Service 层”的三步重构。不是为了炫技而是让学生亲手把MapString, Object这种万金油容器变成有行为、有约束、有边界的真正对象——这才是 Java Web 开发的第一课。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑