How to update link table in Hibernate ManyToMany Mapping - java

I have a Hibernate ManyToMany mapping between data table and user table linked by data_user table. Now I want to update the data table to add one more user to the data. How to update link table(data_user) to add one more entry for the new user?
I, first updated the user collection: data.getUsers().add(user), and then in the DAO layer tried session.saveOrUpdate(data). But it deleted everything in the link table.
Update1: data_user(iddata_user,iddataroom,iduser) was manually created in the db.
Update2 : implemented Hashcode and equals for Data and User.
Update 3: I changed to CascadeType.MERGE. This updates my link table. Also, I am never going to update User table from Data which hibernate was trying when CascadeType was ALL.
org.hibernate.NonUniqueObjectException: a different object with the
same identifier value was already associated with the session.
PS: I am very new to hibernate.
public class Data {
private int dataId;
private Data parentData;
private Set<User> users;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "data_user", joinColumns = { #JoinColumn(name = "iddata") },
inverseJoinColumns = { #JoinColumn(name = "iduser") })
public Set<User> getUsers() {
return users;
}
...
}

You need to override equals() and hashCode() in your entities or you are going to run into issues sooner or latter see this for more details.
If your problem still exists after overriding equals() and hashCode() you may add a mappedBy on the other side of the relation see this for more details.

Related

Hibernate #OneToOne Unidirectional Mapping...Cascade Delete

I'm working on a Spring Boot Application with Hibernate and I'm just trying to understand the correct way to approach a OneToOne mapping when it comes to using cascade delete.
So, we have a User table and a PasswordResetToken table. A user has standard user columns: id, username, password, email.
A password reset token has an id, a FK to userId, and a string for a token.
So, my question now is: how do I correctly model this so we can properly cascade delete?
My thought process is that we have a unidirectional mapping since password reset token has a FK to user, and user does NOT have a FK to password reset token.
So I would think that we would place the #OneToOne on our PasswordResetToken class in Java and not have a reference to PasswordResetToken in our User class, but then the PasswordResetToken class will have a reference to a User object.
But, through some stackoverflowing, I found that people would have the child object (PasswordResetToken) inside the parent object (User) despite the parent object's table not having a reference to the child object's table (since the User table doesn't have a PasswordResetToken in it) which allows for adding the cascade remove to the #OneToOne annotation which means that when a User gets deleted, all children will get deleted as well.
So, which way is the right way to model this relationship?
Thanks for your time
There are many ways to solve your problem. Some are less, some are more efficient.
Bidirectional with foreign key
#Entity
public class PasswordResetToken {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User User;
// other fields
}
#Entity
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private PasswordResetToken passwordResetToken;
// other fields
}
Bidirectional with principal/parent's primary key as foreign key
Since it's 1-1 relationship, you could use User's ID as a primary key for PasswordResetToken table.
#Entity
public class PasswordResetToken {
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User User;
// other fields
}
#Entity
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private PasswordResetToken passwordResetToken;
// other fields
}
Unidirectional
If you want to have unidirectional mapping, and to have PasswordResetToken entity as part of User entity, you'll have to move the foreign key to User table, since #JoinColumn has to be applied on entity owning the foreign key.
#Entity
public class User {
#OneToOne(cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JoinColumn("password_reset_token_id") // FK in User table
private PasswordResetToken passwordResetToken;
// other fields
}
As for performance, the most efficient is bidirectional with #MapsId. Bidirectional with #JoinColumn is less efficient, and I'm not sure about unidirectional mapping. One to one mappings are not that common in practice, and I'm not sure how often people use unidirectional mapping. Probably not at all, since the foreign key is usually on dependent side.
I don't know how big the token is, but what is wrong with storing the token in the User entity as simple column? You can abstract some parts by using an #Embeddable but really this should IMO be in the same table. If you are concerned with the amount of data fetched, you should be using DTOs to reduce the amount of data.

1:n disable constraints for the n-side?

The Problem
I have a 1:n relation, but the n side shouldnt rely on constraints. So i actually wanna insert a EntityPojo via its future id, when its not saved yet ( Lets ignore that its a bad practice ). This looks kinda like this.
var relation = new RelationshipPojo();
.
.
.
relation.targets.add(session.getReference(futureID, EntityPojo.class));
session.save(relation);
// A few frames later
session.save(theEntityPojoWithTheSpecificId);
Cascading is not possible here, i only have its future ID, not a reference to the object i wanna save. Only its id it will have in the future.
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
.
.
.
#ManyToMany(cascade = {}, fetch = FetchType.EAGER)
public Set<EntityPojo> targets = new LinkedHashSet<>();
}
Question
How do we tell hibernate that it should ignore the constraints for this 1:n "target" relation ? It should just insert the given ID into the database, ignoring if that EntityPojo really exists yet.
Glad for any help on this topic, thanks !
For a much simpler solution, see the EDIT below
If the goal is to insert rows into the join table, without affecting the ENTITY_POJO table, you could model the many-to-many association as an entity itself:
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
#OneToMany(cascade = PERSIST, fetch = EAGER, mappedBy = "relationship")
public Set<RelationShipEntityPojo> targets = new LinkedHashSet<>();
}
#Entity
public class RelationShipEntityPojo {
#Column(name = "entity_id")
private Long entityId;
#ManyToOne
private RelationshipPojo relationship;
#ManyToOne
#NotFound(action = IGNORE)
#JoinColumn(insertable = false, updatable = false)
private EntityPojo entity;
}
This way, you'll be able to set a value to the entityId property to a non-existent id, and if an EntityPojo by that id is later inserted, Hibernate will know how to populate relationship properly. The caveat is a more complicated domain model, and the fact that you will need to control the association between RelationshipEntityPojo and EntityPojo using the entityId property, not entity.
EDIT Actually, disregard the above answer, it's overly complicated. Turing85 is right in that you should simply remove the constraint. You can prevent Hibernate from generating it in the first place using:
#ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinTable(inverseJoinColumns = #JoinColumn(name = "target_id", foreignKey = #ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT)))
public Set<EntityPojo> targets = new LinkedHashSet<>();
The only caveat is that when you try to load RelationshipPojo.targets before inserting the missing EntityPojo, Hibernate will complain about the missing entity, as apparently #NotFound is ignored for #ManyToMany.

