I am currently trying out Spring boot and working with a test project, I ran into a problem with @ManyToMany-Relationships.
There should be movies, that can be saved and they can have multiple genres, actors and stuff like that. The actors can take part in many movies.
Now I can save the movie to the database, but for some reason I can only read out simple data, like the title or the year it was produced. If I try to print the genres to the command line, or the actors taking part, I get the following exception:
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.demo.Movie.actors, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:612) at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218) at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:591) at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149) at org.hibernate.collection.internal.PersistentBag.get(PersistentBag.java:561) at com.example.demo.MoviedbApplication.main(MoviedbApplication.java:75)
Here's my Code:
ConfigurableApplicationContext run = SpringApplication.run(MoviedbApplication.class, args);
GenreRepository genreRepository = run.getBean(GenreRepository.class);
ActorRepository actorRepository = run.getBean(ActorRepository.class);
AuthorRepository authorRepository = run.getBean(AuthorRepository.class);
DirectorRepository directorRepository = run.getBean(DirectorRepository.class);
MovieRepository movieRepository = run.getBean(MovieRepository.class);
Movie theGodfather = new Movie();
theGodfather.setTitle("Der Pate");
theGodfather.setYear(1972);
theGodfather.setOriginalTitle("The Godfather");
theGodfather.setLengthInMinutes(175);
theGodfather.setPlot("Der alternde Patriarch einer Verbrecherdynastie will die Herrschaft über sein geheimes Reich auf seinen widerwilligen Sohn übertragen.");
theGodfather.setAge(16);
List<Director> dirs = new ArrayList<>();
dirs.add(new Director("Francis Ford Coppola"));
List<Actor> acts = new ArrayList<>();
acts.add(new Actor("Marlon Brando"));
acts.add(new Actor("Al Pacino"));
acts.add(new Actor("James Caan"));
List<Genre> genres = new ArrayList<>();
genres.add(new Genre("Krimi"));
genres.add(new Genre("Drama"));
List<Author> authors = new ArrayList<>();
authors.add(new Author("Mario Puzo"));
authors.add(new Author("Francis Ford Coppola"));
theGodfather.setActors(acts);
theGodfather.setAuthors(authors);
theGodfather.setDirectors(dirs);
theGodfather.setGenres(genres);
movieRepository.save(theGodfather);
List<Movie> der_pate = movieRepository.findByTitle("Der Pate");
Movie movie = der_pate.get(0);
System.out.println("Test");
System.out.println(movie.getYear());
System.out.println(movie.getTitle());
System.out.println(movie.getId());
System.out.println(movie.getActors().get(0).getName());
System.out.println(movie.getAuthors().get(0).getName());
@Entity
@Table(name="movies")
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String title;
private String originalTitle;
private int year;
private int age;
private int lengthInMinutes;
private String plot;
private String linkToCover;
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_genres",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name = "genre_id")
)
private List<Genre> genres = new ArrayList<>();
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_actors",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name="actor_id")
)
private List<Actor> actors = new ArrayList<>();
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_directors",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name = "director_id")
)
private List<Director> directors = new ArrayList<>();
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_authors",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name = "author_id")
)
private List<Author> authors = new ArrayList<>();
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_genres",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name = "genre_id")
)
private List<Genre> genres = new ArrayList<>();
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_actors",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name="actor_id")
)
private List<Actor> actors = new ArrayList<>();
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_directors",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name = "director_id")
)
private List<Director> directors = new ArrayList<>();
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinTable(
name = "movie_authors",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name = "author_id")
)
private List<Author> authors = new ArrayList<>();
@Entity @Table(name="Actor") public class Actor {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
@ManyToMany
@Cascade({CascadeType.PERSIST})
@JoinColumn(name = "movie_id")
private List<Movie> movies = new ArrayList<>();
public Actor(String name) {
this.name=name;
}`
What I already tried, was saving the actors and genres to their corresponding repositories, which lead to the following exception:
detached entity passed to persist: com.example.demo.Actor; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.demo.Actor
I also fumbled around with eager fetching and stuff..Now I think, I have missed something quite essential here, so I am sorry, if this sounds like a beginners question..
Thank you very much in advance!
CodePudding user response:
It is about the fetching type and session management. If you want to access actors associated with your movie, you need to set your fetch type as @ManyToMany(fetch = FetchType.EAGER)
in this situation.
Default fetch type is LAZY for ManyToMany relations.
Probably, your movie entity was detached from session after getting it from DB. So, even if you have line as movie.getActors() you get LazyInitializationException.
See for detail about hibernate lazy loading.
https://howtodoinjava.com/hibernate/lazy-loading-in-hibernate/
CodePudding user response:
in you "findByTitle" method, you must load your ManyToMany relations as they are fetched LAZY by default. To load the collections, you can use the size() method. For example :
List<Movie> findByTitle(String title) {
List<Movie> movies;
//fetch movies
//now load ManyToMany relationships
for(var movie : movies) {
movie.getActors().size();
movie.getAuthors().size();
}
return movies;
}
I also suggest to use a Set instead of a List in your ManyToMany relationships