JPQL查询导致多次往返数据库,即使是EAGER和JOIN FETCH。

huangapple go评论59阅读模式
英文:

JPQL Query results in multiple roundtrips to database even EAGER and JOIN FETCH

问题

我正试图理解为什么我的查询会导致对数据库的两次调用。据我理解,查询中使用了 FETCH 关键字的 EAGER 加载应该只产生一次往返,但在下面的情况下并非如此... 感谢任何提示!

查询代码如下:

TypedQuery<Recipe> query = em.createQuery("SELECT r FROM Recipe r" +
    "  LEFT JOIN FETCH r.ingredients ri LEFT JOIN FETCH r.author a WHERE r.id= :id ", Recipe.class);

query.setParameter("id", id);

Recipe 类:

@Entity
@Table(name = "recipes")
@Getter
@Setter
@NoArgsConstructor
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id")
@JsonSerialize(using = RecipeMetaSerializer.class)
public class Recipe implements Serializable {

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

    @ManyToOne(fetch = FetchType.EAGER)
    private User author;

    @OneToMany(
        mappedBy = "recipe",
        orphanRemoval = true,
        fetch = FetchType.LAZY,
        cascade = CascadeType.PERSIST
    )
    private List<RecipeIngredient> ingredients;
}

第一个关联表 RecipeIngredient:

@Entity
@Table(name="recipe_ingredients")
@Getter
@Setter
@NoArgsConstructor
@IdClass(RecipeIngredientId.class)
public class RecipeIngredient implements Serializable {

    @Id
    @ManyToOne(fetch= FetchType.EAGER)
    private Recipe recipe;

    @Id
    @ManyToOne(fetch= FetchType.LAZY)
    private Ingredient ingredient;
    ...
}

第二个关联表:

@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
public class User {

    @Id
    private Long id;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
    private List<Recipe> recipes;
}

JPQL 查询导致以下两次对数据库的调用,都包含了对表 users 的左外连接:

select recipe0_.id as id1_6_0_, ingredient1_.ingredient_id as ingredie4_5_0__, user2_.img_url as img_url2_7_2_, user2_.username as username4_7_2_ from recipes recipe0_ **left outer join recipe_ingredients** ingredient1_ on recipe0_.id=ingredient1_.recipe_id **left outer join users** user2_ on recipe0_.author_id=user2_.id where recipe0_.id=?

select recipe0_.id as id1_6_0_, user1_.username as username4_7_1_ from recipes recipe0_ **left outer join users** user1_ on recipe0_.author_id=user1_.id where recipe0_.id=?

我本希望只有一次对 Users 表的连接,而不是两次.. 有什么想法吗?谢谢!

英文:

Im trying to understand why my query results in 2 calls to database. In my understanding I have an EAGER loading with FETCH keyword in the query that should result in one roundtrip, but in below this isnt the case .. Grateful for any tips!


		TypedQuery&lt;Recipe&gt; query = em.createQuery(&quot;SELECT r FROM Recipe r&quot; +
				&quot;  LEFT JOIN FETCH r.ingredients ri LEFT JOIN FETCH r.author a WHERE r.id= :id &quot;, Recipe.class);
		
		query.setParameter(&quot;id&quot;, id);

The Recipe Class:

@Entity
@Table(name = &quot;recipes&quot;)
@Getter
@Setter
@NoArgsConstructor
@JsonIdentityInfo(
		generator = ObjectIdGenerators.PropertyGenerator.class,
		property = &quot;id&quot;)
@JsonSerialize(using = RecipeMetaSerializer.class)
public class Recipe implements Serializable {

	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Id
	private Long id;
	
	
	@ManyToOne(fetch = FetchType.EAGER)
	private User author;


	@OneToMany(
			mappedBy = &quot;recipe&quot;,
			orphanRemoval = true,
			fetch = FetchType.LAZY,
			cascade = CascadeType.PERSIST
	)
	private List&lt;RecipeIngredient&gt; ingredients;
}

First Join table RecipeIngredient:

@Entity
@Table(name=&quot;recipe_ingredients&quot;)
@Getter
@Setter
@NoArgsConstructor
@IdClass(RecipeIngredientId.class)
public class RecipeIngredient implements Serializable {
	
	@Id
	@ManyToOne(fetch= FetchType.EAGER)
	private Recipe recipe;

	@Id
	@ManyToOne(fetch= FetchType.LAZY)
	private Ingredient ingredient;
.....
}

Second join table:

@Entity
@Table(name = &quot;users&quot;)
@Getter
@Setter
@NoArgsConstructor
public class User {
	
	@Id
	private Long id;
	
	@OneToMany(fetch = FetchType.LAZY, mappedBy = &quot;author&quot;)
	private List&lt;Recipe&gt; recipes;
	
}

The JPQL query results in following two calls to DB both containing left outer join to table users:

select recipe0_.id as id1_6_0_, ingredient1_.ingredient_id as ingredie4_5_0__, user2_.img_url as img_url2_7_2_, user2_.username as username4_7_2_ from recipes recipe0_ **left outer join recipe_ingredients** ingredient1_ on recipe0_.id=ingredient1_.recipe_id **left outer join users** user2_ on recipe0_.author_id=user2_.id where recipe0_.id=?

select recipe0_.id as id1_6_0_, user1_.username as username4_7_1_ from recipes recipe0_ **left outer join users** user1_ on recipe0_.author_id=user1_.id where recipe0_.id=?

I was hoping for a join to Users table once, not twice.. Any ideas? Thanks!

答案1

得分: 1

看起来第二个查询是针对这里的 Recipe recipe

@Entity
public class RecipeIngredient {
    
    @Id
    @ManyToOne(fetch= FetchType.EAGER)
    private Recipe recipe;
 
}

只需使用 FetchType.LAZY

@Entity
public class RecipeIngredient {
    
    @Id
    @ManyToOne(fetch= FetchType.LAZY)
    private Recipe recipe;
 
}

如果您使用 entityManager.find() 方法,就不会有第二个查询。recipe 已经存在于缓存中。

但是对于 JPQL,Hibernate 认为在第一个查询中找到的 recipe 不在缓存中,因此会再次获取它(即使它是相同的 recipe)。

建议

无论何时都要使用惰性加载。在运行时无法禁用急切加载。而且,如果您想将 eager 更改为 lazy,测试所有内容将非常困难。

https://vladmihalcea.com/eager-fetching-is-a-code-smell

英文:

Looks like the second query is for Recipe recipe here

@Entity
public class RecipeIngredient {
    
    @Id
    @ManyToOne(fetch= FetchType.EAGER)
    private Recipe recipe;
 
}

Just use FetchType.LAZY

@Entity
public class RecipeIngredient {
    
    @Id
    @ManyToOne(fetch= FetchType.LAZY)
    private Recipe recipe;
 
}

You would not have the second query, if you use entityManager.find() method. The recipe will be already in the cache.

But for JPQL Hibernate considers that recipe, that is found in the first query, is not in the cache, so it gets it again (even it is the same recipe).

Advice

Always use lazy loading everywhere. It is impossible to disable eager loading in runtime. Also, it will be very hard to test everything, if you will want to change eager to lazy.

https://vladmihalcea.com/eager-fetching-is-a-code-smell

huangapple
  • 本文由 发表于 2020年9月2日 04:30:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/63695037.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定