JPA渴望获取未加入


112

JPA的提取策略究竟控制什么?我无法发现渴望与懒惰之间的任何区别。在这两种情况下,JPA / Hibernate都不会自动加入多对一关系。

示例:人员有一个地址。一个地址可以属于许多人。JPA注释的实体类如下所示:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

如果我使用JPA查询:

select p from Person p where ...

JPA / Hibernate生成一个SQL查询以从Person表中选择,然后为每个人进行不同的地址查询:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

这对于大型结果集非常不利。如果有1000个人,它将生成1001个查询(“个人”中有1个,“地址”中有1000个)。我知道这是因为我正在查看MySQL的查询日志。据我了解,将地址的获取类型设置为热切会导致JPA / Hibernate自动进行联接查询。但是,无论获取类型如何,它仍会生成不同的关系查询。

只有当我明确告诉它加入时,它才真正加入:

select p, a from Person p left join p.address a where ...

我在这里想念什么吗?现在,我必须手动编写每个查询的代码,以使它离开连接多对一关系。我正在将Hibernate的JPA实现与MySQL配合使用。

编辑:看起来(不会在这里这里看到Hibernate FAQ )FetchType不会影响JPA查询。因此,就我而言,我已明确告诉它要加入。


5
常见问题解答条目的链接已断开,此处仅工作一个
n0weak 2011年

Answers:


99

JPA没有提供有关映射注释以选择提取策略的任何规范。通常,可以以下任何一种方式获取相关实体

  • SELECT =>对根实体的一个查询+对相关映射实体/每个根实体的集合的一个查询=(n + 1)个查询
  • SUBSELECT =>一个查询根实体+第二个查询相关的映射实体/在第一个查询中检索到的所有根实体的集合= 2个查询
  • JOIN =>一个查询以同时获取根实体及其所有映射的实体/集合= 1个查询

所以SELECTJOIN是两个极端SUBSELECT,介于两者之间。可以根据自己的领域模型选择合适的策略。

默认情况下SELECT,JPA / EclipseLink和Hibernate都使用它。可以使用以下方法覆盖:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

在休眠状态。它还允许SELECT显式设置模式@Fetch(FetchMode.SELECT),可以使用批处理大小来调整模式,例如@BatchSize(size=10)

EclipseLink中的相应注释是:

@JoinFetch
@BatchFetch

5
为什么存在这些设置?我认为必须几乎总是使用JOIN。现在,我必须使用休眠特定的注释来标记所有映射。
vbezhenar 2014年

4
有趣但可悲的是@Fetch(FetchMode.JOIN)对我来说根本不起作用(Hibernate 4.2.15),在JPQL中就像在Criteria中一样。
Aphax

3
使用Spring JPA,Hibernate注释似乎也对我不起作用
TheBakker

2
@Aphax这可能是因为Hibernate对JPAQL / Criteria和em.find()使用了不同的默认策略。请参阅vladmihalcea.com/2013/10/17/… 和参考文档。
约书亚·戴维斯

1
@vbezhenar(稍后会再读他的评论):JOIN查询在数据库中生成笛卡尔乘积,因此您应确保要计算笛卡尔乘积。请注意,如果您使用访存联接,即使您放了LAZY,也将立即加载它。
Walfrat

45

“ mxc”是正确的。fetchType仅指定何时应解决该关系。

要通过使用外部联接来优化渴望的加载,您必须添加

@Fetch(FetchMode.JOIN)

到您的领域。这是休眠特定的注释。


6
对于JPQL或Criteria中的Hibernate 4.2.15,这对我不起作用。
Aphax

6
@Aphax我认为这是因为JPAQL和Criteria不遵守Fetch规范。Fetch批注仅适用于em.find()和AFAIK。请参阅vladmihalcea.com/2013/10/17/… 另外,请参阅休眠的docos。我很确定这已经覆盖了某个地方。
约书亚·戴维斯

@JoshuaDavis我的意思是,\ @ Fetch注释没有在查询中应用任何类型的JOIN优化,无论是JPQL还是em.find(),我只是在Hibernate 5.2。+上进行了另一次尝试,而且仍然相同
Aphax

38

fetchType属性控制获取主要实体时是否立即获取带注释的字段。它不一定指示如何构造fetch语句,实际的sql实现取决于您使用的toplink / hibernate等提供程序。

如果设置,fetchType=EAGER则表示带注释的字段将与实体中的其他字段同时填充其值。因此,如果您打开一个entitymanager来检索您的人员对象,然后关闭entitymanager,则随后执行person.address不会导致引发延迟加载异常。

如果设置fetchType=LAZY,则仅在访问该字段时填充该字段。如果您已经关闭了entitymanager,那么如果执行person.address,则将引发延迟加载异常。要加载该字段,您需要使用em.merge()将实体放回entermangers上下文中,然后进行字段访问,然后关闭entitymanager。

在构建带有客户订单集合的客户类时,您可能需要延迟加载。如果要获取客户列表时检索了客户的每个订单,则仅查找客户名称和联系方式时,这可能是一项昂贵的数据库操作。最好将数据库访问权限保留到以后。

对于问题的第二部分-如何进入休眠状态以生成优化的SQL?

Hibernate应该允许您提供有关如何构造最有效查询的提示,但是我怀疑您的表构造有问题。表格中是否建立了关系?Hibernate可能已经决定,简单查询比连接查询要快,尤其是在缺少索引等的情况下。


20

尝试:

select p from Person p left join FETCH p.address a where...

它与JPA2 / EclipseLink类似,对我有用,但似乎此功能也存在于JPA1中




1

除了Person类具有嵌入式键类之外,我确实遇到了这个问题。我自己的解决方案是将它们加入查询并删除

@Fetch(FetchMode.JOIN)

我的嵌入式id类:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}

-1

我发生两件事。

首先,您确定您的意思是ManyToOne吗?这意味着多个人将拥有相同的地址。如果为其中之一进行了编辑,则将为所有它们进行编辑。那是你的意图吗?99%的时间地址是“私有的”(从某种意义上说,它们仅属于一个人)。

其次,您在Person实体上是否还有其他渴望的关系?如果我没记错的话,Hibernate只能处理一个实体上的一个渴望的关系,但这可能是过时的信息。

我之所以这样说,是因为从我所坐的位置上,您对这应该如何工作的理解基本上是正确的。


这是一个使用多对一的组合示例。人员地址可能不是最好的例子。我在代码中没有看到其他渴望获取的类型。
Steve Kuo

然后,我的建议是将其简化为一个简单的示例,该示例可以运行并执行您所看到的,然后将其发布。您的模型中可能还存在其他导致意外行为的并发症。
cletus

1
我完全按照上面显示的方式运行了代码,并且显示了上述行为。
Steve Kuo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.