Skip to content

PUT Request Issue with Composite Keys and OneToMany Relationship (Single Repo for Main Entity)Β #2398

@t0bij

Description

@t0bij

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:

if (property.isIdProperty() || property.isVersionProperty() || !property.isWritable()) {
return;
}

The only working setup is inspired by AresEkb from #2324 and involves:

  • Using a HashMap for the OneToMany ratings and writing getRatings and setRatings methods 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.putItemResource to 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 HashMap and AOP workarounds.
  • experimental/hash-set: Setup with HashSet and AOP, but adding list elements in the middle fails.
  • experimental/plain: Setup with HashSet and without any AOP or workarounds; many tests fail.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions