简体   繁体   English

JPA eager fetch不加入

[英]JPA eager fetch does not join

What exactly does JPA's fetch strategy control? JPA的获取策略到底控制了什么? I can't detect any difference between eager and lazy. 我无法发现渴望和懒惰之间的任何区别。 In both cases JPA/Hibernate does not automatically join many-to-one relationships. 在这两种情况下,JPA / Hibernate都不会自动加入多对一关系。

Example: Person has a single address. 示例:Person有一个地址。 An address can belong to many people. 地址可以属于很多人。 The JPA annotated entity classes look like: 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: 如果我使用JPA查询:

select p from Person p where ...

JPA/Hibernate generates one SQL query to select from Person table, and then a distinct address query for each person: 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

This is very bad for large result sets. 这对于大型结果集非常糟糕。 If there are 1000 people it generates 1001 queries (1 from Person and 1000 distinct from Address). 如果有1000个人,则会生成1001个查询(1个来自Person,1000个来自地址)。 I know this because I'm looking at MySQL's query log. 我知道这是因为我正在查看MySQL的查询日志。 It was my understanding that setting address's fetch type to eager will cause JPA/Hibernate to automatically query with a join. 我的理解是,将地址的提取类型设置为eager会导致JPA / Hibernate自动使用连接进行查询。 However, regardless of the fetch type, it still generates distinct queries for relationships. 但是,无论获取类型如何,它仍会为关系生成不同的查询。

Only when I explicitly tell it to join does it actually join: 只有当我明确告诉它加入时它才真正加入:

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

Am I missing something here? 我在这里错过了什么吗? I now have to hand code every query so that it left joins the many-to-one relationships. 我现在必须手动编写每个查询的代码,以便它离开加入多对一关系。 I'm using Hibernate's JPA implementation with MySQL. 我正在使用Hibernate的JPA实现与MySQL。

Edit: It appears (see Hibernate FAQ here and here ) that FetchType does not impact JPA queries. 编辑:出现(请参阅此处此处的 Hibernate常见问题解答) FetchType不会影响JPA查询。 So in my case I have explicitly tell it to join. 所以在我的情况下,我明确告诉它加入。

JPA doesn't provide any specification on mapping annotations to select fetch strategy. JPA没有提供关于将注释映射到选择获取策略的任何规范。 In general, related entities can be fetched in any one of the ways given below 通常,可以以下面给出的任何一种方式获取相关实体

  • SELECT => one query for root entities + one query for related mapped entity/collection of each root entity = (n+1) queries SELECT =>一个根实体查询+一个查询相关映射实体/每个根实体的集合=(n + 1)个查询
  • SUBSELECT => one query for root entities + second query for related mapped entity/collection of all root entities retrieved in first query = 2 queries SUBSELECT =>根实体的一个查询+第一个查询中检索到的所有根实体的相关映射实体/集合的第二个查询= 2个查询
  • JOIN => one query to fetch both root entities and all of their mapped entity/collection = 1 query JOIN =>一个查询,用于获取两个根实体及其所有映射的实体/集合= 1查询

So SELECT and JOIN are two extremes and SUBSELECT falls in between. 所以SELECTJOIN是两个极端, SUBSELECT介于两者之间。 One can choose suitable strategy based on her/his domain model. 可以根据她/他的领域模型选择合适的策略。

By default SELECT is used by both JPA/EclipseLink and Hibernate. 默认情况下,JPA / EclipseLink和Hibernate都使用SELECT This can be overridden by using: 这可以通过使用来覆盖:

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

in Hibernate. 在Hibernate中。 It also allows to set SELECT mode explicitly using @Fetch(FetchMode.SELECT) which can be tuned by using batch size eg @BatchSize(size=10) . 它还允许使用@Fetch(FetchMode.SELECT)显式设置SELECT模式,可以使用批量大小调整,例如@BatchSize(size=10)

Corresponding annotations in EclipseLink are: EclipseLink中的相应注释是:

@JoinFetch
@BatchFetch

"mxc" is right. “mxc”是对的。 fetchType just specifies when the relation should be resolved. fetchType只指定何时应该解析关系。

To optimize eager loading by using an outer join you have to add 要通过使用外部联接来优化预先加载,您必须添加

@Fetch(FetchMode.JOIN)

to your field. 到你的领域。 This is a hibernate specific annotation. 这是一个特定于hibernate的注释。

The fetchType attribute controls whether the annotated field is fetched immediately when the primary entity is fetched. fetchType属性控制在获取主实体时是否立即获取带注释的字段。 It does not necessarily dictate how the fetch statement is constructed, the actual sql implementation depends on the provider you are using toplink/hibernate etc. 它不一定决定如何构造fetch语句,实际的sql实现依赖于你使用toplink / hibernate等的提供者。

