深扒:HttpSession與Cart域模型設(shè)計的完整教程)
jpetstore-6購物車實現(xiàn)深扒HttpSession與Cart域模型設(shè)計的完整教程【免費下載鏈接】jpetstore-6A web application built on top of MyBatis 3, Spring 3 and Stripes項目地址: https://gitcode.com/gh_mirrors/jp/jpetstore-6jpetstore-6 是一個基于 MyBatis 3 與 Spring 3 構(gòu)建的經(jīng)典 Java Web 電商示例項目本文帶你完整拆解它的購物車實現(xiàn)購物車數(shù)據(jù)如何通過HttpSession在瀏覽器訪問間保持、Cart與CartItem這兩個Cart 域模型是如何設(shè)計的、以及從加購到下單的完整數(shù)據(jù)流。讀完你會發(fā)現(xiàn)一個看似簡單的購物車背后藏著懶加載、雙數(shù)據(jù)結(jié)構(gòu)同步、BigDecimal 金額計算等一連串值得學(xué)習(xí)的工程細節(jié) 一、先看全貌購物車數(shù)據(jù)在系統(tǒng)里如何流轉(zhuǎn)整個購物車功能橫跨三層各自由獨立文件承載層次職責(zé)核心文件Web 層接收請求、讀寫 Sessionsrc/main/java/org/mybatis/jpetstore/web/controllers/CartController.java域模型層內(nèi)存中維護購物車數(shù)據(jù)結(jié)構(gòu)src/main/java/org/mybatis/jpetstore/domain/Cart.java、src/main/java/org/mybatis/jpetstore/domain/CartItem.java視圖層渲染購物車表格src/main/webapp/WEB-INF/jsp/cart/Cart.jsp數(shù)據(jù)流非常直觀每次請求 → 從 HttpSession 取出 Cart 對象 → 修改 → 隨 Session 保存 → 交給 JSP 渲染。購物車不經(jīng)過數(shù)據(jù)庫全部存在服務(wù)器內(nèi)存里直到用戶確認下單時才落庫。二、Cart 域模型深扒一個購物車兩套數(shù)據(jù)結(jié)構(gòu)打開Cart.java最先映入眼簾的是兩個成員變量這是整個設(shè)計最精妙的地方private final MapString, CartItem itemMap new ConcurrentHashMap(); private final ListCartItem itemList new ArrayList();為什么要一份數(shù)據(jù)存兩份itemMapConcurrentHashMap以itemId為鍵實現(xiàn) O(1) 的快速查找。加購前先查購物車?yán)镉袥]有這件商品、按 ID 改數(shù)量、刪除商品全靠它。用并發(fā)容器也體現(xiàn)了對多線程 Web 環(huán)境的防御。itemListArrayList保持商品加入順序?qū)iT用于頁面展示和遍歷計算。兩套結(jié)構(gòu)在每次寫操作時嚴(yán)格同步例如addItem()的邏輯是若itemMap中沒有該商品則新建CartItem并同時放入 map 和 list隨后統(tǒng)一調(diào)用incrementQuantity()。removeItemById()同理兩邊一起移除。 這種雙寫模式是購物車這類既要快查、又要保序場景的常見技巧。再看CartItem.java它是購物車的一行記錄只有 4 個字段item完整的商品對象來自domain/Item.java含價格、屬性、所屬產(chǎn)品quantity數(shù)量inStock加購時是否庫存充足的快照標(biāo)記total小計金額一個容易忽略的細節(jié)是calculateTotal()——每次setItem、setQuantity、incrementQuantity都會自動用BigDecimal重算total listPrice × quantity。金額計算全程使用BigDecimal而非double避免了浮點誤差這是電商代碼的基本功。購物車總價getSubTotal()則用 Stream 一行流式求和itemList.stream() .map(ci - ci.getItem().getListPrice().multiply(new BigDecimal(ci.getQuantity()))) .reduce(BigDecimal.ZERO, BigDecimal::add);另外Cart和CartItem都實現(xiàn)了Serializable并顯式聲明serialVersionUID——因為 Session 在某些服務(wù)器配置下會把內(nèi)容序列化到磁盤或內(nèi)存換頁區(qū)不實現(xiàn)接口購物車就無法持久存活。三、HttpSession 中的購物車懶初始化模式CartController里有一段幾乎可以照抄到任何 Java Web 項目的代碼private Cart getCart(HttpSession session) { Cart cart (Cart) session.getAttribute(cart); if (cart null) { cart new Cart(); session.setAttribute(cart, cart); } return cart; }這就是懶初始化Lazy Initialization用戶還沒買任何東西時Session 里并不存在cart屬性第一次調(diào)用任意購物車接口時才創(chuàng)建并掛到 Session 上。好處是零浪費每個訪問者都只占一份內(nèi)存。這里也暴露了 Session 方案的固有特性值得新手理解?匿名可用未登錄游客也能加購因為 Session 不依賴賬戶體系?讀寫極快純內(nèi)存操作無需數(shù)據(jù)庫往返??單機限制多臺服務(wù)器做集群時Session 不共享購物車會丟??重啟即失應(yīng)用重啟后內(nèi)存中的購物車清空理解這些利弊你就知道為什么大型電商會改用 Redis 集中存儲購物車而 jpetstore-6 作為示例項目選擇 Session 是最務(wù)實的簡化。四、四個購物車操作接口逐個拆解CartController以RequestMapping(/cart)注冊共 4 個接口全部圍繞上面的getCart(session)展開1. 加購GET /cart/addItem參數(shù)為workingItemId。先做空值校驗再分兩條路徑商品已在車?yán)颿art.containsItemId()→ 直接incrementQuantityByItemId()新車?yán)锏纳唐?→ 先經(jīng)CatalogService.isItemInStock()查庫確認庫存再cart.addItem(item, isInStock)注意inStock是加購那一刻的快照之后庫存賣光購物車?yán)锶员A粼撋唐凡@示無貨用戶可以自行移除。2. 移除GET /cart/removeItem調(diào)用cart.removeItemById(itemId)若返回null說明車?yán)锔緵]這件商品走錯誤頁。3. 批量改數(shù)量POST /cart/update這是最接地氣的一個接口配合Cart.jsp中的表格——每行的數(shù)量輸入框name就是itemIdinput typetext size3 name${cartItem.item.itemId} value${cartItem.quantity} /服務(wù)端遍歷購物車用request.getParameter(itemId)按商品 ID 取回新數(shù)量并setQuantityByItemId()數(shù)量小于 1 就順手把該行刪除。遇到非法數(shù)字輸入則靜默跳過保證一個壞行不影響其他行。4. 去結(jié)算GET /cart/checkout僅僅把購物車塞進 Model轉(zhuǎn)發(fā)到cart/Checkout.jsp展示結(jié)算摘要真正的下單在下一節(jié)登場。五、從購物車到訂單一次教科書級的模型轉(zhuǎn)換購物車數(shù)據(jù)最終要在下單時物化成數(shù)據(jù)庫記錄這個轉(zhuǎn)換發(fā)生在OrderController.java與domain/Order.java之間分三步第一步登錄校驗 生成訂單草稿。GET /order/new要求accountBean已認證否則跳轉(zhuǎn)登錄頁隨后調(diào)用order.initOrder(account, cart)。第二步Cart → Order 字段搬運。Order.initOrder()完成三件事把賬戶里的收貨/賬單地址、姓名整塊拷貝到訂單totalPrice cart.getSubTotal()直接復(fù)用第二節(jié)的流式求和遍歷cart.getAllCartItems()把每個CartItem轉(zhuǎn)成LineItem行項目拷貝數(shù)量、商品 ID、單價并重算小計LineItemdomain/LineItem.java與CartItem結(jié)構(gòu)高度相似——這正是購物車行與訂單行同構(gòu)的體現(xiàn)轉(zhuǎn)換成本極低。第三步確認下單 清空購物車。POST /order/new攜帶confirmedtrue時調(diào)用OrderService.insertOrder()service/OrderService.java這是一個Transactional事務(wù)方法先用Sequence取訂單號 → 逐行updateInventoryQuantity扣減庫存 → 插入訂單主表、狀態(tài)表和全部行項目。任何一步失敗整體回滾庫存扣減與訂單落庫強一致。成功后執(zhí)行關(guān)鍵一行session.setAttribute(cart, new Cart());用一個新的空 Cart 覆蓋 Session 里的舊值——既避免殘留舊引用又與懶初始化模式無縫銜接下一輪購物從零開始。六、單元測試與設(shè)計總結(jié)src/test/java/org/mybatis/jpetstore/domain/CartTest.java用 JUnit 5 把域模型測得很干凈加購合并數(shù)量、庫存標(biāo)記、按 ID 刪除含刪除不存在的商品、連續(xù)增量、直接設(shè)量、空車與多商品的小計計算——基本覆蓋了Cart的每個公開方法。建議新手在理解本文后運行一遍這些測試對照斷言看輸出是學(xué)習(xí)域模型最快的方式。最后提煉 4 條可直接復(fù)用的設(shè)計經(jīng)驗雙數(shù)據(jù)結(jié)構(gòu)Map 管快速查找、List 管有序展示寫操作保持兩邊同步懶初始化 SessiongetAttribute為 null 才setAttribute按需創(chuàng)建金額必用 BigDecimal且小計在模型內(nèi)自動重算防止視圖層算錯賬事務(wù)邊界放在訂單服務(wù)購物車階段不碰數(shù)據(jù)庫下單瞬間才在Transactional里扣庫存、寫訂單最后重置購物車——讀放大到內(nèi)存、寫集中到事務(wù)。把這套模式吃透再去理解 Redis 購物車、分布式鎖等進階方案就有扎實的對照基礎(chǔ)了 【免費下載鏈接】jpetstore-6A web application built on top of MyBatis 3, Spring 3 and Stripes項目地址: https://gitcode.com/gh_mirrors/jp/jpetstore-6創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考