I have two entities, films and genres, made like this
Film:
package com.cinematic.cinematic.models;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Set;
//@NamedEntityGraph(
// name = "film-entity-graph",
// attributeNodes = {
// @NamedAttributeNode("filmGenre"),
// }
//)
@Entity
@Data
@AllArgsConstructor
@Builder(toBuilder = true)
@NoArgsConstructor
@Table(name = "films")
public class Film {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long filmId;
@Column(name = "cover_img")
private String coverImg;
@Column(name = "title")
private String title;
@Column(name = "nation_of_production")
private String nationOfProduction;
@Column(name = "plot")
private String plot;
@Column(name = "rating")
private Float rating;
@Column(name = "fun_facts")
private String funFacts;
@OneToMany(mappedBy = "film", fetch = FetchType.EAGER)
private Set<FilmsGenres> filmGenre;
}
Genre:
package com.cinematic.cinematic.models;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Set;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
@Table(name = "genres")
//@NamedEntityGraph(
// name = "genre-entity-graph",
// attributeNodes = {
// @NamedAttributeNode("filmsGenres")
// }
//)
public class Genre {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long genreId;
@Column(name = "genre_name")
private String genreName;
@OneToMany(mappedBy = "genre", fetch = FetchType.EAGER)
private Set<FilmsGenres> filmGenre;
}
and they have a many to many relation, made using a bridge table FilmsGenres defined here
package com.cinematic.cinematic.models;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
@Table(name = "films_genres")
@NamedEntityGraph(
name = "films-genres-entity-graph",
attributeNodes = {
@NamedAttributeNode("film"),
@NamedAttributeNode("genre")
}
)
public class FilmsGenres {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long filmGenreId;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "film_id", referencedColumnName = "id")
private Film film;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "genre_id", referencedColumnName = "id")
private Genre genre;
}
I'm trying to define a retrieveAllFilms method to retrieve all films
public List<Film> retrieveAllFilms(){
log.info("Start - retrieveAllFilms - args:none");
val allFilms = filmRepository.findAllWithFilmGenre();
log.info("End - retrieveAllFilms - out: {}", allFilms.size());
return allFilms;
}
this is the filmsRepository
package com.cinematic.cinematic.repositories;
import com.cinematic.cinematic.models.Film;
import jakarta.persistence.NamedEntityGraph;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.Optional;
public interface FilmRepository extends JpaRepository<Film, Long> {
@EntityGraph(attributePaths = "filmGenre")
List<Film> findAllByTitleContaining(String title);
@EntityGraph(attributePaths = "filmGenre")
Optional<Film> findByTitle(String title);
@Query("SELECT f FROM Film f")
@EntityGraph(value = "films-genres-entity-graph", type = EntityGraph.EntityGraphType.LOAD)
List<Film> findAllWithFilmGenre();
}
the endpoint actually works, but it doesn't retrieve the element in the relation, I get this as result
[
{
"filmId": 1,
"coverImg": "https://i.ebayimg.com/images/g/KxgAAOSwqjpan3GV/s-l1200.webp",
"title": "the avengers",
"nationOfProduction": "U.S.A",
"plot": "è un film brutto dove salvano il mondo (devono proprio)",
"rating": 2.0,
"funFacts": "Non piace a nessuno(in verità la gente ne va pazza non capisco il perchè)",
"filmGenre": []
}
]
I'm sure that in the db the relation is defined, since the FilmsGenres table has the right fields with ids but it doesn't retrieve the filmGenre array
First, get rid of lombok's @Data
annotation on your Entity
classes (Film, Genre and FilmsGenres) and replace it with lombok's @Getter
and @Setter
, these ones are fine. @Data
It is known to cause issues with JPA entities (problems with equals, hashcode etc), see the following for more info:
Once you've done it, filmGenre in Film will be fetched, but you may experience infinite recursive fetch if using Spring standard settings for Jackson (that is because Film aks for FilmsGenres to be fetched, FilmsGenres aks for Film to be fetched, and this cause an infinite nested json).
To solve this last problem, you could add com.fasterxml.jackson.annotation.JsonIgnore
annotation on FilmsGenres.film
field and on Genre.filmGenre
field too (this will cause the circular json dependency as well).
In the end you should get a result like this:
[
{
"filmId": 1,
"coverImg": "https://i.ebayimg.com/images/g/KxgAAOSwqjpan3GV/s-l1200.webp",
"title": "the avengers",
"nationOfProduction": "U.S.A",
"plot": "è un film brutto dove salvano il mondo (devono proprio)",
"rating": 2.0,
"funFacts": "Non piace a nessuno(in verità la gente ne va pazza non capisco il perchè)",
"filmGenre": [
{
"filmGenreId": 2,
"genre": {
"genreId": 2,
"genreName": "sci-fi"
}
},
{
"filmGenreId": 1,
"genre": {
"genreId": 1,
"genreName": "action"
}
},
{
"filmGenreId": 3,
"genre": {
"genreId": 3,
"genreName": "adventure"
}
}
]
}
]