Hibernate removes relation on update

I want to define my #ManyToMany relationship with JPA Annotations so that relations are not removed when updating entity.
#ManyToMany(targetEntity=Event.class, cascade={CascadeType.ALL}, fetch = FetchType.LAZY)
#JoinTable(
name = "event_user",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "event_id")
)
private Set<Event> events;
and Event class
#ManyToMany(cascade = {CascadeType.ALL}, mappedBy="events", targetEntity=User.class, fetch = FetchType.LAZY)
private Set<User> attending;
I thought setting CascadeType.REMOVE would not trigger deletion when updating but when I call update on a user object, all its related events are removed.
This is from my DAO
#Override
public User update(User entity) {
sessionFactory.getCurrentSession().update(entity);
return entity;
}
When I call update on my entity, Hibernate does:
Hibernate: delete from event_user where user_id=?
The comments on your questions are correct so far. You obviously do not load the entity from the database before updating it. Hence, hibernate updates everything just as it finds it in your entity. So, load the entity (by id?), merge your changes and update it afterwards.
Btw you should also consider using the delete orphans annotation. You would hence make sure that events to a user would also get deleted when setting the event collection to null and not only when removing the entire user.

Trying to understand difference in CascadeType.ALL vs. #OnDelete!

Let me get my question straight, using the #OnDelete here will delete this and any other InventoryPreference entities if the Inventory entity is deleted? I just can't understand a thing from Hibernate's annotations reference.. so I need your help to confirm that I understood it correctly.
public class InventoryPreference {
...
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "inventory_id", nullable = false)
public Inventory getInventory() {
return inventory;
}
}
Do I then in the Inventory entity need to use CascadeType.ALL too to get all the InventoryPreferences deleted if the Inventory entity is deleted?
public class Inventory {
...
#OneToMany(mappedBy = "inventory", cascade = CascadeType.ALL)
public Set<InventoryPreference> getPreferenceItems() {
return preferenceItems;
}
}
If the first question is true, then I don't see the point of CascadeType.ALL. If it's not then what do each of these do and what annotations and configuration I need to specify to get the InventoryPreferences deleted when Inventory is deleted? Oh and I don't want the Inventory to be deleted if InventoryPreference gets deleted. Sorry if it's too obvious.
They do somewhat different things. #OnDelete is a schema generation instruction. It will add 'on delete cascade' to the end of the DDL generated for the foreign key (or dialect equivalent.) If you're not using hibernate to generate your database, it isn't going to do anything.
The cascade property on the #OneToMany or #ManyToOne is what's used at runtime to generate additional actual SQL statements. That's probably what you actually want, additional delete statements to remove the children, not delete cascades turned on in the database table? If what you want is for InventoryPreferences to get removed when you delete an Inventory, then you want:
#OneToMany(mappedBy = "inventory", cascade = CascadeType.REMOVE, orphanRemoval=true)
public Set<InventoryPreference> getPreferenceItems() {
return preferenceItems;
}
And of course add additional Cascade Types as appropriate to your design.

Hibernate not populate data from mapping table

I have User class and Country class with respective tables. Country table data is fixed.
I make a mapping table User_Country(userid, countryid) and following mapping in User class
#OneToMany(fetch=FetchType.EAGER)
#JoinTable(name = "User_Country", joinColumns ={
#JoinColumn(name = "userid")
}, inverseJoinColumns = {
#JoinColumn(name = "COUNTRYID")
})
private Set<Country> country;
When i persist User class it successfully persist user data and insert data in mapping table(user_country). This is exactly i want but when i find User by using hql('from user where userid=?') and then try to get country maaping (which is stored in mapping tableuser_country). I didn't get any data from user_country. How can i write annotation so that it gets data from user_country. If i put cascade then it update country table(which is fixed) which i don't want.
I am not too sure but, try with inverse="false", as that might help.

Categories

Resources