gpt4 book ai didi

hibernate - 为什么在 native 查询 Hibernate 延迟加载的子实体中?

转载 作者:行者123 更新时间:2023-12-03 23:46:54 26 4
gpt4 key购买 nike

我不明白,当我使用 JPQL 和 JOIN fetch hibernate 应该做一个查询来加入子实体,但是当我想使用 native 查询并用一个查询加入所有 child 时, hibernate ​​仍然懒惰地加载其他查询中的 child 。
我正在使用 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;
}

最佳答案

对于原生查询,Hibernate 不知道如何映射高级数据。在您的情况下,您有一个请求来获取 Recipe实体,实体映射器知道如何从 SELECT * FROM recipe 中提取结果.但是ingredients属性是反向映射,它被实现为一个惰性初始化集合,后面有查询。这就是 JPA 和 Spring 数据为您所做的,但它们不够聪明,无法自动理解并进一步映射以急切地将查询结果映射到集合属性。

另外,我猜您已经在查询结果中看到了多个相同的 Recipe实体。

如果出于某种原因您确实想处理 native 查询,那么请正确使用它们: native 查询的结果通常不是 JPA 托管实体,而是投影。

因此,创建您在 native 查询中所拥有的行的特定投影:

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的集合了到您 Recipe 的类似对象:
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 ,这可能会导致不完整的 Recipe fetch ,并且肯定是在糟糕的分页数据中(它可能只包含 1 FullRecipe ,它可能无法完全加载!)。

关于hibernate - 为什么在 native 查询 Hibernate 延迟加载的子实体中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62147349/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com