dikkini
dikkini

Reputation: 1182

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?

Upvotes: 1

Views: 7861

Answers (1)

JamesENL
JamesENL

Reputation: 6540

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.

Upvotes: 2

Related Questions