admin管理员组

文章数量:1312836

So I am trying to implement a short e-commerce kind of project using SpringBoot and everything. As per my model, every user will have a cart with a list of items (CartItems in my case), and these items will consist of a particular product.

I am adding only the relevant fields in these classes

CartItem.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CartItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cart_item_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "cart_id")
    private Cart cart;

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
}

Cart.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Cart {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cart_id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;

    @OneToMany(mappedBy = "cart", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, orphanRemoval = true)
    private List<CartItem> cartItems = new ArrayList<>();

    private Double totalPrice = 0.0;
}

Product.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "product_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "category_id")
    private Category category;

    @OneToMany(mappedBy = "product", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.EAGER)
    private List<CartItem> products = new ArrayList<>();
}

Everything is working fine until I try to delete a product from the cart. With the endpoint, what I want should happen is that the cart's total price should change, and the corresponding cartItem that was holding that product in that specific cart should get deleted.

Below is the controller code -

@DeleteMapping("/carts/{cartId}/product/{productId}")
public ResponseEntity<String> deleteProductFromCart(@PathVariable Long cartId,
                                                    @PathVariable Long productId) {
    String status = cartService.deleteProductFromCart(cartId, productId);
    return new ResponseEntity<>(status, HttpStatus.OK);
}

Below is the service implementation -

@Transactional
@Override
public String deleteProductFromCart(Long cartId, Long productId) {
    Cart cart = cartRepository.findById(cartId)
            .orElseThrow(() -> new ResourceNotFoundException("Cart", "id", cartId));
    CartItem cartItem = cartItemRepository.findCartItemByProductIdAndCartId(cartId, productId);
    if (cartItem == null) {
        throw new ResourceNotFoundException("Product", "id", productId);
    }
    cart.setTotalPrice(cart.getTotalPrice() -
            (cartItem.getPrice() * cartItem.getQuantity()));

    // Code #1
    // cart.getCartItems().remove(cartItem);
    // cartRepository.save(cart);
    
    // Code #2
    // cartItemRepository.delete(cartItem);
    
    // Code #3
    // cartItemRepository.deleteCartItemByProductIdAndCartId(cartId, productId);

    return "Product " + cartItem.getProduct().getName() + " removed from the cart !!!";
}

The custom repository method -

@Modifying
@Query("DELETE FROM CartItem ci WHERE ci.cart.id = ?1 AND ci.product.id = ?2")
void deleteCartItemByProductIdAndCartId(Long cartId, Long productId);

Out of the 3 blocks of code, only the 3rd one seems to be deleting the cart item from the database. Why is that??

As per my understanding, code block 1 should work because of orphanRemoval = true in the entity class and code block 2 should work because I am directly deleting the cartItem that I fetched from the database. I believe I am missing some fundamental thing. Please help!

So I am trying to implement a short e-commerce kind of project using SpringBoot and everything. As per my model, every user will have a cart with a list of items (CartItems in my case), and these items will consist of a particular product.

I am adding only the relevant fields in these classes

CartItem.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CartItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cart_item_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "cart_id")
    private Cart cart;

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;
}

Cart.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Cart {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cart_id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;

    @OneToMany(mappedBy = "cart", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, orphanRemoval = true)
    private List<CartItem> cartItems = new ArrayList<>();

    private Double totalPrice = 0.0;
}

Product.java

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "product_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "category_id")
    private Category category;

    @OneToMany(mappedBy = "product", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.EAGER)
    private List<CartItem> products = new ArrayList<>();
}

Everything is working fine until I try to delete a product from the cart. With the endpoint, what I want should happen is that the cart's total price should change, and the corresponding cartItem that was holding that product in that specific cart should get deleted.

Below is the controller code -

@DeleteMapping("/carts/{cartId}/product/{productId}")
public ResponseEntity<String> deleteProductFromCart(@PathVariable Long cartId,
                                                    @PathVariable Long productId) {
    String status = cartService.deleteProductFromCart(cartId, productId);
    return new ResponseEntity<>(status, HttpStatus.OK);
}

