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?
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.