If you set fetchType=EAGER This means that the annotated field is populated with its values at the same time as the other fields in the entity. 如果设置fetchType=EAGER这意味着带注释的字段将使用其值与实体中的其他字段同时填充。 So if you open an entitymanager retrieve your person objects and then close the entitymanager, subsequently doing a person.address will not result in a lazy load exception being thrown. 因此,如果您打开一个实体管理器来检索您的人物对象然后关闭实体管理器,则随后执行person.address将不会导致抛出延迟加载异常。

If you set fetchType=LAZY the field is only populated when it is accessed. 如果设置fetchType=LAZY ,则仅在访问字段时填充该字段。 If you have closed the entitymanager by then a lazy load exception will be thrown if you do a person.address. 如果您已关闭实体管理器,那么如果您执行person.address,则会抛出延迟加载异常。 To load the field you need to put the entity back into an entitymangers context with em.merge(), then do the field access and then close the entitymanager. 要加载字段,您需要使用em.merge()将实体放回到实体管理器上下文中,然后执行字段访问,然后关闭实体管理器。

You might want lazy loading when constructing a customer class with a collection for customer orders. 在构建具有客户订单集合的客户类时,您可能需要延迟加载。 If you retrieved every order for a customer when you wanted to get a customer list this may be a expensive database operation when you only looking for customer name and contact details. 如果您在想要获取客户列表时检索了客户的每个订单,那么当您只查找客户名称和联系详细信息时,这可能是一项昂贵的数据库操作。 Best to leave the db access till later. 最好将数据库访问留到以后。

For the second part of the question - how to get hibernate to generate optimised SQL? 对于问题的第二部分 - 如何让hibernate生成优化的SQL?

Hibernate should allow you to provide hints as to how to construct the most efficient query but I suspect there is something wrong with your table construction. Hibernate应该允许您提供有关如何构建最有效查询的提示,但我怀疑您的表构造有问题。 Is the relationship established in the tables? 表中是否建立了关系? Hibernate may have decided that a simple query will be quicker than a join especially if indexes etc are missing. Hibernate可能已经决定简单的查询比连接更快,特别是如果缺少索引等。

Try with: 试试:

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

It works for me in a similar with JPA2/EclipseLink, but it seems this feature is present in JPA1 too : 它对我来说与JPA2 / EclipseLink类似,但似乎JPA1中也存在此功能:

If you use EclipseLink instead of Hibernate you can optimize your queries by "query hints". 如果您使用EclipseLink而不是Hibernate,则可以通过“查询提示”优化查询。 See this article from the Eclipse Wiki: EclipseLink/Examples/JPA/QueryOptimization . 请参阅Eclipse Wiki中的这篇文章: EclipseLink / Examples / JPA / QueryOptimization

There is a chapter about "Joined Reading". 有一章关于“加入阅读”。

to join you can do multiple things (using eclipselink) 加入你可以做多件事(使用eclipselink)

  • in jpql you can do left join fetch 在jpql中你可以做左连接提取

  • in named query you can specify query hint 在命名查询中,您可以指定查询提示

  • in TypedQuery you can say something like 在TypedQuery中你可以说类似的东西

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • there is also batch fetch hint 还有批量提取提示

    query.setHint("eclipselink.batch", "e.address");

see 看到

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

I had exactly this problem with the exception that the Person class had a embedded key class. 我确实遇到了这个问题,除了Person类有一个嵌入式密钥类。 My own solution was to join them in the query AND remove 我自己的解决方案是将它们加入查询并删除

@Fetch(FetchMode.JOIN)

My embedded id class: 我的嵌入式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();
    }

}

Two things occur to me. 我发生了两件事。

First, are you sure you mean ManyToOne for address? 首先,你确定你的意思是ManyToOne的地址吗? That means multiple people will have the same address. 这意味着多人将拥有相同的地址。 If it's edited for one of them, it'll be edited for all of them. 如果为其中一个编辑了它,它将被编辑为所有这些。 Is that your intent? 这是你的意图吗? 99% of the time addresses are "private" (in the sense that they belong to only one person). 99%的时间地址是“私人的”(从某种意义上说,它们只属于一个人)。

Secondly, do you have any other eager relationships on the Person entity? 其次,你对Person实体有任何其他渴望的关系吗? If I recall correctly, Hibernate can only handle one eager relationship on an entity but that is possibly outdated information. 如果我没记错的话,Hibernate只能在一个实体上处理一个急切的关系,但这可能是过时的信息。

I say that because your understanding of how this should work is essentially correct from where I'm sitting. 我之所以这么说是因为你对我的工作方式的理解基本上是正确的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM