与共享主键的OneToOne关系生成n + 1个选择; 任何解决方法?
想象一下关系数据库中的2个表,例如Person和Billing。 在这些实体之间定义了(非强制性)OneToOne关联,并且它们共享Person主键(即PERSON_ID在Person和Billing中定义,并且它是后者中的外键)。
通过命名查询对Person进行选择时,例如:
from Person p where p.id = :id
Hibernate / JPA生成两个选择查询,一个在Person表上,另一个在Billing表上。
上面的示例非常简单,并且不会导致任何性能问题,因为查询只返回一个结果。 现在,假设Person
与其他实体(所有共享Person
主键)具有n
OneToOne关系(所有非强制关系)。
如果我错了,请纠正我,但在Person上运行select
查询,返回r
行,将导致(n+1)*r
选择由Hibernate生成,即使关联是懒惰的 。
是否存在针对此潜在性能灾难的解决方法(除了根本不使用共享主键)? 谢谢你的所有想法。
想象一下关系数据库中的2个表,例如Person和Billing。 这些实体之间定义了(非强制性)OneToOne关联,
对于非强制性OneToOne,默认情况下,懒惰提取在概念上是不可能的,Hibernate必须访问数据库才能知道关联是否为null
。 来自这个旧维基页面的更多细节:
关于延迟加载的一些解释(一对一)
[…]
现在考虑我们的B类与C有一对一的关联
class B { private C cee; public C getCee() { return cee; } public void setCee(C cee) { this.cee = cee; } } class C { // Not important really }
在加载B之后,你可以调用
getCee()
来获得C.但是看看,getCee()
是你的类的一个方法,而Hibernate无法控制它。 Hibernate不知道有人打算调用getCee()
。 这意味着Hibernate必须在从数据库加载B时将适当的值放入“cee
”属性。 如果为C
启用了代理,Hibernate可以放置一个尚未加载的C代理对象,但会在有人使用它时加载。 这为one-to-one
提供了延迟加载。但现在想象你的
B
对象可能有也可能没有关联的C
(constrained="false"
)。 当特定B
没有C
时,getCee()
应该返回什么? 空值。 但请记住,Hibernate必须在设置B
设置正确的“cee”值(因为它不知道有人会调用getCee()
)。 代理在这里没有帮助,因为代理本身已经是非null对象。所以简历: 如果你的B-> C映射是强制性的(
constrained=true
),Hibernate将使用C代理导致延迟初始化。 但是如果你允许B没有C,那么Hibernate只是在它加载B时检查C的存在。但是检查存在的SELECT效率很低,因为相同的SELECT可能不只是检查存在,而是加载整个对象。 懒惰的装载消失了 。
所以,不可能……默认情况下。
是否存在针对此潜在性能灾难的解决方法(除了根本不使用共享主键)? 谢谢你的所有想法。
问题不是共享主键,无论是否有共享主键,你都会得到它,问题是可以为空的 OneToOne。
第一个选项 :使用字节码检测(参见下面的文档参考)和无代理提取:
@OneToOne( fetch = FetchType.LAZY ) @org.hibernate.annotations.LazyToOne(org.hibernate.annotations.LazyToOneOption.NO_PROXY)
第二个选项 :使用假的ManyToOne(fetch=FetchType.LAZY)
。 这可能是最简单的解决方案(据我所知,推荐的解决方案)。 但我没有用共享的PK测试这个。
第三个选项 :使用join fetch
预先加载结算。
相关问题
- 使OneToOne关系变得懒散
参考
- Hibernate参考指南
- 19.1.3。 单端关联代理
- 19.1.7。 使用延迟属性获取
- 旧的Hibernate FAQ
- 如何建立一对一的懒惰关系?
- Hibernate Wiki
- 关于延迟加载的一些解释(一对一)
这是Hibernate常见的性能问题(只搜索“Hibernate n + 1”)。 避免n + 1个查询有三个选项:
- 批量大小
- 子选择
- 在查询中执行LEFT JOIN
这些和这里都包含在Hibernate常见问题解答中
您可以尝试“盲猜优化”,这对“n + 1选择问题”很有用。 像这样注释你的字段(或getter):
@org.hibernate.annotations.BatchSize(size = 10) java.util.Set bills = new HashSet ();
远离hibernate的OneToOne映射
它非常破碎和危险。 您是远离数据库损坏问题的一个小错误。
http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128
仅当您将关系指定为延迟或明确指示希望hibernate运行单独的查询时,才会出现“n + 1”问题。
Hibernate可以通过选择Person上的外部联接来获取与Billing的关系,完全避免了n + 1问题。 我认为这是你的hbm文件中的fetch =“XXX”指示。
查看关于获取策略的简短入门