Below is the service implementation -

@Transactional
@Override
public String deleteProductFromCart(Long cartId, Long productId) {
    Cart cart = cartRepository.findById(cartId)
            .orElseThrow(() -> new ResourceNotFoundException("Cart", "id", cartId));
    CartItem cartItem = cartItemRepository.findCartItemByProductIdAndCartId(cartId, productId);
    if (cartItem == null) {
        throw new ResourceNotFoundException("Product", "id", productId);
    }
    cart.setTotalPrice(cart.getTotalPrice() -
            (cartItem.getPrice() * cartItem.getQuantity()));

    // Code #1
    // cart.getCartItems().remove(cartItem);
    // cartRepository.save(cart);
    
    // Code #2
    // cartItemRepository.delete(cartItem);
    
    // Code #3
    // cartItemRepository.deleteCartItemByProductIdAndCartId(cartId, productId);

    return "Product " + cartItem.getProduct().getName() + " removed from the cart !!!";
}

The custom repository method -

@Modifying
@Query("DELETE FROM CartItem ci WHERE ci.cart.id = ?1 AND ci.product.id = ?2")
void deleteCartItemByProductIdAndCartId(Long cartId, Long productId);

Out of the 3 blocks of code, only the 3rd one seems to be deleting the cart item from the database. Why is that??

As per my understanding, code block 1 should work because of orphanRemoval = true in the entity class and code block 2 should work because I am directly deleting the cartItem that I fetched from the database. I believe I am missing some fundamental thing. Please help!

Share Improve this question edited Feb 2 at 19:12 love_to_code asked Feb 2 at 18:54 love_to_codelove_to_code 1191 silver badge6 bronze badges 1
  • The other 2 blocks of code seem to be doing nothing. Plus there is no exception thrown or any kind of red flag seen. – love_to_code Commented Feb 2 at 19:03
Add a comment  | 

1 Answer 1

Reset to default 0

The reason, why your code is not working as you expect - is probably because of Hibernate's entity lifecycle, persistence context, and how deletions are cascaded.

 // Code #1
 cart.getCartItems().remove(cartItem);
 cartRepository.save(cart);

It is true, that orphanRemoval = true should trigger CartItem deletion from the database. But there can be cases, when the removal won't trigger deletion, for example, if cartItem is not actually contained in cart.getCartItems(). It can happen if Hibernate fetches collections lazily or because of differences in persistence context.

 // Code #2
 cartItemRepository.delete(cartItem);

When .delete(entity) is called, the entity can be just marked for deletion, but the delete action will happen only at flush time. You can try to break the association explicitly with cartItem.setCart(null);

 // Code #3
 cartItemRepository.deleteCartItemByProductIdAndCartId(cartId, productId);

This is a JPQL update/delete query, it ignores entity states, associations, or persistence context issues. That is why it works always.

But be careful, JPQL query completely bypasses the Hibernate persistence lifecycle, it is better to be used only when you want an immediate, bulk deletion, but only when entity lifecycle doesn't matter. There can be some negative consequences of using JPQL queries in some cases:

  • If you use a JPQL delete query, Hibernate doesn’t know that an entity has been deleted. This can lead to stale state issues where an entity still exists in memory but is actually deleted from the database.
  • Hibernate provides entity lifecycle callbacks like @PreRemove and @PostRemove that let you run logic before or after an entity is deleted. But JPQL query won't trigger this events.
  • If you delete with JPQL and later rollback the transaction, the entity remains deleted in the database.

So, it is always better to use JPA, except few specific cases (like bulk deletion, avoiding unnecessary fetching or for scheduled cleanup jobs).

I suggest you to find the right order for operations to trigger the deletion. For example, this way:

cart.getCartItems().remove(cartItem); // remove from parent collection
cartItem.setCart(null);               // prevent Hibernate from reattaching it
cartRepository.save(cart);             // persist changes 
cartItemRepository.delete(cartItem);   // ensure the entity is deleted if orphanRemoval fails

Hope, this will help you.

本文标签: javaJPAHibernate not updating the database after calling deleteStack Overflow