ChatGPT解决这个技术问题 Extra ChatGPT

JPA 急切未加入

JPA 的 fetch 策略究竟控制什么?我无法发现急切和懒惰之间的任何区别。在这两种情况下,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 个来自 Person,1000 个来自 Address)。我知道这一点是因为我正在查看 MySQL 的查询日志。我的理解是,将地址的获取类型设置为 eager 会导致 JPA/Hibernate 自动使用连接进行查询。但是,无论获取类型如何,它仍然会为关系生成不同的查询。

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

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

我在这里错过了什么吗?我现在必须手动编写每个查询的代码,以便它离开多对一关系。我在 MySQL 中使用 Hibernate 的 JPA 实现。

编辑:看起来(参见 Hibernate FAQ herehereFetchType 不会影响 JPA 查询。所以就我而言,我已经明确告诉它加入。

指向常见问题解答条目的链接已损坏,这里正在运行 one

j
john16384

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

SELECT => 一次查询根实体 + 一次查询相关映射实体/每个根实体的集合 = (n+1) 次查询

SUBSELECT => 一个查询根实体 + 第二个查询相关映射实体/在第一个查询中检索到的所有根实体的集合 = 2 个查询

JOIN => 一个查询来获取根实体及其所有映射实体/集合 = 1 个查询

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

默认情况下,JPA/EclipseLink 和 Hibernate 都使用 SELECT。这可以通过使用覆盖:

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

在休眠中。它还允许使用 @Fetch(FetchMode.SELECT) 显式设置 SELECT 模式,可以通过使用批量大小进行调整,例如 @BatchSize(size=10)

EclipseLink 中对应的注解是:

@JoinFetch
@BatchFetch

为什么存在这些设置?我认为几乎总是必须使用 JOIN。现在我必须用特定于休眠的注释标记所有映射。
有趣但可悲的是@Fetch(FetchMode.JOIN) 对我来说根本不起作用(Hibernate 4.2.15),在 JPQL 和 Criteria 中一样。
使用 Spring JPA,Hibernate 注释似乎对我也不起作用
@Aphax 这可能是因为 Hibernate 对 JPAQL/Criteria 与 em.find() 使用了不同的默认策略。请参阅 vladmihalcea.com/2013/10/17/… 和参考文档。
@vbezhenar(以及稍后阅读他的评论的其他人):JOIN 查询在数据库中生成笛卡尔积,因此您应该确定要计算该笛卡尔积。请注意,如果您使用 fetch 连接,即使您放 LAZY,它也会被急切加载。
r
rudolfson

“MXC”是对的。 fetchType 只是指定何时应该解决关系。

要通过使用外连接来优化急切加载,您必须添加

@Fetch(FetchMode.JOIN)

到你的领域。这是一个特定于休眠的注释。


这对我在 JPQL 或 Criteria 中的 Hibernate 4.2.15 不起作用。
@Aphax 我认为这是因为 JPAQL 和 Criteria 不遵守 Fetch 规范。 Fetch 注释仅适用于 em.find()、AFAIK。请参阅 vladmihalcea.com/2013/10/17/… 另外,请参阅休眠文档。我很确定这在某处被覆盖。
@JoshuaDavis 我的意思是 \@Fetch 注释在查询中没有应用任何类型的 JOIN 优化,无论是 JPQL 还是 em.find(),我刚刚在 Hibernate 5.2.+ 上进行了另一次尝试,它仍然是一样的
T
Tomasz Nurkiewicz

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

如果您设置 fetchType=EAGER 这意味着注释字段与实体中的其他字段同时填充其值。因此,如果您打开实体管理器检索您的人员对象,然后关闭实体管理器,随后执行 person.address 将不会导致引发延迟加载异常。

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

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

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

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


s
sinuhepop

尝试:

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

它对我来说与 JPA2/EclipseLink 类似,但似乎此功能存在于 JPA1 too 中:


u
uı6ʎɹnɯ ꞁəıuɐp

如果您使用 EclipseLink 而不是 Hibernate,您可以通过“查询提示”优化您的查询。请参阅 Eclipse Wiki 中的这篇文章:EclipseLink/Examples/JPA/QueryOptimization

有一章是关于“联读”的。


C
Community

加入你可以做很多事情(使用eclipselink)

在 jpql 你可以做左连接提取

在命名查询中,您可以指定查询提示

在 TypedQuery 你可以说类似 query.setHint("eclipselink.join-fetch", "e.projects.milestones");

还有批量获取提示 query.setHint("eclipselink.batch", "e.address");

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html


G
Gunslinger

除了 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();
    }

}

c
cletus

我想到两件事。

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

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

我这么说是因为您对这应该如何工作的理解从我现在的位置来看基本上是正确的。


这是一个使用多对一的虚构示例。个人地址可能不是最好的例子。我在我的代码中没有看到任何其他急切的 fetch 类型。
然后我的建议是将其简化为一个简单的示例,该示例可以运行并执行您所看到的操作,然后将其发布。您的模型中可能存在导致意外行为的其他复杂情况。
我完全按照上面显示的方式运行了代码,它表现出上述行为。
我从陌生的地方(有时称为城市)听说,多人共享同一个地址(这里有大到足以容纳一个人以上的建筑物),甚至一间公寓里有多个人。 - 我们生活在@cletus 的奇怪世界