当前位置: 首页 > 知识库问答 >
问题:

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

孙承弼
2023-03-14

我不明白,当我使用JPQL与JOIN获取Hibernate应该做一个查询来加入子实体,但是当我想使用本机查询并用一个查询加入所有子实体时,Hibernate仍然懒惰地加载其他查询中的子实体。我正在使用Spring Data 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;
}

共有1个答案

郎健柏
2023-03-14

对于本机查询,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);

然后可以将FullRecipeProjection的集合转换为配方的类似对象:

public class FullRecipe {
    private final Integer recipeId;
    private final Set<IngredientProjection> ingredients;
    public FullRecipe(Integer recipeId, Set<IngredientProjection> ingredients) {...}
}

public class IngredientProjection {
    private final Integer ingredientId;
    private final String ingredientName;
    public IngredientProjection(Integer ingredientId, String ingredientName) {...}
}

然后你可以像这样得到你想要的:

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,可能无法完全加载!)。

 类似资料:
  • 我试着把我的头绕到相对较新的img属性“加载”上。 我知道,如果img具有load=“lazy”属性,那么它会告诉支持该属性的浏览器,在接近视口时可以加载该属性。 那么为什么不总是设置loading=“lazy”?那些立即出现在屏幕上的图像无论如何都会被渲染,因为它们已经在视口中了。因此,在这种情况下,基本上忽略了load=“lazy”。 在这个演示https://mathiasbynens.be

  • 我想在我的repo中写一个本机查询“Select*in from table”。表名与实体名不同。 运行查询时, 1如果我把实体名称返回表未找到。 2如果我将表名放在查询中,则查询的验证失败。 问题是 如果我使用"Select*from TariffPacks r2..., nativeQuery=true",我得到错误TariffPacks不存在。如果我使用"Select*from RECHAR

  • 这组订单应该是惰性加载的,但我得到以下异常:org.springframework.web.util.NestedServletException:请求处理失败;嵌套异常是org.hibernate.lazyInitializationException:未能懒洋洋地初始化Role:...,没有会话或会话被关闭 根本原因:org.hibernate.lazyInitializationExcept

  • 我想知道是否有办法微调JPA/Hibernate以管理以下用例。我有以下对象模型: 然后我执行以下代码: 我想要实现的是: > 根据用例调整Hibernate,即不更改实体类; 使Hibernate执行两个查询: 上面的代码片段与Child-Sunder lazy fetch的基本行为是: (1)执行对实体的一个查询; (2)对实体的所有实体执行一个查询; (3)n个查询,每个实体一个。 通过阅读

  • 我希望有人经历过类似的事情,并能帮助我: 我正在使用graphql java(以及spring、graphql java工具等)和hibernate,我遇到了一个奇怪的问题: 每当我执行查询(或变异)并通过Hibernate加载实体时,它都会自动延迟加载关系。我可以在Hibernates查询日志中看到这一点。 即使我不在查询中加载字段,甚至从模式中删除字段,也会发生这种情况。 例如,给定以下模式:

  • 使用:Hibernate 4 在父子关系中,有一个FetchType。迫不及待地映射,我注意到Hibernate随时都会加载父实体,EntityManager。查找是对一个孩子执行的。这似乎有点浪费。父对象的“幕后”加载会在持久性上下文中产生一个额外的SELECT和额外的对象。此外,它还强制应用程序在删除子应用程序时处理父子关联。 为什么冬眠会这样?这是JPA规范规定的吗?