Search code examples
javaspringspring-data-jpamany-to-many

Trying to retrieve data from a manytomany relation with bridge table in spring using Jpa and Hibernate


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


Solution

  • 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"
                    }
                }
            ]
        }
    ]