JPA 急切未加入
Posted
技术标签:
【中文标题】JPA 急切未加入【英文标题】:JPA eager fetch does not join 【发布时间】:2010-10-02 13:51:28 【问题描述】: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 here 和 here)FetchType
不会影响 JPA 查询。所以就我而言,我已经明确告诉它加入。
【问题讨论】:
FAQ 条目的链接已损坏,这里正在运行one 【参考方案1】:JPA 没有提供任何关于映射注释以选择获取策略的规范。通常,可以通过以下任何一种方式获取相关实体
SELECT => 一次查询根实体 + 一次查询相关映射实体/每个根实体的集合 = (n+1) 次查询 SUBSELECT => 一个查询根实体 + 第二个查询相关映射实体/在第一个查询中检索到的所有根实体的集合 = 2 个查询 JOIN => 一个查询来获取根实体及其所有映射实体/集合 = 1 个查询所以SELECT
和JOIN
是两个极端,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 中一样。 Hibernate 注释似乎对我也不起作用,使用 Spring JPA @Aphax 这可能是因为 Hibernate 对 JPAQL/Criteria 与 em.find() 使用了不同的默认策略。请参阅vladmihalcea.com/2013/10/17/… 和参考文档。 @vbezhenar(以及稍后阅读他的评论的其他人):JOIN 查询在数据库中生成笛卡尔积,因此您应该确定要计算该笛卡尔积。请注意,如果您使用 fetch join,即使您放 LAZY,它也会被预先加载。【参考方案2】:“mxc”是对的。 fetchType
只是指定何时应该解决关系。
要通过使用外部连接来优化急切加载,您必须添加
@Fetch(FetchMode.JOIN)
到你的领域。这是一个特定于休眠的注解。
【讨论】:
这对我来说不适用于 Hibernate 4.2.15,在 JPQL 或 Criteria 中。 @Aphax 我认为这是因为 JPAQL 和 Criteria 不遵守 Fetch 规范。 Fetch 注释仅适用于 em.find()、AFAIK。请参阅vladmihalcea.com/2013/10/17/… 另外,请参阅休眠文档。我很确定这在某处被覆盖。 @JoshuaDavis 我的意思是 \@Fetch 注释在查询中没有应用任何类型的 JOIN 优化,无论是 JPQL 还是 em.find(),我只是在 Hibernate 5.2 上再次尝试.+ 还是一样的【参考方案3】:fetchType 属性控制在获取主实体时是否立即获取带注释的字段。它不一定规定如何构造 fetch 语句,实际的 sql 实现取决于您使用的提供程序 toplink/hibernate 等。
如果您设置fetchType=EAGER
这意味着注释字段与实体中的其他字段同时填充其值。因此,如果您打开实体管理器检索您的人员对象,然后关闭实体管理器,随后执行 person.address 将不会导致引发延迟加载异常。
如果您设置fetchType=LAZY
,则该字段仅在访问时才会填充。如果您已经关闭了实体管理器,那么如果您执行 person.address 将引发延迟加载异常。要加载字段,您需要使用 em.merge() 将实体放回 entitymangers 上下文中,然后进行字段访问,然后关闭 entitymanager。
在构建包含客户订单集合的客户类时,您可能需要延迟加载。如果您在想要获取客户列表时检索了客户的每个订单,那么当您只查找客户姓名和联系方式时,这可能是一项昂贵的数据库操作。最好将数据库访问权限留到以后。
对于问题的第二部分——如何让hibernate生成优化的SQL?
Hibernate 应该允许您提供有关如何构造最有效查询的提示,但我怀疑您的表构造有问题。表中是否建立了关系? Hibernate 可能已经决定一个简单的查询将比一个连接更快,尤其是在缺少索引等的情况下。
【讨论】:
【参考方案4】:尝试:
select p from Person p left join FETCH p.address a where...
它对我来说与 JPA2/EclipseLink 类似,但似乎此功能存在于 JPA1 too 中:
【讨论】:
【参考方案5】:如果您使用 EclipseLink 而不是 Hibernate,您可以通过“查询提示”优化您的查询。请参阅 Eclipse Wiki 中的这篇文章:EclipseLink/Examples/JPA/QueryOptimization。
有一个章节是关于“加入阅读”的。
【讨论】:
【参考方案6】:加入你可以做很多事情(使用eclipselink)
在 jpql 中你可以做 left join fetch
在命名查询中可以指定查询提示
在 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
【讨论】:
【参考方案7】:我确实遇到了这个问题,只是 Person 类有一个嵌入的键类。 我自己的解决方案是将它们加入查询中并 remove
@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();
【讨论】:
【参考方案8】:我想到了两件事。
首先,您确定您的地址是指 ManyToOne 吗?这意味着多个人将拥有相同的地址。如果它是为其中一个编辑的,它将为所有这些编辑。这是你的意图吗? 99% 的时间地址都是“私人的”(从某种意义上说,它们只属于一个人)。
其次,您是否对 Person 实体有任何其他急切的关系?如果我没记错的话,Hibernate 只能处理一个实体上的一个急切关系,但这可能是过时的信息。
我这么说是因为从我现在的位置来看,您对它应该如何工作的理解基本上是正确的。
【讨论】:
这是一个使用多对一的虚构示例。个人地址可能不是最好的例子。我在我的代码中没有看到任何其他急切的 fetch 类型。 我的建议是将其简化为一个简单的示例,该示例可以运行并执行您所看到的操作,然后发布。您的模型中可能存在导致意外行为的其他复杂情况。 我完全按照上面显示的方式运行了代码,它表现出上述行为。 我从陌生的地方(有时称为城市)听说,多人共享同一个地址(这里有大到足以容纳一个人以上的建筑物),甚至一间公寓里有多个人。 - 我们生活在@cletus 的陌生世界以上是关于JPA 急切未加入的主要内容,如果未能解决你的问题,请参考以下文章