2

I have this method:

@Override 
public Movie createMovie(Movie movie) {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    JwtUser user = (JwtUser)authentication.getPrincipal();
    User current_user = userRepository.findOne(user.getId());       

    movieRepository.save(movie);

    userRepository.save(new HashSet<User>(){{
        add(new User(current_user, new HashSet<Movie>(){{
            add(movie);
        }}));
    }});

    return movieRepository.save(movie);
}

When I run my application and call that function I get this error:

Found shared references to a collection: com.movieseat.model.security.User.movies

In my User model I have:

@ManyToMany
@JoinTable(name = "user_movie",
    joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
    inverseJoinColumns = @JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies;

And in my Movie model I have:

@ManyToMany(mappedBy = "movies", cascade = CascadeType.ALL)
private Set<User> users = new HashSet<>(); 

public Set<User> getUsers() {
    return users;
}

What produces the error?

Peter Boomsma
  • 8,851
  • 16
  • 93
  • 185
  • Taking into account `cascade = CascadeType.ALL` on users inside the movie entity I would suggest you to try adding a new user to the movie and then store the movie itself. Theoretically, It should store both the movie and the user. – Danylo Zatorsky Nov 12 '17 at 13:44
  • can you able to post entitys – Kalaiselvan Dec 01 '17 at 08:58
  • why are you saving two times movie? can you show what the new User(current_user, new HashSet code does? – Zeromus Dec 01 '17 at 09:37

1 Answers1

6

As I understand your code, you're trying to create a Movie in database and bind it to the current User. Correct me if I'm wrong.

At first, as you may learn from Hibernate User Guide, bidirectional @ManyToMany association should be defined and used differently.

A bidirectional @ManyToMany association has an owning and a mappedBy side. To preserve synchronicity between both sides, it’s good practice to provide helper methods for adding or removing child entities.

Secondly, you should not use CascadeType.ALL on @ManyToMany associations:

For @ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.

For example, if @ManyToMany(cascade = CascadeType.ALL) was defined and the first person would be deleted, Hibernate would throw an exception because another person is still associated with the address that’s being deleted.

So, we should move cascade to the owning side, change cascade type, provide helper methods to the User and update only the owning side (User) of the association in our business logic. Let's change the code.

User model:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(name = "user_movie",
    joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
    inverseJoinColumns = @JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies = new HashSet<>();

public void addMovie(Movie movie) {
    movies.add(movie);
    movie.getUsers().add(this);
}

public void removeMovie(Movie movie) {
    movies.remove(movie);
    movie.getUsers().remove(this);
}

Movie model:

@ManyToMany(mappedBy = "movies")
private Set<User> users = new HashSet<>();

And business logic:

@Override
public Movie createMovie(Movie movie) {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    JwtUser user = (JwtUser)authentication.getPrincipal();
    User current_user = userRepository.findOne(user.getId());

    current_user.addMovie(movie);
    userRepository.save(current_user);

    return movie;
}
Anatoly Shamov
  • 2,608
  • 1
  • 17
  • 27
  • Yes, that's exactly what I'm trying to do! Thank you for the explanation and snippets. Things kind of work now. When I create the movie I do create a record in my movie table and in the user_movie but then the application crashes. So I think there's a reference somewhere that causes a infinite loop.` Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Infinite recursion (StackOverflowError) (through reference chain: org.hibernate.collection.internal.PersistentBag[0]->com.movieseat.model.security.Authority["users"]` – Peter Boomsma Dec 01 '17 at 12:10
  • The problem stated above is resolved when I remove the many to many relation between movies and users. – Peter Boomsma Dec 01 '17 at 12:20
  • Seems like the message converter (MappingJackson2HttpMessageConverter, if you respond with JSON) can't convert `Movie` due to the recursive chain Movie -> users -> User -> movies -> etc. Try to mark `User.movies` or `Movie.users` with @JsonIgnore annotation. – Anatoly Shamov Dec 01 '17 at 12:26
  • Looks like this is a different issue, the message states: `->org.hibernate.collection.internal.PersistentBag[0]->com.movieseat.model.security.User["authorities"]->org.hibernate.collection.internal.PersistentBag[0]->com.movieseat.model.security.Authority["users"]` . I'll reward you the answer for this question and the bounty. Thanks. – Peter Boomsma Dec 01 '17 at 12:46
  • Oh, then it's just different entites that have recursive reference chain - User.authorities‌​ and Authority.user‌​s. See [this question's](https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue) answers. Seems like `@JsonManagedReference` and `@JsonBackReference` should handle this as well as `@JsonIgnore`. – Anatoly Shamov Dec 01 '17 at 12:54
  • Thanks, that question helped :) Cheers for the help! – Peter Boomsma Dec 01 '17 at 13:50