javahibernatepersistenthibernate-cascade

Hibernate One to Many add new children


I have read a lot of articles and answers on similar questions, but i still can not understand.

Here my parent class:

@Entity
@Table(name = "users")
public class User implements Serializable, Annotation {

    @Id
    @GeneratedValue(generator = "system-uuid", strategy = GenerationType.IDENTITY)
    @GenericGenerator(name = "system-uuid", strategy = "uuid2")
    @Column(name = "uuid", unique = true)
    protected String uuid;

    @Column(name = "username")
    protected String username;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user", cascade = CascadeType.ALL)
    private Set<UserItem> userItems;
}

Here we can see two sets - Items and Friends of User.

Example of UserItemId class:

@Entity
@Table(name = "user_item")
@AssociationOverrides({
        @AssociationOverride(name = "pk.user",
                joinColumns = @JoinColumn(name = "user_uuid")),
        @AssociationOverride(name = "pk.ItemShop",
                joinColumns = @JoinColumn(name = "item_shop_uuid")) })
public class UserItem implements Serializable {

    @EmbeddedId
    protected UserItemId pk = new UserItemId();

    @Transient
    public User getUser() {
        return getPk().getUser();
    }

    public void setUser(User user) {
        getPk().setUser(User);
    }

    @Transient
    public ItemShop getItemShop() {
        return getPk().getItemShop();
    }

    public void setItemShop(ItemShop ItemShop) {
        getPk().setItemShop(ItemShop);
    }
}

And now i'm trying to add new Item to user:

/* 
'user' in this code i got from SecurityContext in controller 
and I give it as parameter from controller to method (service layer) which can add new Item to user. 
So user it is entity which already exists in database - it is logged user 
*/
UserItem userItem = new UserItem();
userItem.setUser(user);
userItem.setItemShop(ItemShop);

user.getUserItems().add(userItem);
session.saveOrUpdate(user);

But i got an error:

org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session

I understand that the main problem is in CascadeTypes - ALL it is not best variant for this, because hibernate can not do what i want - understand that User is a persistent object and just add to persistent new item and save it to database. So i want to ask you, what is the best practice to win in this situation, when i have Parent class (User) and children (Items) and i want to add new items and delete items from parent and save it to database. Indeed i found working method (using iterator), but i understand that on highload project it is the worst variant make for loop for such operations. I really want to find best practices for such situations.

QUESTION UPDATE: I've made mistake. When i call code which execute adding child to set (code above) - everything is good - child added in database and in entity, but when I'm trying to delete child:

Set<UserItem> userItems = user.getUserItems();
UserItem userItem = userDAO.findUserItem(user,  itemShop);
userItems.remove(userItem);
session.saveOrUpdate(user);
session.flush();

Then i got exception:

org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session

ONE MORE UPDATE:

After a lot of experiments i got working code of adding item:

UserItem userItem = new UserItem();
userItem.setUser(user);
userItem.setItemShop(ItemShop);

user.getUserItems().add(userItem);
session.saveOrUpdate(user);

It is a code of method in a Service layer which add item to user. And it is working. And i have half-working code to delete item from user:

       Set<UserItem> userItems = user.getuserItems();
UserItem userItem = UserDAO.findUserItem(user, itemShop);
userItems.remove(userItem);
userDAO.merge(user);

It is not properly works code. We have a situation - user has 3 items. I delete one item - everything is fine. Later I'm trying to delete one more item and hibernate says me:

ObjectNotFoundException: No row with the given identifier exists

That means in cache still 3 items - that one i delete first - still in cache and hibernate try to add it into database - but it is deleted.

And i got one more mind idea, which can help us to answer this question. Object User - where i'm trying to delete child - it is logged user from SecurityContext - i got it from controller - controller has annotation where User got from Security. So, hibernate already has in cache logged user (User object). Any ideas?


Solution

  • OP has to manually manage what is effectively a join table because there are extra fields on the join table that have to be managed as well. This is causing issues when trying to delete an item object off a user.

    This is the standard way that I set up a bi-directional many to many when I have to manage the join explicitly or I have extra information attached to the join. If I don't have any extra info, or don't have to manage the join myself, just use the @JoinTable annotation.

    New Objects:

    @Entity
    @Table(name = "users")
    public class User implements Serializable, Annotation {
    
        @Id
        @GeneratedValue(generator = "system-uuid", strategy = GenerationType.IDENTITY)
        @GenericGenerator(name = "system-uuid", strategy = "uuid2")
        @Column(name = "uuid", unique = true)
        protected String userUUID;
    
        @Column(name = "username")
        protected String username;
    
        @OneToMany(mappedBy="user", cascade = CascadeType.ALL, orphanRemoval=true)
        private Set<UserItem> items = new HashSet<UserItem>();
    }
    

    Item Object:

    @Entity
    @Table(name="items")
        public class Item implements Serializable{
        @Id
        @GeneratedValue(generator = "system-uuid")
        @GenericGenerator(name = "system-uuid", strategy = "uuid2")
        @Column(name = "uuid", unique = true)
        protected String itemUUID;
    
        @Column(name = "name")
        protected String name;
    
        @Column(name = "description")
        protected String description;
    
        @OneToMany(mappedBy="item", cascade = CascadeType.ALL, orphanRemoval=true)
        private Set<UserItem> users = new HashSet<UserItem>();
    

    UserItem class:

    @Entity
    @Table(name = "user_item")
    public class UserItem implements Serializable {
    
       //Generate this like you are doing with your others.
       private String userItemUUID;
    
       @ManyToOne
       @JoinColumn(name="userUUID")
       private User user;
    
       @ManyToOne
       @JoinColumn(name="itemUUID")
       private Item item;
    
       private BigDecimal moneyCollect;
    
       private Date dateAdded = new Date();
    }
    

    Your service to delete looks like this:

    UserItem userItem = userDAO.findUserItem(user, itemShop);
    user.getUserItems().remove(userItem);
    session.saveOrUpdate(user);
    session.flush();
    

    You can do it the other way as well.

    UserItem userItem = userDAO.findUserItem(user, itemShop);
    item.getUserItems().remove(userItem);
    session.saveOrUpdate(item);
    session.flush();
    

    The exception you are getting is caused by Hibernate finding two UserItem objects within the method that are both persistent and not knowing which one to delete.

    //Create one UserItem instance here
    Set<UserItem> userItems = user.getUserItems();
    //Create another UserItem instance here
    UserItem userItem = userDAO.findUserItem(user,  itemShop);
    userItems.remove(userItem);
    
    //Hibernate doesn't know which UserItem instance to remove, and throws an exception.
    session.saveOrUpdate(user);
    session.flush();
    

    If any of that didn't make sense, let me know.

    ADDENDUM: OP was getting a persistent instance of a User inside their controller and not doing anything with it. This was causing the NonUniqueObjectException that Hibernate was throwing.