Reputation: 426
I would like to update an entity without updating/editing child entities. Here is the entity in question:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
//other fields omitted for brevity
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "project_user",
joinColumns = @JoinColumn(name="user_id", updatable = false),
inverseJoinColumns = @JoinColumn(name="project_id",updatable = false))
@JsonIgnore
private Set<Project> projects;
//getters and setters omitted for brevity
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (id != other.id)
return false;
return true;
}
}
Here is the project entity:
@Entity
@Table(name = "project")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;
//other fields omitted for brevity
@ManyToMany
@JoinTable(name = "project_user",
joinColumns = @JoinColumn(name="project_id"),
inverseJoinColumns = @JoinColumn(name="user_id"))
@JsonIgnoreProperties(value = {"projects", "connected", "notifications"})
private Set<User> members;
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Project other = (Project) obj;
if (id != other.getId())
return false;
return true;
}
}
Here is the merge procedure:
@Override
public void update(User user) {
//user is created from a dto as a new object (that has its id set manually)
entityManager.merge(user);
}
The issue is the hibernate logs delete from the join table. How can I stop that?
Hibernate logs
Hibernate: //login query
select
distinct authuser0_.id as id1_7_,
authuser0_.enabled as enabled8_7_,
authuser0_.password as password5_7_,
authuser0_.username as username6_7_,
roles1_.username as username1_0_0__,
roles1_.authority as authorit2_0_0__
from
users authuser0_
inner join
authorities roles1_
on authuser0_.username=roles1_.username
where
authuser0_.username=?
Hibernate: //entityManager.merge() calls this to merge it
select
user0_.id as id1_7_0_,
user0_.email as email2_7_0_,
user0_.first_name as first_na3_7_0_,
user0_.image as image7_7_0_,
user0_.last_name as last_nam4_7_0_,
user0_.password as password5_7_0_,
user0_.username as username6_7_0_
from
users user0_
where
user0_.id=?
Hibernate: //the actual update statement
update
users
set
email=?,
first_name=?,
image=?,
last_name=?,
password=?,
username=?
where
id=?
Hibernate: // not wanted, need to stop this query
delete
from
project_user
where
user_id=?
I have read online that it has to do with equals and hashcode. However, I can't seem to get it to work.
I know of two options both of which are not optimal:
Thanks In Advance
Upvotes: 2
Views: 2219
Reputation: 11551
Well, I know you accepted an answer already, but ...
There is more going on there than you have thought about.
The reason there is a Hibernate: delete from project_user where user_id=?
is that there are inserts afterwards. Did you not see them? Perhaps the relationship was not updated if the relationship table was emptied for that user but not inserted later? A delete only can be reproduced as well but you should test your code and verify that there are no projects for that user. That probably means that you either didn't update the projects field in the merged user.
So, running a basic JPA example, let's start with your problem by creating a starting point:
em.getTransaction().begin();
Project project = new Project();
em.persist(project);
User user = new User();
Set<Project> projects = new HashSet<>();
projects.add(project);
user.setProjects(projects);
em.persist(user);
em.getTransaction().commit();
Then, clear the entityManager context:
em.clear();
And then reproduce your problem:
em.getTransaction().begin();
Project p2 = new Project();
em.persist(p2);
projects.add(p2);
User un = em.find(User.class, 1);
un.setProjects(projects);
em.merge(un);
em.getTransaction().commit();
This second part gives me the logs:
Hibernate: insert into project (id) values (default)
Hibernate: select user0_.id as id1_2_0_ from users user0_ where user0_.id=?
Hibernate: select project0_.id as id1_0_0_ from project project0_ where project0_.id=?
Hibernate: delete from project_user where user_id=?
Hibernate: insert into project_user (user_id, project_id) values (?, ?)
Hibernate: insert into project_user (user_id, project_id) values (?, ?)
Now let's remove the offending delete
statement by changing the code and running it again:
Project p2 = new Project();
em.persist(p2);
User un = em.find(User.class, 1);
projects = un.getProjects();
projects.add(p2);
em.merge(un);
em.getTransaction().commit();
Which gives me the logs:
Hibernate: insert into project (id) values (default)
Hibernate: select user0_.id as id1_2_0_ from users user0_ where user0_.id=?
Hibernate: select projects0_.user_id as user_id1_1_0_, projects0_.project_id as project_2_1_0_, project1_.id as id1_0_1_ from project_user projects0_ inner join project project1_ on projects0_.project_id=project1_.id where projects0_.user_id=?
Hibernate: insert into project_user (user_id, project_id) values (?, ?)
No delete statement. Look carefully at the first "second part" and notice that I added the new project to the original set of projects which was no longer part of the JPA persistence context. In the second example I specifically retrieved the set of projects from the user
object that was part of the JPA persistence context. Now, when I update the set of projects and merge the user JPA doesn't have to start over fresh with the user/project relationship.
BTW: The accepted answer is accurate in terms of the mappedBy
logic, but this code was done with your original example. As long as JPA knows what you are doing it will handle it properly. When you do things outside of JPA then JPA will "punt" and recreate.
Upvotes: 3
Reputation: 10716
Why are two distinct associations (User.projects
and Project.users
) mapped to the same join table?
If you want a bidirectional association, only one of the sides should be the owning side, the other side should be mapped with mappedBy
. Using mappedBy
for the User.projects
side will solve your problem, by the way.
Upvotes: 2