杰瑞科技汇

Java Session购物车如何实现与优化?

核心概念

  1. Session (会话):

    Java Session购物车如何实现与优化?-图1
    (图片来源网络,侵删)
    • 当用户第一次访问网站时,服务器会为该用户创建一个独一无二的 Session 对象。
    • 服务器会给这个 Session 对象一个唯一的 ID,称为 Session ID
    • 服务器将 Session ID 通过 Cookie 发送给浏览器,并保存起来。
    • 在后续的每一次请求中,浏览器都会自动携带这个 Cookie(包含 Session ID),服务器通过 Session ID 就能找到对应的 Session 对象,从而知道是哪个用户在访问。
    • Session 的生命周期:从用户访问开始,到用户关闭浏览器或 Session 超时(30 分钟)为止,在这期间,同一个用户的多次请求共享同一个 Session 对象。
  2. 购物车:

    • 购物车是一个临时的数据容器,用于存储用户在本次会话中想要购买的商品。
    • 每个用户的购物车应该是独立的,用户 A 的购物车里不能有用户 B 的商品。
    • 这与 Session 的特性完美契合:每个用户一个 Session,我们可以将每个用户的购物车数据存储在其对应的 Session 对象中。

实现步骤

我们将使用最基础的 Servlet + JSP 技术来实现,这样逻辑最清晰,如果你使用的是 Spring Boot 等框架,核心思想是相通的,只是 API 和配置方式不同。

第 1 步:准备项目环境

  1. 创建一个 Dynamic Web Project (动态 Web 项目)。
  2. 准备一个商品列表页面 products.jsp,用于展示所有商品。
  3. 准备一个购物车页面 cart.jsp,用于展示购物车中的商品。
  4. 准备一个 Product 类,用于封装商品信息。

第 2 步:创建 Product

这是一个简单的 Java Bean,用来表示一个商品。

// Product.java
package com.example.model;
public class Product {
    private int id;
    private String name;
    private double price;
    public Product(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
    // Getters and Setters
    public int getId() { return id; }
    public String getName() { return name; }
    public double getPrice() { return price; }
}

第 3 步:创建商品列表页面 products.jsp

这个页面展示所有商品,每个商品旁边都有一个“加入购物车”的链接。

Java Session购物车如何实现与优化?-图2
(图片来源网络,侵删)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List, com.example.model.Product" %>
<html>
<head>商品列表</title>
</head>
<body>
    <h1>欢迎光临我们的商店</h1>
    <table border="1">
        <tr>
            <th>商品ID</th>
            <th>商品名称</th>
            <th>商品价格</th>
            <th>操作</th>
        </tr>
        <%-- 模拟从数据库获取商品列表 --%>
        <%
            List<Product> products = List.of(
                new Product(1, "苹果手机", 6999.0),
                new Product(2, "华为笔记本", 8999.0),
                new Product(3, "小米耳机", 299.0)
            );
            request.setAttribute("productList", products);
        %>
        <c:forEach items="${productList}" var="product">
            <tr>
                <td>${product.id}</td>
                <td>${product.name}</td>
                <td>¥${product.price}</td>
                <td>
                    <%-- 点击链接,将商品ID提交给AddToCartServlet --%>
                    <a href="add-to-cart?id=${product.id}">加入购物车</a>
                </td>
            </tr>
        </c:forEach>
    </table>
    <br/>
    <a href="cart.jsp">查看我的购物车</a>
</body>
</html>

第 4 步:创建处理“加入购物车”逻辑的 Servlet (AddToCartServlet)

这是核心部分,Servlet 从请求中获取商品 ID,然后找到对应的商品,最后将其添加到 Session 中。

// AddToCartServlet.java
package com.example.servlet;
import com.example.model.Product;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/add-to-cart")
public class AddToCartServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 从请求参数中获取商品ID
        String productIdStr = request.getParameter("id");
        if (productIdStr == null || productIdStr.isEmpty()) {
            response.sendRedirect("products.jsp");
            return;
        }
        int productId = Integer.parseInt(productIdStr);
        // 2. 从Session中获取购物车列表
        // getAttribute返回的是Object,需要强制转换
        // 如果是第一次获取,Session中可能没有购物车,所以需要处理null的情况
        HttpSession session = request.getSession();
        List<Product> cart = (List<Product>) session.getAttribute("cart");
        if (cart == null) {
            // 如果购物车为空,说明是第一次添加,创建一个新的ArrayList
            cart = new ArrayList<>();
            session.setAttribute("cart", cart);
        }
        // 3. 根据ID查找商品 (实际项目中应该从数据库查询)
        Product productToAdd = findProductById(productId);
        if (productToAdd != null) {
            // 4. 将商品添加到购物车列表
            cart.add(productToAdd);
        }
        // 5. 重定向到购物车页面,避免刷新页面重复提交
        response.sendRedirect("cart.jsp");
    }
    // 模拟根据ID查找商品的方法
    private Product findProductById(int id) {
        List<Product> allProducts = List.of(
            new Product(1, "苹果手机", 6999.0),
            new Product(2, "华为笔记本", 8999.0),
            new Product(3, "小米耳机", 299.0)
        );
        for (Product p : allProducts) {
            if (p.getId() == id) {
                return p;
            }
        }
        return null;
    }
}

第 5 步:创建购物车页面 cart.jsp

