与共享主键的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对象可能有也可能没有关联的Cconstrained="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”指示。

查看关于获取策略的简短入门