Hibernate 为什么在本机查询中休眠惰性加载的子实体?

Hibernate 为什么在本机查询中休眠惰性加载的子实体?,hibernate,spring-data-jpa,Hibernate,Spring Data Jpa,我不明白,当我使用JPQL和JOIN fetch时,hibernate应该执行一个查询来连接子实体,但当我想使用本机查询并使用一个查询连接所有子实体时,hibernate仍然在其他查询中惰性地加载子实体。 我使用的是Spring数据2 使用本机查询时,我应该如何避免延迟加载或n+1查询 例如: @Query(value = "SELECT recipe.*, r_ing.*, ing.* FROM recipe recipe join " + " on recipe.id = r

我不明白,当我使用JPQL和JOIN fetch时,hibernate应该执行一个查询来连接子实体,但当我想使用本机查询并使用一个查询连接所有子实体时,hibernate仍然在其他查询中惰性地加载子实体。 我使用的是Spring数据2

使用本机查询时,我应该如何避免延迟加载或n+1查询

例如:

@Query(value = "SELECT recipe.*, r_ing.*, ing.* FROM recipe recipe join " +
        " on recipe.id = r.recipe_id " +
        " LEFT JOIN recipe_ingredients r_ing on r.recipe_id = r_ing.recipe_id " +
        " LEFT JOIN ingredient ing on r_ing.ingredient_id = ing.id where ing.names in (:ingredientsNames)",
        countQuery = "SELECT count(*) FROM recipe recipe join " +
                " on recipe.id = r.recipe_id " +
                " LEFT JOIN recipe_ingredients r_ing on r.recipe_id = r_ing.recipe_id " +
                " LEFT JOIN ingredient ing on r_ing.ingredient_id = ing.id where ing.names in (:ingredientsNames)",
        nativeQuery = true
)
Page<Recipe> findAllByIngredientsNames(List<String> ingredientsNames, Pageable page);
实体:

@Entity
public class Recipe {
    @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<RecipeIngredients> ingredients;
}
@Entity
public class RecipeIngredients implements Serializable {

    @EmbeddedId
    private RecipeIngredientsId recipeIngredientsId;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("recipeId")
    private Recipe recipe;

    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @MapsId("ingredientId")
        private Ingredient ingredient;
}

@Entity
public class Ingredient {

    @NaturalId
    @Column(unique = true)
    private String name;
}

对于本机查询,Hibernate不知道如何映射高级数据。在您的例子中,您有一个获取配方实体的请求,实体映射器知道如何从SELECT*from Recipe中提取结果。但Components属性是反向映射,它是作为一个惰性的init集合实现的,后面是查询。这就是JPA和Spring数据为您所做的,但它们不够聪明,无法自动理解并进一步映射它,从而急切地将查询结果映射到集合属性

另外,我想您已经在查询结果中看到了多个相同的配方实体

如果出于任何原因,您确实希望处理本机查询,那么只需正确使用这些查询:本机查询的结果通常不是JPA管理的实体,而是投影

因此,为本机查询中的行创建一个特定的投影:

public class FullRecipeProjection {
    private final Integer recipeId; 
    private final Integer recipeIngredientsId;
    private final Integer ingredientId
    private final Integer ingredientName 

    /* Full-arg-constructor */
    public FullRecipeProjection (Integer recipeId, Integer recipeIngredientsId, Integer ingredientId, String ingredientName) {...}

}
然后,您可以创建查询:

@Query(value = "SELECT new FullRecipeProjection(recipe.recipeId, r_ing.recipeIngredientsId, ing.ingredientId, ing.IngredientName) FROM recipe recipe join " +
        " on recipe.id = r.recipe_id " +
        " LEFT JOIN recipe_ingredients r_ing on r.recipe_id = r_ing.recipe_id " +
        " LEFT JOIN ingredient ing on r_ing.ingredient_id = ing.id where ing.names in (:ingredientsNames)",
        countQuery = "SELECT count(*) FROM recipe recipe join " +
                " on recipe.id = r.recipe_id " +
                " LEFT JOIN recipe_ingredients r_ing on r.recipe_id = r_ing.recipe_id " +
                " LEFT JOIN ingredient ing on r_ing.ingredient_id = ing.id where ing.names in (:ingredientsNames)",
        nativeQuery = true
)
List<FullRecipeProjection> findAllByIngredientsNames(List<String> ingredientsNames);
然后你可以像这样得到你想要的:

final List<FullRecipeProjection> data = repository.findAllByIngredientsNames(ingredientsNames);

final List<FullRecipe> results = data
    .stream()
    // extracting distinct identities of recipes, you have fetched  
    .map(FullRecipeProjection::recipeId)
    .distinct()
    // now we have unique key for the data and can map it
    .map(it -> 
         new FullRecipe(
             it, 
             // extracting all ingredients, which  were fetched in rows with references to recipe.
             data
                 .stream()
                 .filter(o -> o.recipeId.equals(it))
                 .map(ing -> new IngredientProjection(ing.ingredientId, ing.ingredientName))
                 .collect(Collectors.toSet())
    .collect(Collectors.toList()) ; 
相当长的一段路。但这就是它的工作原理。当您使用JPQL查询时,这种长时间的处理是由Hibernate完成的


注意:分页对于这种数据提取来说是一个麻烦的操作:按照您指定的方式,您将分页的不是最终结果,而是FullRecipeProjection,这可能会导致不完整的配方提取,并且可以肯定的是,在分页不好的数据中,它可能只包含1个FullRecipe,这可能无法完全加载

请添加您的控制器/服务代码,如果您使用任何dto进行响应,请同时添加。另外,尽量减少代码来重现您的问题。
final List<FullRecipeProjection> data = repository.findAllByIngredientsNames(ingredientsNames);

final List<FullRecipe> results = data
    .stream()
    // extracting distinct identities of recipes, you have fetched  
    .map(FullRecipeProjection::recipeId)
    .distinct()
    // now we have unique key for the data and can map it
    .map(it -> 
         new FullRecipe(
             it, 
             // extracting all ingredients, which  were fetched in rows with references to recipe.
             data
                 .stream()
                 .filter(o -> o.recipeId.equals(it))
                 .map(ing -> new IngredientProjection(ing.ingredientId, ing.ingredientName))
                 .collect(Collectors.toSet())
    .collect(Collectors.toList()) ;