0

I have a user and a movie model: user:

@Entity(name = "User")
@Table(name = "USER")
public class User {

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
    @SequenceGenerator(name = "user_seq", sequenceName = "user_seq", allocationSize = 1)
    private Long id;

    @Column(name = "USERNAME", length = 50, unique = true)
    @NotNull
    @Size(min = 4, max = 50)
    private String username;

    @Column(name = "PASSWORD", length = 100)
    @NotNull
    @Size(min = 4, max = 100)
    private String password;

    @Column(name = "FIRSTNAME", length = 50)
    @NotNull
    @Size(min = 4, max = 50)
    private String firstname;

    @Column(name = "LASTNAME", length = 50)
    @NotNull
    @Size(min = 4, max = 50)
    private String lastname;

    @Column(name = "EMAIL", length = 50)
    @NotNull
    @Size(min = 4, max = 50)
    private String email;

    @Column(name = "ENABLED")
    @NotNull
    private Boolean enabled;

    @Column(name = "LASTPASSWORDRESETDATE")
    @Temporal(TemporalType.TIMESTAMP)
    @NotNull
    private Date lastPasswordResetDate;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "USER_AUTHORITY",
            joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
            inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")})
    private List<Authority> authorities;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public List<Authority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<Authority> authorities) {
        this.authorities = authorities;
    }

    public Date getLastPasswordResetDate() {
        return lastPasswordResetDate;
    }

    public void setLastPasswordResetDate(Date lastPasswordResetDate) {
        this.lastPasswordResetDate = lastPasswordResetDate;
    }

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

    public Set<Movie> getMovies() {
        return movies;
    }

    public void setMovies(Set<Movie> movies) {
        this.movies = movies;
    }


}

movie:

@Entity(name = "Movie")
@Table(name = "movie")
public class Movie {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String name;

    public Movie(){}

    public Movie(Integer id, String name ) {
        this.id = id;
        this.name = name;
    }

    @ManyToMany(mappedBy = "movies")
    private Set<User> users;    
    public Set<User> getUsers() {
        return users;
    }

    public void addUser(User user){
        System.out.println("ADD MOVIE: " + user);
        users.add(user);
        user.getMovies().add(this);
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString(){
        return "id: " + id + "name: " + name;
    }

}

I've set up a many to many relation between these models. With, if I am correct, the user as the owner of the relation.

In my MovieController.java I have:

@RequestMapping(value = "/", method = RequestMethod.POST)
public Movie createMovie(@RequestBody Movie movie){
    return movieService.createMovie(movie);
}

This calls the MovieService.java:

@Override 
public Movie createMovie(Movie movie) {
    return movieRepository.save(movie);
}

And this calls the MovieRepository.java:

@Repository
public interface MovieRepository extends CrudRepository<Movie, Serializable> {}

When I call the post methode from my front-end a movie record is saved in my movie table, but no record is created in the user_movie table. Doesn't Hibernate do this implicit since I set up a Many to Many relation between user and movie?

Peter Boomsma
  • 8,851
  • 16
  • 93
  • 185
  • Already has an answer here: https://stackoverflow.com/questions/3484325/jpa-which-side-should-be-the-owning-side-in-a-mn-relationship – Peter Rader Nov 09 '17 at 14:03
  • @PeterRader how does this answer my question? I'm asking how to store a manytomany record. Not asking about the ownership. – Peter Boomsma Nov 09 '17 at 14:06
  • The accepted answer pointed out that in a n-m relation one side is the owning side. In your case User is the owning side. You must save the User having the Movie referenced, but you save the Movie referencing the User what is not the owning side and will not work. – Peter Rader Nov 09 '17 at 14:10
  • This is quite hard to grasp. It doesn't matter which of the entities is the owner. But I do have to save the owner side? So If I would change the ownership to the Movie entity, how/where do I reference the current user? – Peter Boomsma Nov 09 '17 at 14:24
  • It has nothing to do with the owner you yust need to use the cascade – Tom Nov 09 '17 at 14:33
  • I'm using ` CascadeType.ALL`. The output in the console doesn't change if I comment out the relationship between user/movie. So it looks like Hibernate isn't using it at all. – Peter Boomsma Nov 09 '17 at 14:40

2 Answers2

0

You save the movie and in order to also have the user saved the cascade has to be set in the movie. Otherwise you can keep the cascade in user and save him.

You need to put the cascade to the entity on which you call save to cascade it.

Movie{
    @ManyToMany(mappedBy = "movies", cascade={CascadeType.ALL})
    private Set<User> users;    
    public Set<User> getUsers() {
        return users;
    }
}

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

Don't forget to add the user to movie and vice versa before saving.

As with all bi-directional relationships it is your object model's and application's responsibility to maintain the relationship in both direction. There is no magic in JPA, if you add or remove to one side of the collection, you must also add or remove from the other side, see object corruption. Technically the database will be updated correctly if you only add/remove from the owning side of the relationship, but then your object model will be out of synch, which can cause issues.

see here: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany

Tom
  • 977
  • 11
  • 18
  • Hm. If I add the mappedBy annotation to the movieModel hibernate crashes. If I remove it the code compiles and hibernate creates a table caled movie_users with a movies_id and users_id column but when I add a movie nothing is inserted into that table. – Peter Boomsma Nov 09 '17 at 15:16
  • Updated my answer. Hope it helps. – Tom Nov 09 '17 at 15:48
  • Where do I add the current user to the movie? In the controller, service? – Peter Boomsma Nov 09 '17 at 16:04
  • Service should be fine. Add the movie to the user and the user to the movie. Then save the movie. – Tom Nov 09 '17 at 16:14
  • I should add the current user id in the createMovie method in the service and pass that into the .save method of the movieRepository? – Peter Boomsma Nov 09 '17 at 16:21
  • Ok mybe my wording was misleading. You don't have to add the current user. You need to make shure that movie holds the users you want and the user that belongs to that movie needs to have it set also. – Tom Nov 09 '17 at 16:32
  • I just added this code http://javabycode.com/spring-framework-tutorial/spring-boot-tutorial/spring-boot-jpa-many-to-many-relationship-mapping-example.html in my project and when I ran it, it added the subject and students in a join table so I'm going to check why that's working and my code aint :) – Peter Boomsma Nov 09 '17 at 16:33
0

For the first view, your code is correct.
The problem can be in GenerationType.SEQUENCE (try to use GenerationType.AUTO for User's id), or you need to add @Transactional to your controller.