连接返回许多部分重复项的高效 Hibernate 标准
Efficient Hibernate criteria for join returning many partial duplicates
我正在获取一长串实体,这些实体指的是其他实体,这些实体指的是......最后,通常所有实体都将单个 user
作为它们的 owner
.并不奇怪,因为查询的是属于单个 user
的实体。多行重复的部分较多;实际上,只有一小部分是独特的数据。由于查询似乎很慢,我虽然可以通过使用
单独获取东西来获得一些好处
criteria.setFetchMode(path, FetchMode.SELECT);
这在我的上述情况下有效,但是当查询许多用户(作为管理员)时,它变得很糟糕,因为休眠会针对 every user
发出单独的查询,而不是像
SELECT * FROM User WHERE id IN (?, ?, ..., ?)
或根本不获取它们(这比每个实体一个查询更糟糕)。我想知道我错过了什么?
因此,我 运行 进入 1+N 问题,而不是获取大量冗余数据,显然 1+1 查询就可以了。
- 有没有办法指示 Hibernate 使用正确的查询?
- 有没有一种方法可以通过在标准本身中指定它来防止 Hibernate 获取所有者(而不是将
fetch=FetchType.LAZY
放在字段上;惰性应该是特定于查询的)?
我认为这不重要,但我的类就像
class Child {
@ManyToOne Father father;
@ManyToOne Mother mother;
...
}
class Father {
@ManyToOne User owner;
...
}
class Mother {
@ManyToOne User owner;
...
}
查询就像
createCriteria(Child.class)
.add(Restrictions.in("id", idList))
.add(Restrictions.eq("isDeleted", false))
.createAlias("Father", "f")
.add(Restrictions.eq("f.isDeleted", false))
.setFetchMode("f.owner", FetchMode.SELECT)
.createAlias("Mother", "m")
.add(Restrictions.eq("m.isDeleted", false))
.setFetchMode("m.owner", FetchMode.SELECT)
.list();
重要的部分是 owner
不会被使用并且可以被代理。 FetchMode.SELECT
的 javadoc 说
Fetch eagerly, using a separate select
所以它基本上承诺我想要的 1+1 查询而不是 "using a separate select per entity"。
我写了一个小项目来演示这个行为。根据您的条件生成的 SQL 如下:
select
this_.id as id1_0_4_,
this_.father_id as father_i3_0_4_,
this_.isDeleted as isDelete2_0_4_,
this_.mother_id as mother_i4_0_4_,
f1_.id as id1_1_0_,
f1_.isDeleted as isDelete2_1_0_,
f1_.owner_id as owner_id3_1_0_,
user5_.id as id1_3_1_,
user5_.isDeleted as isDelete2_3_1_,
m2_.id as id1_2_2_,
m2_.isDeleted as isDelete2_2_2_,
m2_.owner_id as owner_id3_2_2_,
user7_.id as id1_3_3_,
user7_.isDeleted as isDelete2_3_3_
from
Child this_
inner join
Father f1_
on this_.father_id=f1_.id
left outer join
User user5_
on f1_.owner_id=user5_.id
inner join
Mother m2_
on this_.mother_id=m2_.id
left outer join
User user7_
on m2_.owner_id=user7_.id
where
this_.id in (
?, ?
)
and this_.isDeleted=?
and f1_.isDeleted=?
and m2_.isDeleted=?
- 更改条件中的 FetchMode API 不会影响查询。仍然查询所有者数据。
- Id 在 "in" 子句中,Hibernate 没有为每个 Id 发出单独的查询。
如其他答案中所述,如果实体关系设置为EAGER
,即JPA 默认值,则无法在Criteria API 中更改获取模式。 fetch mode需要改成LAZY
.
可以看到here
Fetch profiles are meant to help you achieve what you want, but are very limited at the time being and you can override the default fetch plan/strategy only with the join-style fetch profiles (you can make a lazy association eager, but not vice versa). However, you could use this trick 反转该行为:默认设置惰性关联并默认为所有 sessions/transactions 启用配置文件。然后禁用您想要延迟加载的事务中的配置文件。
恕我直言,上面的解决方案看起来太麻烦了,我在大多数用例中使用的方法来避免加载冗余数据和 N+1 选择问题是使关联延迟并定义 。
总结一下我的挫败感……Hibernate 在这方面充满了惊喜(错误?):
- 除非 属性 是用
@ManyToOne(fetch=FetchType.LAZY)
声明的,否则您无法更改任何内容
- 默认是
FetchType.EAGER
,这是愚蠢的,因为它不能被覆盖
- 使用
criteria.setFetchMode(path, FetchMode.SELECT)
毫无意义 因为它始终是空操作(由于 属性 或属性 已经很懒了)!
- 延迟获取默认会导致 1+N 问题
- 它可以通过 class 级
@BatchSize
注释进行控制
- 在标量字段上放置
@BatchSize
注释会被静默忽略
为了得到我想要的(两个 SQL 查询),我只需要两件事:
- 用
@ManyToOne(fetch=FetchType.LAZY)
声明 属性
- 将
@BatchSize(size=aLot)
放在 属性 的 class 上
这很简单,但有点难以找出(因为上面所有被忽略的事情)。我还没有研究获取配置文件。
unless the property is declared with
@ManyToOne(fetch=FetchType.LAZY)
, you can't change anything
是的,至少目前是这样,直到扩展获取配置文件功能以提供将预加载更改为延迟加载的能力。
the default is FetchType.EAGER
, which is stupid, as it can't be
overridden
是的,我同意这很糟糕,但是在 Hibernate native API 默认情况下一切都是惰性的;除非另有明确说明,否则 JPA 要求一对一关联是热切的。
using criteria.setFetchMode(path, FetchMode.SELECT) is pointless as
it's always a no-op (either it gets ignored because of the
non-overridable eagerness of the property or the property is lazy
already)!
有了它,您应该能够覆盖其他延迟获取模式。请参阅来自一位主要 Hibernate 贡献者的关于 javadoc 混淆的 HHH-980 and this comment。
fetching lazily leads by default to the 1+N problem
它与延迟加载无关,如果您不在同一查询中获取预加载关联,它也是预加载的默认设置。
it can be controlled via a class-level @BatchSize
annotation
您必须将其放置在 class 层级才能使其对与该实体的一对一关联生效; this answer 很有帮助。对于集合关联(与在其他实体中定义的该实体的对多关联),您可以灵活地为每个关联单独定义它。
我正在获取一长串实体,这些实体指的是其他实体,这些实体指的是......最后,通常所有实体都将单个 user
作为它们的 owner
.并不奇怪,因为查询的是属于单个 user
的实体。多行重复的部分较多;实际上,只有一小部分是独特的数据。由于查询似乎很慢,我虽然可以通过使用
criteria.setFetchMode(path, FetchMode.SELECT);
这在我的上述情况下有效,但是当查询许多用户(作为管理员)时,它变得很糟糕,因为休眠会针对 every user
发出单独的查询,而不是像
SELECT * FROM User WHERE id IN (?, ?, ..., ?)
或根本不获取它们(这比每个实体一个查询更糟糕)。我想知道我错过了什么?
因此,我 运行 进入 1+N 问题,而不是获取大量冗余数据,显然 1+1 查询就可以了。
- 有没有办法指示 Hibernate 使用正确的查询?
- 有没有一种方法可以通过在标准本身中指定它来防止 Hibernate 获取所有者(而不是将
fetch=FetchType.LAZY
放在字段上;惰性应该是特定于查询的)?
我认为这不重要,但我的类就像
class Child {
@ManyToOne Father father;
@ManyToOne Mother mother;
...
}
class Father {
@ManyToOne User owner;
...
}
class Mother {
@ManyToOne User owner;
...
}
查询就像
createCriteria(Child.class)
.add(Restrictions.in("id", idList))
.add(Restrictions.eq("isDeleted", false))
.createAlias("Father", "f")
.add(Restrictions.eq("f.isDeleted", false))
.setFetchMode("f.owner", FetchMode.SELECT)
.createAlias("Mother", "m")
.add(Restrictions.eq("m.isDeleted", false))
.setFetchMode("m.owner", FetchMode.SELECT)
.list();
重要的部分是 owner
不会被使用并且可以被代理。 FetchMode.SELECT
的 javadoc 说
Fetch eagerly, using a separate select
所以它基本上承诺我想要的 1+1 查询而不是 "using a separate select per entity"。
我写了一个小项目来演示这个行为。根据您的条件生成的 SQL 如下:
select
this_.id as id1_0_4_,
this_.father_id as father_i3_0_4_,
this_.isDeleted as isDelete2_0_4_,
this_.mother_id as mother_i4_0_4_,
f1_.id as id1_1_0_,
f1_.isDeleted as isDelete2_1_0_,
f1_.owner_id as owner_id3_1_0_,
user5_.id as id1_3_1_,
user5_.isDeleted as isDelete2_3_1_,
m2_.id as id1_2_2_,
m2_.isDeleted as isDelete2_2_2_,
m2_.owner_id as owner_id3_2_2_,
user7_.id as id1_3_3_,
user7_.isDeleted as isDelete2_3_3_
from
Child this_
inner join
Father f1_
on this_.father_id=f1_.id
left outer join
User user5_
on f1_.owner_id=user5_.id
inner join
Mother m2_
on this_.mother_id=m2_.id
left outer join
User user7_
on m2_.owner_id=user7_.id
where
this_.id in (
?, ?
)
and this_.isDeleted=?
and f1_.isDeleted=?
and m2_.isDeleted=?
- 更改条件中的 FetchMode API 不会影响查询。仍然查询所有者数据。
- Id 在 "in" 子句中,Hibernate 没有为每个 Id 发出单独的查询。
如其他答案中所述,如果实体关系设置为EAGER
,即JPA 默认值,则无法在Criteria API 中更改获取模式。 fetch mode需要改成LAZY
.
可以看到here
Fetch profiles are meant to help you achieve what you want, but are very limited at the time being and you can override the default fetch plan/strategy only with the join-style fetch profiles (you can make a lazy association eager, but not vice versa). However, you could use this trick 反转该行为:默认设置惰性关联并默认为所有 sessions/transactions 启用配置文件。然后禁用您想要延迟加载的事务中的配置文件。
恕我直言,上面的解决方案看起来太麻烦了,我在大多数用例中使用的方法来避免加载冗余数据和 N+1 选择问题是使关联延迟并定义
总结一下我的挫败感……Hibernate 在这方面充满了惊喜(错误?):
- 除非 属性 是用
@ManyToOne(fetch=FetchType.LAZY)
声明的,否则您无法更改任何内容 - 默认是
FetchType.EAGER
,这是愚蠢的,因为它不能被覆盖 - 使用
criteria.setFetchMode(path, FetchMode.SELECT)
毫无意义 因为它始终是空操作(由于 属性 或属性 已经很懒了)! - 延迟获取默认会导致 1+N 问题
- 它可以通过 class 级
@BatchSize
注释进行控制 - 在标量字段上放置
@BatchSize
注释会被静默忽略
为了得到我想要的(两个 SQL 查询),我只需要两件事:
- 用
@ManyToOne(fetch=FetchType.LAZY)
声明 属性
- 将
@BatchSize(size=aLot)
放在 属性 的 class 上
这很简单,但有点难以找出(因为上面所有被忽略的事情)。我还没有研究获取配置文件。
unless the property is declared with
@ManyToOne(fetch=FetchType.LAZY)
, you can't change anything
是的,至少目前是这样,直到扩展获取配置文件功能以提供将预加载更改为延迟加载的能力。
the default is
FetchType.EAGER
, which is stupid, as it can't be overridden
是的,我同意这很糟糕,但是在 Hibernate native API 默认情况下一切都是惰性的;除非另有明确说明,否则 JPA 要求一对一关联是热切的。
using criteria.setFetchMode(path, FetchMode.SELECT) is pointless as it's always a no-op (either it gets ignored because of the non-overridable eagerness of the property or the property is lazy already)!
有了它,您应该能够覆盖其他延迟获取模式。请参阅来自一位主要 Hibernate 贡献者的关于 javadoc 混淆的 HHH-980 and this comment。
fetching lazily leads by default to the 1+N problem
它与延迟加载无关,如果您不在同一查询中获取预加载关联,它也是预加载的默认设置。
it can be controlled via a class-level
@BatchSize
annotation
您必须将其放置在 class 层级才能使其对与该实体的一对一关联生效; this answer 很有帮助。对于集合关联(与在其他实体中定义的该实体的对多关联),您可以灵活地为每个关联单独定义它。