I am developping a web application of REST type, with Angular (Typescript) for the frontend, and Spring (boot, rest, data, hibernate) for the backend which is connected to a MySQL database. In this application I’m using JPA annotation.
My relations between objets are all bi-directionals :
- A User has one Address, and an Address can corresponds to one or several User(s) : User n - 1 Address
- A user provides one or more Report(s), and a Report is provided by only one User : User 1 - n Report
- User n - n Role (so association table User_Role)
- User n - n Group (so association table User_Group)
My issue is that hibernate deletes the link with the child entity (the non-owning) when I modify the owner side, and I don’t want that. I will detail my issue and ask questions below, after my code.
In User.java
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "userId")
private Long userId;
private String firstName;
private String lastName;
private String email;
...
@JsonSerialize(using = AddressCustomSerializer.class)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="address_id")
private Address address;
@ManyToMany
@JoinTable( name = "user_role",
joinColumns = @JoinColumn( name = "role_id" ),
inverseJoinColumns = @JoinColumn( name = "user_id" ) )
private List<Role> roles = new ArrayList<>();
@OneToMany(mappedBy = "user")
private List<Report> report = new ArrayList<>();
// List of groups the user belongs to.
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "user_group",
joinColumns = { @JoinColumn(name = "user_id") },
inverseJoinColumns = { @JoinColumn(name = "group_id") }
)
private List<Group> userGroups = new ArrayList<>();
In Address.java
@OneToMany(mappedBy = "address")
private List<User> users = new ArrayList<>();
In Role.java
@JsonSerialize(using = UserListForRoleCustomSerializer.class)
@ManyToMany(mappedBy = "roles")
private List<User> users = new ArrayList<>();
In Group.java
// List of users included in the group.
@JsonSerialize(using = UserListForGroupCustomSerializer.class)
@ManyToMany(mappedBy = "userGroups")
private List<User> users = new ArrayList<>();
in Report.java
@JsonSerialize(using = UserCustomSerializer.class)
@ManyToOne @JoinColumn(name="user_id")
private User user;
When I click on a user, my backend sends me a json file containing all values for the user attributes and all linked objects which is OK.
But when I modify a simple User attribute like firstname or lastname, I have to send to the backend a json file containing all attributes and all linked objects.
If not, Hibernate deletes the link with the child entity (the non-owning), like you can see below in SQL traces :
2020-04-27 org.hibernate.SQL : update user set address_id=?, charter_acceptance_date=?, creation_date=?..
The column “address_id” is set to null, but the corresponding address is still in the Address table.
2020-04-27 org.hibernate.SQL : delete from user_role where role_id=?
In the association table “User_Role” the row, containing “user_id” and “role_id”, is deleted. But the corresponding role is still in Role table, and the corresponding user is still in User table.
2020-04-27 org.hibernate.SQL : delete from user_group where user_id=?
Same problem in association table “User_Group”.
1) From my understanding :
- if I don’t send all linked entities, Hibernate “thinks” that the owner-side (the parent entity) User is not linked any more to entities, and hibernate deletes theses links.
- Regarding “User 1 - n Report” relation, the link beetwen User and Report is not deleted because Report is the owning-side (parent). But when I will want to modify a simple attribute of a report, I will have the same issue.
Am I right ?
2) How to avoid this problem, knowing that :
- I wouldn’t like to send a json file containing ALL attributes and ALL linked objects, if I modify only a simple attribute of the user.
- I would like to managed myself what link, or entity is deleted or updated, when I modify or delete a linked entity (owner-side or not).
- I’m going to have ternary and quaternary relations in my datase. I will need to customize each update and deletion, object by object.
- I tried @SelectBeforeUpdate(value=true) @DynamicUpdate(value = true) annotations, but I have the same issue.
- I don’t use EntityManager, Sring does it for me.
3) At the beginning I used Hibernate cascades (CascadeType.MERGE and CascadeType.PERSIST) on all relations quoted in my code. But :
- I had the same issue ;
- And someone told me that hibernate cascades must be reserved for the UML composition relation. For example : if I delete a user and I want all his/her reports to be deleted. But it is not my need !! If I delete a user I want to keep his/her reports.
- What do you think about the fact that hibernate cascades must be reserved for the UML composition relation ?
Many thanks in advance for your answers.