购物车删除最后一项异常:@GetMapping接口未触发问题排查
问题
删除购物车最后一项商品时,@GetMapping("remove-song-from-cart")接口完全没被调用,但删除之前的商品时,这个接口能正常触发并返回购物车页面,怀疑问题出在这个GET接口的实现上。
相关代码
Cart 实体类
package musicstore.musicselling.Entity; import java.util.HashSet; import java.util.Set; import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; @Entity public class Cart { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long cartId; @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) private Set<CartItem> cartItems = new HashSet<>(); public Long getCartId() { return cartId; } public Set<CartItem> getCartItems() { return cartItems; } public void setCartItems(Set<CartItem> cartItems) { this.cartItems = cartItems; } public void addCartItems(CartItem item) { cartItems.add(item); item.setCart(this); } public void removeCartItems(CartItem item) { cartItems.remove(item); item.setCart(null); } }
CartItem 实体类
package musicstore.musicselling.Entity; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @Entity public class CartItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long itemId; @ManyToOne @JoinColumn(name = "cartId") private Cart cart; @ManyToOne @JoinColumn(name = "songId") private Song song; @ManyToOne @JoinColumn(name = "albumId") private Album album; public CartItem() { } public Long getItemId() { return itemId; } public Song getSong() { return song; } public void setSong(Song song) { this.song = song; } public Album getAlbum() { return album; } public void setAlbum(Album album) { this.album = album; } public Cart getCart() { return cart; } public void setCart(Cart cart) { this.cart = cart; } }
CartController 控制器
package musicstore.musicselling.Controller; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.ui.Model; import musicstore.musicselling.Entity.*; import musicstore.musicselling.Repository.AlbumRepository; import musicstore.musicselling.Repository.CartItemRepository; import musicstore.musicselling.Repository.CartRepository; import musicstore.musicselling.Repository.SongRepository; @Controller public class CartController { @Autowired SongRepository songRepository; @Autowired AlbumRepository albumRepository; @Autowired CartRepository cartRepository; @Autowired private CartItemRepository cartItemRepository; private Cart getOrCreateCart() { if (cartRepository.findByCartId(1L) == null) { Cart cart = new Cart(); return cartRepository.save(cart); } return cartRepository.findByCartId(1L); } // add the song to the cart @GetMapping("/add-song-to-cart") public String addSongToCart(@RequestParam Long songId) { Cart cart = getOrCreateCart(); Song song = songRepository.findBySongId(songId); CartItem item = new CartItem(); item.setSong(song); cart.addCartItems(item); cartRepository.save(cart); System.out.println("Add success"); return "redirect:/song-list"; } // push the data of cart list to the cart page @GetMapping("/cart") public String songCart(Model model) { Cart cart = getOrCreateCart(); double price = 0; List<Song> songItems = new ArrayList<>(); for (CartItem cartItem : cart.getCartItems()) { if (cartItem.getSong() != null) { songItems.add(cartItem.getSong()); price = price + cartItem.getSong().getSongPrice(); } } String totalPrice = String.format("%.2f", price); model.addAttribute("totalPrice", totalPrice); model.addAttribute("songItems", songItems); System.out.println("cart item size: " + cart.getCartItems().size()); for (CartItem cartItem : cart.getCartItems()) { System.out.println("Song id: " + cartItem.getSong().getSongId()); } return "cart"; } // remove the song from cart @GetMapping("remove-song-from-cart") public String removeSongFromCart(@RequestParam Long songId) { Cart cart = getOrCreateCart(); Set<CartItem> cartList = cart.getCartItems(); System.out.println("The fucking cart size: " + cartList.size()); CartItem itemToRemove = null; for (CartItem item : cartList) { if (item.getSong().getSongId().equals(songId)) { itemToRemove = item; } } if (itemToRemove != null) { cart.removeCartItems(itemToRemove); cartRepository.save(cart); } System.out.println("item to remove: " + itemToRemove.getSong().getSongName()); return "redirect:/cart"; } }
修复方案
统一接口路径格式
把删除接口的映射改成@GetMapping("/remove-song-from-cart")(开头加斜杠),确保和其他接口的路径格式一致,避免前端相对路径解析出错导致请求发不出去。修复空指针异常
删除最后一项时,如果itemToRemove为null(比如购物车已被清空但前端仍显示按钮),直接调用itemToRemove.getSong()会抛出空指针,导致请求失败。修改代码:if (itemToRemove != null) { cart.removeCartItems(itemToRemove); cartRepository.save(cart); System.out.println("item to remove: " + itemToRemove.getSong().getSongName()); } else { System.out.println("未找到要删除的商品"); }检查前端渲染逻辑
确认购物车为空时,前端是否还渲染了删除按钮,或者按钮的点击事件是否绑定正常。如果购物车为空时按钮还存在,但实际没有对应的商品ID,请求会无效。验证JPA持久化操作
删除后打印cart.getCartItems().size(),确认集合已为空;同时查看数据库中CartItem表的记录是否被删除,确保orphanRemoval = true配置生效。
内容的提问来源于stack exchange,提问作者Tuong Nguyen
相关产品推荐
相关产品推荐

