与共享主键的OneToOne关系生成n + 1个选择;任何解决方法?

时间:2009-06-16 16:22:13

标签: java hibernate jpa

想象一下关系数据库中的2个表,例如人和计费。在这些实体之间定义了一个(非强制性)OneToOne关联,它们共享Person主键(即PERSON_ID在Person和Billing中定义,并且在后者中是外键)。

通过命名查询(例如:

)对Person进行选择时
from Person p where p.id = :id

Hibernate / JPA生成两个选择查询,一个在Person表上,另一个在Billing表上。

上面的示例非常简单,并且不会导致任何性能问题,因为查询只返回一个结果。现在,假设Person与其他实体(均共享n主键)具有Person OneToOne关系(所有非强制关系)。

如果我错了,请纠正我,但在Person上运行select查询,返回r行,将导致Hibernate生成(n+1)*r个选项,即使这些关联是懒惰

是否存在针对此潜在性能灾难的解决方法(除了根本不使用共享主键)?谢谢你的所有想法。

6 个答案:

答案 0 :(得分:8)

  

想象一下关系数据库中的2个表,例如人和计费。在这些实体之间定义了(非强制性)OneToOne关联,

默认情况下,对于非强制性OneToOne,延迟提取概念是不可能的,Hibernate必须访问数据库才能知道关联是否为null。来自这个旧维基页面的更多细节:

  

Some explanations on lazy loading (one-to-one)

     

[...]

     

现在考虑我们的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必须放一个   适当的值到“cee”   在它加载B的那一刻的财产   数据库。如果启用了代理   C,Hibernate可以放一个C代理   尚未加载的对象,但是   将在有人使用它时加载。   这给了延迟加载   one-to-one

     

但现在想象你的B对象可能或   可能没有关联C   (constrained="false")。什么应该   具体getCee()时会B返回   没有C?空值。但要记住,   Hibernate必须设置正确的值   设定B时的“cee”   (因为它不知道什么时候有人   将致电getCee())。代理没有   在这里帮助因为代理本身   已经是非空对象。

     

所以简历:如果您的B-> C映射   是强制性的(constrained=true),   Hibernate将使用C代理   导致延迟初始化。但   如果你允许B没有C,Hibernate   只是要检查C的存在   它加载的时刻B.但是一个SELECT来   检查存在效率低下   因为相同的SELECT可能不仅仅是   检查存在,但加载整个   宾语。所以延迟加载消失

所以,不可能......默认情况下。

  

是否存在针对此潜在性能灾难的解决方法(除了根本不使用共享主键)?谢谢你的所有想法。

问题不在于共享主键,无论是否有共享主键,您都会得到它,问题是可空 OneToOne。

第一个选项:使用字节码检测(请参阅下面的文档参考)和 no-proxy 提取:

@OneToOne( fetch = FetchType.LAZY )
@org.hibernate.annotations.LazyToOne(org.hibernate.annotations.LazyToOneOption.NO_PROXY)

第二个选项:使用假的ManyToOne(fetch=FetchType.LAZY)。这可能是最简单的解决方案(据我所知,推荐的解决方案)。但我没有用共享的PK测试这个。

第三个选项:使用join fetch预先加载结算。

相关问题

参考

答案 1 :(得分:1)

这是Hibernate的常见性能问题(只搜索“Hibernate n + 1”)。避免n + 1个查询有三种选择:

  • 批量大小
  • 子选择
  • 在您的查询中执行LEFT JOIN

这些内容包含在Hibernate常见问题解答herehere

答案 2 :(得分:0)

您可以尝试“盲猜优化”,这对“n + 1选择问题”有利。 像这样注释你的字段(或getter):

@org.hibernate.annotations.BatchSize(size = 10)
java.util.Set<Billing> bills =  new HashSet<Billing>();

答案 3 :(得分:0)

远离hibernate的OneToOne映射

这是非常破碎和危险的。您是远离数据库损坏问题的一个小错误。

http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128

答案 4 :(得分:0)

如果您将关系指定为延迟或明确指示您希望hibernate运行单独的查询,则只会出现“n + 1”问题。

Hibernate可以通过选择Person上的外部联接来获取与Billing的关系,完全避免了n + 1问题。我认为这是你的hbm文件中的fetch =“XXX”指示。

查看A Short Primer On Fetching Strategies

答案 5 :(得分:0)

将 optional =true 与这样的一对一关系使用以避免 n+1 问题

@OneToOne(fetch = FetchType.LAZY, optional=true)
@PrimaryKeyJoinColumn