Reputation: 119
I'm learning about hibernate and i wanted to try and experiment with relations. ManyToMany in particular is rather troublesome for me. Hibernate generates db schema for me. It creates movies and actors entities properly, but it's not updating link table (actors_movies). I'm using h2 database.
Actor
package Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "actors")
@EqualsAndHashCode
public class Actor {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "actor_id", columnDefinition = "VARCHAR(255)")
private UUID id;
@Column
private String name;
@Column(name = "last_name")
private String lastName;
@Column(name = "year_of_birth")
private int yearOfBirth;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(
name = "actors_movies",
joinColumns = { @JoinColumn(name = "actor_id") },
inverseJoinColumns = { @JoinColumn(name = "movie_id") }
)
//@Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Movie> movies = new ArrayList<>();
}
Movie
package Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "movies")
@EqualsAndHashCode
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "movie_id", columnDefinition = "VARCHAR(255)")
private UUID id;
@Column
private String title;
@Column
private int yearOfRelease;
@Column
private int genre;
@ManyToMany(mappedBy = "movies",fetch = FetchType.LAZY, cascade = CascadeType.ALL)
//@Cascade(org.hibernate.annotations.CascadeType.ALL)
private List<Actor> actors = new ArrayList<>();
}
Main
import Connection.HibernateUtil;
import Entity.Actor;
import Entity.Movie;
import org.hibernate.Session;
import org.hibernate.Transaction;
import java.util.List;
public class CinemaTest {
public static void main(String[] args) {
Actor actor = new Actor();
actor.setLastName("aktorrrr");
actor.setYearOfBirth(8837);
Movie movie1 = new Movie();
Movie movie2 = new Movie();
movie1.setTitle("jakisTytul");
movie2.setTitle("ffggggggggggggggggfgfg");
actor.setMovies(List.of(movie1, movie2));
Transaction transaction = null;
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
session.save(movie1);
session.save(movie2);
session.save(actor);
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
}
}
Upvotes: 2
Views: 581
Reputation: 13041
When you use bidirectional @ManyToMany association you should make both sides in-sync.
So, I would suggest you to correct your mapping in this way:
@Entity
public class Actor {
// ...
@ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
name = "actors_movies",
joinColumns = { @JoinColumn(name = "actor_id") },
inverseJoinColumns = { @JoinColumn(name = "movie_id") }
)
private List<Movie> movies = new ArrayList<>();
public void addMovie(Movie movie) {
movies.add( movie );
movie.getActors().add( this );
}
public void removeMovie(Movie movie) {
movies.remove( movie );
movie.getActors().remove( this );
}
}
@Entity
public class Movie {
// ...
@ManyToMany(mappedBy = "movies", fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Actor> actors = new ArrayList<>();
}
and then you can do something like this:
Actor actor = new Actor();
// ...
Movie movie1 = new Movie();
Movie movie2 = new Movie();
// ...
actor.addMovie(movie1);
actor.addMovie(movie2);
// ...
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
// due to cascade = {CascadeType.PERSIST, CascadeType.MERGE}
// movies will be saved as well
session.save(actor);
transaction.commit();
}
P.S.
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
. That is why it's not recommended to use cascade = CascadeType.ALL
with @ManyToMany
associations.
Look also at this article. This is not a good idea to use lombok @Data
annotation with @Entity
.
Upvotes: 1