Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
t0bij opened this issue Jul 17, 2024 · 0 comments
Assignees
Labels
status: waiting-for-triage An issue we've not yet triaged

Comments

@t0bij
Copy link

t0bij commented Jul 17, 2024

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.
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged
Projects
None yet
Development

No branches or pull requests

3 participants