-
Notifications
You must be signed in to change notification settings - Fork 561
Description
Reproducer:
I've created a reproducer to demonstrate the issue. The repository is available here.
Summary
The main entity Movie has a OneToMany relation to Ratings. Ratings has a composite primary key with movieID and ratingPlatformId. Only the Movie entity has a REST repository, and IDs are exposed. A single movie API exists where GET works fine, but updating via PUT doesn't. Updating the ratings list gives unexpected results. For example:
Before:
{
"id": 2,
"name": "The Dark Knight",
"ratings": [
{
"ratingPlatformId": 1,
"score": 10
}
]
}PUT Request:
{
"id": 2,
"name": "The Dark Knight",
"ratings": [
{
"ratingPlatformId": 2,
"score": 2
}
]
}Expectation:
The rating for ratingPlatformId 1 should be deleted, and a new one with ratingPlatformId 2 should be created.
Actual Result:
{
"id": 2,
"name": "The Dark Knight",
"ratings": [
{
"ratingPlatformId": 1,
"score": 2
}
]
}I debugged a bit and ratingPlatformId is not considered at all during the PUT operation and gets skipped here as it is an ID:
Lines 688 to 690 in aaadc34
| if (property.isIdProperty() || property.isVersionProperty() || !property.isWritable()) { | |
| return; | |
| } |
The only working setup is inspired by AresEkb from #2324 and involves:
- Using a
HashMapfor theOneToManyratings and writinggetRatingsandsetRatingsmethods manually.
@OneToMany(mappedBy = "movie", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference("movie-ratings")
@MapKey(name = "ratingPlatformId")
@ToString.Exclude
private Map<Long, Rating> ratings = new HashMap<>();
public Collection<Rating> getRatings() {
return ratings.values();
}
public void setRatings(Collection<Rating> ratings) {
this.ratings.clear();
ratings.forEach(rating -> this.ratings.put(rating.getRatingPlatformId(), rating));
}- Adding some AOP workaround for
RepositoryEntityController.putItemResourceto set the movie in the ratings.
@Around("putItemResource()")
public Object aroundPutItemResource(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
if (args[1] instanceof PersistentEntityResource payload) {
Object content = payload.getContent();
if (content instanceof Movie updatedMovie) {
for (Rating rating : updatedMovie.getRatings()) {
rating.setMovieId(updatedMovie.getId());
rating.setMovie(updatedMovie);
}
}
}Branches
- main: Working "solution" with
HashMapand AOP workarounds. - experimental/hash-set: Setup with
HashSetand AOP, but adding list elements in the middle fails. - experimental/plain: Setup with
HashSetand without any AOP or workarounds; many tests fail.