想象一下关系数据库中的 2 个表,例如人员和计费。这些实体之间定义了一个(非强制的)OneToOne 关联,它们共享 Person 主键(即 PERSON_ID 在 Person 和 Billing 中定义,在后者中是外键)。
当通过命名查询对 Person 进行选择时,例如:
from Person p where p.id = :id
Hibernate/JPA 生成两个选择查询,一个在 Person 表上,另一个在 Billing 表上。
上面的示例非常简单,不会导致任何性能问题,因为查询只返回一个结果。现在,想象一下 Person
有n
与其他实体(所有共享 Person
主键)的 OneToOne 关系(所有非强制)。
如果我错了请纠正我,但运行 select
查询 Person,返回 r
行,将导致 (n+1)*r
选择由 Hibernate 生成,即使关联是惰性。
对于这种潜在的性能灾难是否有解决方法(除了根本不使用共享主键之外)?感谢您的所有想法。
最佳答案
Imagine 2 tables in a relational database, e.g. Person and Billing. There is a (non-mandatory) OneToOne association defined between these entities,
默认情况下,对于非强制性的 OneToOne,延迟获取在概念上是不可能的,Hibernate 必须访问数据库才能知道关联是否为 null
。这个旧 wiki 页面的更多详细信息:
Some explanations on lazy loading (one-to-one)
[...]
Now consider our class B has one-to-one association to C
class B { private C cee; public C getCee() { return cee; } public void setCee(C cee) { this.cee = cee; } } class C { // Not important really }
Right after loading B, you may call
getCee()
to obtain C. But look,getCee()
is a method of YOUR class and Hibernate has no control over it. Hibernate does not know when someone is going to callgetCee()
. That means Hibernate must put an appropriate value into "cee
" property at the moment it loads B from database. If proxy is enabled forC
, Hibernate can put a C-proxy object which is not loaded yet, but will be loaded when someone uses it. This gives lazy loading forone-to-one
.But now imagine your
B
object may or may not have associatedC
(constrained="false"
). What shouldgetCee()
return when specificB
does not haveC
? Null. But remember, Hibernate must set correct value of "cee" at the moment it setB
(because it does no know when someone will callgetCee()
). Proxy does not help here because proxy itself in already non-null object.So the resume: if your B->C mapping is mandatory (
constrained=true
), Hibernate will use proxy for C resulting in lazy initialization. But if you allow B without C, Hibernate just HAS TO check presence of C at the moment it loads B. But a SELECT to check presence is just inefficient because the same SELECT may not just check presence, but load entire object. So lazy loading goes away.
所以,不可能……默认情况下。
Is there a workaround for this potential performance disaster (other than not using a shared primary key at all)? Thank you for all your ideas.
问题不是共享主键,有或没有共享主键,你会明白的,问题是nullable OneToOne。
第一个选项:使用字节码检测(参见下面的文档引用)和无代理抓取:
@OneToOne( fetch = FetchType.LAZY )
@org.hibernate.annotations.LazyToOne(org.hibernate.annotations.LazyToOneOption.NO_PROXY)
第二个选项:使用伪造的ManyToOne(fetch=FetchType.LAZY)
。这可能是最简单的解决方案(据我所知,这是推荐的解决方案)。但是我没有使用共享 PK 对此进行测试。
第三个选项:使用join fetch
急切加载账单。
相关问题
引用资料
- Hibernate 引用指南
- 旧版 Hibernate 常见问题解答
- hibernate 维基
关于java - 具有共享主键的 OneToOne 关系生成 n+1 个选择;任何解决方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1002547/