这个页面从 Session 中获取购物车列表并展示。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List, com.example.model.Product" %>
<html>
<head>我的购物车</title>
</head>
<body>
    <h1>我的购物车</h1>
    <%
        // 从Session中获取购物车
        List<Product> cart = (List<Product>) session.getAttribute("cart");
    %>
    <c:choose>
        <c:when test="${empty cart}">
            <p>您的购物车是空的,快去<a href="products.jsp">添加商品</a>吧!</p>
        </c:when>
        <c:otherwise>
            <table border="1">
                <tr>
                    <th>商品名称</th>
                    <th>商品价格</th>
                </tr>
                <c:forEach items="${cart}" var="item">
                    <tr>
                        <td>${item.name}</td>
                        <td>¥${item.price}</td>
                    </tr>
                </c:forEach>
            </table>
            <p>购物车中共有 ${cart.size()} 件商品。</p>
        </c:otherwise>
    </c:choose>
    <br/>
    <a href="products.jsp">继续购物</a>
</body>
</html>

代码分析与注意事项

  1. HttpSession session = request.getSession();

    • 这是获取 Session 的关键方法,如果当前请求没有关联的 Session,它会自动创建一个新的 Session
  2. List<Product> cart = (List<Product>) session.getAttribute("cart");

    • 我们使用 session.setAttribute("cart", cartList) 将购物车存入 Session 时,用的是 String 类型的键("cart")和 Object 类型的值。
    • 取出时,getAttribute 总是返回 Object 类型,所以必须强制转换回我们原来的类型(List<Product>)。
  3. 处理 null 购物车

    • 用户第一次访问时,Session 中肯定没有 "cart" 这个属性。getAttribute 会返回 null
    • 如果不检查 if (cart == null),直接调用 cart.add(...) 就会抛出 NullPointerException
    • 我们必须先判断购物车是否存在,如果不存在,就创建一个新的 ArrayList 并存入 Session。
  4. 重定向 vs 转发

    • AddToCartServlet 中,我们使用 response.sendRedirect("cart.jsp") 进行重定向
    • 为什么用重定向?
      • 避免重复提交:如果用户刷新 cart.jsp 页面,浏览器会重新发送请求,因为是重定向,所以是 GET 请求,不会再次执行 AddToCartServlet 的逻辑,也就不会重复添加商品。
      • 如果使用 request.getRequestDispatcher("cart.jsp").forward(request, response)转发),刷新页面会再次触发 AddToCartServlet,导致商品被重复添加。
    • 重定向的缺点:URL 会变成 cart.jsp,而不是 add-to-cart,对于用户来说,URL 没有准确反映他们刚刚执行的操作。

更健壮的实现:使用 Map 购物车

上面的实现有一个小问题:如果用户多次点击“加入购物车”,同一个商品会被添加多次,虽然这符合某些购物车的逻辑,但更常见的做法是统计数量。

我们可以修改购物车的数据结构,从 List<Product> 改为 Map<Integer, Product>key 是商品ID,value 是商品对象,这样我们可以轻松地判断商品是否已存在。

修改 AddToCartServlet

@WebServlet("/add-to-cart")
public class AddToCartServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String productIdStr = request.getParameter("id");
        if (productIdStr == null || productIdStr.isEmpty()) {
            response.sendRedirect("products.jsp");
            return;
        }
        int productId = Integer.parseInt(productIdStr);
        HttpSession session = request.getSession();
        // 购物车的数据结构从 List 改为 Map
        Map<Integer, Product> cart = (Map<Integer, Product>) session.getAttribute("cart");
        if (cart == null) {
            cart = new HashMap<>();
            session.setAttribute("cart", cart);
        }
        Product productToAdd = findProductById(productId);
        if (productToAdd != null) {
            // 如果商品已在购物车中,更新数量(这里简化了,直接覆盖,实际应该有数量属性)
            // cart.put(productId, productToAdd);
            // 或者更简单的,直接覆盖,因为对象没变
            cart.putIfAbsent(productId, productToAdd); // 如果key不存在才put
        }
        response.sendRedirect("cart.jsp");
    }
    // ... findProductById 方法不变 ...
}

修改 cart.jsp (展示方式类似)

<c:forEach items="${cart}" var="item">
    <tr>
        <td>${item.value.name}</td>  <!-- Map的value是Product对象 -->
        <td>¥${item.value.price}</td>
    </tr>
</c:forEach>

特性 描述
数据存储 ListMap 购物车对象存入 HttpSession
数据隔离 每个用户有自己的 Session,因此购物车天然隔离,互不影响。
生命周期 购物车随 Session 的创建而创建,随 Session 的销毁(如浏览器关闭、超时)而销毁。这是 Session 购物车最大的优点,也是最大的缺点。
优点 实现简单,无需数据库交互,性能高。
缺点 数据不持久化,如果服务器重启,Session 数据会全部丢失,用户的购物车就空了,不适用于需要长期保存购物车或用户未登录也能购物的场景。

进阶方案:如果需要解决数据不持久化的问题,可以将购物车数据存入数据库,并与用户账户关联,当用户登录时,将数据库中的购物车数据加载到 Session;用户退出登录或 Session 过期时,再将 Session 中的购物车数据存回数据库。

分享:
扫描分享到社交APP
上一篇
下一篇