JPA eager fetch does not join

JPA 的获取策略控制到底是什么?我看不出渴望和懒惰之间有什么区别。在这两种情况下,JPA/Hibernate 不会自动加入多对一关系。

示例: Person 只有一个地址。一个地址可以属于许多人。带 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;
}

If I use the JPA query:

select p from Person p where ...

JPA/Hibernate 生成一个要从 Person 表中选择的 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 的查询日志。我的理解是,将地址的获取类型设置为渴望将导致 JPA/Hibernate 使用连接自动进行查询。但是,无论获取类型如何,它仍然为关系生成不同的查询。

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

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

我错过了什么吗?现在,我必须手工编写每个查询的代码,以便它能够左连接多对一关系。我在 MySQL 中使用 Hibernate 的 JPA 实现。

编辑: 看起来(参见 Hibernate FAQ 给你给你) FetchType并不影响 JPA 查询。所以在我的例子中,我明确地告诉它加入。

134360 次浏览

Two things occur to me.

首先,你确定地址是 ManyToOne 吗?这意味着多个人会有相同的地址。如果是为其中一个人编辑的,就会为所有人编辑。这就是你的目的吗?99% 的时间地址是“私人”的(在这个意义上它们只属于一个人)。

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

我这么说是因为从我的角度来看,你对这个过程的理解基本上是正确的。

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

如果设置 fetchType=EAGER,这意味着注释字段与实体中的其他字段在同一时间用它的值填充。因此,如果您打开一个 entitymanager 检索您的 person 对象,然后关闭 entitymanager,那么随后执行 Person.address 将不会导致抛出延迟加载异常。

如果设置 fetchType=LAZY,则只有在访问该字段时才填充该字段。如果您已经关闭了 entitymanager,那么如果执行 Person.address,将会抛出一个延迟加载异常。要加载字段,您需要使用 em.merge ()将实体放回 entitymanager 上下文中,然后执行字段访问,然后关闭 entitymanager。

在构造具有用于客户订单的集合的客户类时,您可能需要延迟加载。如果您在希望获得客户列表时检索了客户的每个订单,那么当您只查找客户姓名和联系方式时,这可能是一个昂贵的数据库操作。最好把数据库访问留到以后。

对于问题的第二部分——如何让 Hibernate 生成优化的 SQL?

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

“ mxc”是正确的。 fetchType只是指定 什么时候的关系应该得到解决。

要通过使用外部连接来优化即时加载,必须添加

@Fetch(FetchMode.JOIN)

这是一个冬眠特定的注释。

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

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

试试:

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

它与 JPA2/EclipseLink 类似,但在 JPA1也是中似乎有这个特性:

我碰到过这样一个问题,Person 类有一个嵌入的键类。 我自己的解决方案是将它们加入到查询 AND < strong > delete 中

@Fetch(FetchMode.JOIN)

My embedded id class:

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


}

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

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

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

默认情况下,JPA/EclipseLink 和 Hibernate 都使用 SELECT:

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

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

EclipseLink 中相应的注释如下:

@JoinFetch
@BatchFetch

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

  • in jpql you can do 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