Reputation: 58
I am building the Rest API with Java, Spring Boot, Hibernate and JPA. I have 2 tables Users and Friends, where User will have an array of Friend. My client will send several update request on my User table. This the request the API will receive :
{
"username": "bertrand",
"email": "[email protected]",
"birthdate": "11/05/1984",
"lang": "fr",
"secretKey": "qefieqjnhfoipqnhefipqnfp",
"updatedOn": "mise a jour date",
"createdOn": "creation date",
"friends": [
{
"username": "philippe",
"userId": 2,
"email": "[email protected]"
}
]
}
If my client send this request, my API returns :
{
"id": 1,
"username": "bertrand",
"password": "$2a$10$zGpK7/SOceA1xZnphrItpuYRkViBa7pNNgt9DsKDH1Q7HY50FE9hm",
"email": "[email protected]",
"birthdate": "11 mai 1984",
"lang": "fr",
"secretKey": "8138124aff2b9226",
"updatedOn": "mise a jour date",
"createdOn": "creation date",
"friends": [
{
"id": 1,
"userId": 2,
"username": "philippe",
"email": "[email protected]"
}
]
}
This answer is good, but if my client send again the same request, the friend array is populated with a duplicated friend.
{
"id": 1,
"username": "bertrand",
"password": "$2a$10$zGpK7/SOceA1xZnphrItpuYRkViBa7pNNgt9DsKDH1Q7HY50FE9hm",
"email": "[email protected]",
"birthdate": "11 mai 1984",
"lang": "fr",
"secretKey": "8138124aff2b9226",
"updatedOn": "mise a jour date",
"createdOn": "creation date",
"friends": [
{
"id": 1,
"userId": 2,
"username": "philippe",
"email": "[email protected]"
},
{
"id": 2,
"userId": 2,
"username": "philippe",
"email": "[email protected]"
}
]
}
I would like that each friend to be unique and not to create a new entry in Friend table if already exists.
This is my User Entity :
Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "username", nullable = false, length = 20, unique = true)
private String username;
@Column(name = "password", nullable = false, length = 100)
private String password;
@Column(name = "email", nullable = false, length = 50, unique = true)
private String email;
@Column(name = "birthdate", nullable = false, length = 100)
private String birthdate;
@Column(name = "language", nullable = false, length = 2)
private String lang;
@Column(name = "secret_key", nullable = false, length = 200)
private String secretKey;
@Column(name = "updated_on", nullable = false, length = 100)
private String updatedOn;
@Column(name = "created_on", nullable = false, length = 100)
private String createdOn;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "owner_id", nullable = false)
private Set<Friend> friends = new HashSet<>();
public User() {
}
public User(String username, String email, String birthdate, String lang, String secretKey, String updatedOn,
String createdOn, Set<Friend> friends, String password) {
this.username = username;
this.email = email;
this.birthdate = birthdate;
this.lang = lang;
this.secretKey = secretKey;
this.updatedOn = updatedOn;
this.createdOn = createdOn;
this.friends = friends;
this.password = password;
}
+ getters ans setters...
and this is my Friend Entity :
@Entity
@Table(name = "friends")
public class Friend {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "user_id", nullable = false, length = 20)
private long userId;
@Column(name = "username", nullable = false, length = 20)
private String username;
@Column(name = "email", nullable = false, length = 50)
private String email;
public Friend(long id, long userId, String username, String email) {
this.id = id;
this.userId = userId;
this.username = username;
this.email = email;
}
What is the best way to do that please ?
Upvotes: 1
Views: 3860
Reputation: 1026
Because your client did not send friendId
and class Friend
@Entity
@Table(name = "friends")
public class Friend {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
So you can't know what friend need to update and friendId
is auto increment so you get the duplication, let update the request like this:
{
"username": "bertrand",
"email": "[email protected]",
"birthdate": "11/05/1984",
"lang": "fr",
"secretKey": "qefieqjnhfoipqnhefipqnfp",
"updatedOn": "mise a jour date",
"createdOn": "creation date",
"friends": [
{
"id": 1,
"username": "philippe",
"userId": 2,
"email": "[email protected]"
}
]
}
Upvotes: 1
Reputation: 19073
That is because when you send a request for that child info
{ "username": "philippe",
"userId": 2,
"email": "[email protected]"
}
your child is considered a new entity for hibernate. Your key is null here. It has no way to know if it is duplicate or not.
1st Solution
Don't send an info for update like that. First get the info from your Api, make changes and send that back for update. This way your child will have the id.
So client hits your api to retrieve user 1 and takes back
userRepository.findById(1L);
Client makes changes to user
Client calls API to update user which calls
userRepository.save(user)
but now the user will have as children
{ "id": 1
"username": "philippe",
"userId": 2,
"email": "[email protected]"
}
2nd solution
Change the key so that your children identity is not based on some database long value but instead is based on some business key. You could mark for example userId with username as a composite key
That way the key will always exist even before persisting something in your database. Therefore hibernate will know when something is duplicate or not even before it is persisted.
Here is a very popular article on that problem that you face
Don't let hibernate steal your identity
3rd solution
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "owner_id", nullable = false)
private Set<Friend> friends = new HashSet<>();
Remove cascade = CascadeType.ALL
from User.
Then let the client from that API call to update only User fields and not something that has to do with it's children. If client wants to update some Friend he must call the relevant API for that specific friend as it has done now for user.
Any Friend info contained in the call for User update will be ignored.
Upvotes: 5