Home > Back-end >  How to retrieve data from parent-child tables using Spring Data JPA?
How to retrieve data from parent-child tables using Spring Data JPA?

Time:12-03

In my Spring Boot app, I use Hibernate and applied the necessary relations to the following entities properly.

@Entity
public class Recipe {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable=false, length=50)
    private String title;

    @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
    private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}

@Entity
public class RecipeIngredient {

    @EmbeddedId
    private RecipeIngredientId recipeIngredientId = new RecipeIngredientId();

    @ManyToOne(optional = true, fetch = FetchType.LAZY)
    @MapsId("recipeId")
    @JoinColumn(name = "recipe_id", referencedColumnName = "id")
    private Recipe recipe;

    @ManyToOne(optional = true, fetch = FetchType.LAZY)
    @MapsId("ingredientId")
    @JoinColumn(name = "ingredient_id", referencedColumnName = "id")
    private Ingredient ingredient;
}

@Entity
public class Ingredient
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique=true, nullable=false, length=50)
    @EqualsAndHashCode.Include
    private String name;

    @OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL)
    private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
}

Now I am trying to retrieve data by merging related entities. For example, when retrieving a Recipe, I also need to retrieve all Ingredients belonging to this Recipe.

As far as I know, I can use Projection and maybe it is better to only use Hibernate features and retrieve related table data via Java Stream. I have no idea how should I retrieve data via Hibernate.

Suppose that I just need an Optional<Recipe> that has List<Ingredient>. Then, I probably need a DTO class something like that:

@Data
public class ResponseDTO {

    private Long id;
    private String title;

    List<RecipeIngredient> ingredients;

    // getter, setter, constructor
}

So, how should I populate this DTO with the requested Recipe and corresponding Ingredient data (getting Ingredient names besides id values) using Java Stream?

Or if you suggest Projection way, I tried it but the data is multiplied by the ingredient count belonging to the searched recipe.


Update:

@Getter
@Setter
@NoArgsConstructor
public class ResponseDTO {

    private Long id;
    private String title;

    List<IngredientDTO> ingredientDTOList;

    public ResponseDTO(Recipe recipe) {
        this.id = recipe.getId();
        this.title = recipe.getTitle();

        this.ingredientDTOList = recipe.getRecipeIngredients().stream()
                .map(ri -> new IngredientDTO(ri.getIngredient().getName()))
                .toList();
    }
}

@Getter
@Setter
public class IngredientDTO {

    private Long id;
    private String name;

    public IngredientDTO(String name) {
        this.name = name;
    }
}

CodePudding user response:

First, in the ResponseDTO you will need you change the type of ingredients from List<RecipeIngredient> to List<Ingredient>.

To manually perform the mapping, you should use (to map from a suppose Recipe recipe to a RespondeDTO response):

ResponseDTO recipeToResponseDTO(Recipe recipe) {
    ResponseDTO response = new ResponseDTO();
    response.setId(recipe.getId());
    response.setTitle(recipe.getTitle());
    response.setIngredients(recipe.recipeIngredients.stream()
            .map(RecipeIngredient::getIngredient()
            .collect(Collectors.toList());
    return response;
}

On the other hand, to model a n-n relation, I encourage you to use the approach proposed by E-Riz in the comment.

  • Related