0

I have an issue when trying to update the contents of a cart with new values added one by one. I am using Spring boot with Hibernate, JPA Repositories, MySQL Database and a front-end built with vanilla JS.I will describe my issue in the following lines.

I am having an user entity that looks like this:

@Entity
@Table(name = "users")
@Getter
@Setter
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles;


    @Column(name = "cartprod_id")
    @OneToMany(cascade = {CascadeType.ALL})
    private List<CartItem> cartProduct;

This entity has a List<CartItem> field that looks like this:

@Entity
@Getter
@Setter
public class CartItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "product_id")
    private int productId;
    @JsonIgnore
    @ManyToOne
    private User user;
}

And the relationship between them in the Database looks like in this image: enter image description here

The idea of the above code is to have a way to keep the state of the products Cart of an user.

The flow is as follows :

I am doing a request from the front-end each time a user adds a product in the cart, when this happens, I am saving the contents of the cart(that consist of only the products ID's) in the database using the CartItem entity.

The problem

With this approach, instead of saving only the last product added to the cart(or replace the cart completely with the new values similar to an update), it is inserting the same object over and over again but with all the new appended values instead of overwriting the old object(table) in the database. An example of this would be in this first image . As you can see I have added a product to the cart with id 327 first.

enter image description here

Next I am adding a product with id 328 but it also adds 327 a second time, this goes on and on, the more products I add. This last code snippet contains my controller code .

 @PostMapping("/savecart")
    public ResponseEntity<String> saveCartToDb(@RequestBody List<CartItem> cartItemList, Principal principal){
        System.out.println(cartItemList);
        User logedInUser = userService.findUserByUsername(principal.getName()).get();

        List<CartItem> cartItem = logedInUser.getCartProduct();
        if(cartItem.isEmpty()){
            logedInUser.setCartProduct(cartItemList);
            userService.saveNewUser(logedInUser);
        }else {
            cartItem = cartItemList;
            logedInUser.setCartProduct(cartItem);
            userService.saveNewUser(logedInUser);
        }
//        userService.saveNewUser(logedInUser);
        return ResponseEntity.ok().body("ASD");
    }

How can I overwrite the contents of the List<CartItems> for the user so that I will not append new and old values again and again ? Would a SET help as it won't allow duplicates ?

I have also tried this How do I update an entity using spring-data-jpa? but I a not sure that I need to create a @Query for this issue.

helloApp
  • 449
  • 1
  • 4
  • 21
  • Please don't post images of code. – tgdavies Oct 23 '22 at 07:44
  • @tgdavies I am doing such a well informed post and put so much effort into detailing everything , and you pick on my screenshots , why ? they look better than using ``` for adding the code. – helloApp Oct 23 '22 at 07:46
  • See https://meta.stackoverflow.com/questions/285551/why-should-i-not-upload-images-of-code-data-errors-when-asking-a-question – tgdavies Oct 23 '22 at 07:49
  • @tgdavies well ok, I will replace the image with the actual code, in the case of the controller. But for mysql i can't do that. If you gave me -1 for that please reverse it after I modify the post – helloApp Oct 23 '22 at 07:52
  • 3
    @helloApp, to update an existing object (rather than insert a new record) you always have to fetch it from your database. This means that e.g. whatever data you're getting from the front end (e.g product id etc), use that information only to fetch the matching database records and do whatever changes you want on those records. The save method can then be used to update the records in question – dsp_user Oct 23 '22 at 15:49
  • 1
    What is in the cartItemList exactly for CartItem? Does the 'duplicate' have its ID already set, or are you passing in the same blank without the ID set that you might have passed in before, passing it through userService.saveNewUser? Make sure that if you are going to reuse instances, they are the managed instances - 'save' in spring returns the managed instance which should have all IDs set. Otherwise, if it doesn't have an ID, it must be assumed to be new and inserted. Why it doesn't replace the existing one.. more info would be needed; show what is in the saved entity's list after each pass – Chris Oct 24 '22 at 14:00
  • @Chris yes , before saving i pass the id of the parent to each object. I managed to make it work. It was a bidirectional relationship issue. I will pots my solution. – helloApp Oct 25 '22 at 21:26

1 Answers1

0

I managed to make it work. The solution is the following.

This is a bidirectional one-to-many / many-to-one issue. In order to be able to remove child elements from the parent, we need to decouple(detach) them from the parent. Since parent and child are bound together by a foreign key the detachment has to be done on both ends. If one has a reference to the other this will not work so BOTH REFERENCES have to be removed. 2 methods are needed and both need to be called when doing decoupling. This is what I used.

private Set<CartProduct> cartProduct;

This one to be added in the parent class (User).

 public void removeChild(CartProduct child) {
        this.cartProduct.remove(child);
    }

This one to be added in the Child class

  public void removeParent() {
        this.user.removeChild(this);
        this.user = null;
    }

Methods also have to be called like this

   for(CartProduct cartItem : cartItemList){
                cartItem.removeParent();
                logedInUser.removeChild(cartItem);
            }

L.E

It may be that with the above implementation you will get a

java.util.ConcurrentModificationException: null

it happen to me in one of the cases too. In order to fix this I used an Iterator like below.

for (Iterator<CartProduct> iterator = cartItemList.iterator(); iterator.hasNext();) {
                CartProduct cartItem = iterator.next();
                if (cartItem != null) {  
                    iterator.remove();
                }
            }
helloApp
  • 449
  • 1
  • 4
  • 21
  • There were no actual duplicates being added which is what confused me in your problem description; you are just replacing a list and expecting the now non-referenced elements from the list to be somehow handled. This answer though is only half of the solution, isn't it? You have to actually call these remove methods for the de-referenced cartItem (child) elements of the list - you can't just replace it with a new one. – Chris Oct 25 '22 at 21:47
  • @Chris yes there where not duplicates, instead it was inserting new data containing the new elements over the old data. so that is why when I was adding lets say 1 to the cart it was inserting 1 in the DB, but then besides 1 I would also add another product with id 2, so now the set from the frontend was containing 1 and 2, so it was adding 1 and 2 over the first 1 in the database, and so on. Yes this is half the solution methods have to be called aswell. I have added my whole solution to the post now with method calling included. – helloApp Oct 25 '22 at 22:24