ChatGPT解决这个技术问题 Extra ChatGPT

JPA eager fetch does not join

What exactly does JPA's fetch strategy control? I can't detect any difference between eager and lazy. In both cases JPA/Hibernate does not automatically join many-to-one relationships.

Example: Person has a single address. An address can belong to many people. The JPA annotated entity classes look like:

@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 generates one SQL query to select from Person table, and then a distinct address query for each 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). I know this because I'm looking at MySQL's query log. It was my understanding that setting address's fetch type to eager will cause JPA/Hibernate to automatically query with a join. 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.

Edit: It appears (see Hibernate FAQ here and here) that FetchType does not impact JPA queries. So in my case I have explicitly tell it to join.

Links to FAQ entries are broken, here is working one

j
john16384

JPA doesn't provide any specification on mapping annotations to select fetch strategy. 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

SUBSELECT => one query for root entities + second query for related mapped entity/collection of all root entities retrieved in first query = 2 queries

JOIN => one query to fetch both root entities and all of their mapped entity/collection = 1 query

So SELECT and JOIN are two extremes and SUBSELECT falls in between. One can choose suitable strategy based on her/his domain model.

By default SELECT is used by both JPA/EclipseLink and Hibernate. This can be overridden by using:

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

in Hibernate. It also allows to set SELECT mode explicitly using @Fetch(FetchMode.SELECT) which can be tuned by using batch size e.g. @BatchSize(size=10).

Corresponding annotations in EclipseLink are:

@JoinFetch
@BatchFetch

Why does those settings exist? I think that JOIN must be used almost always. Now I have to mark all mappings with hibernate-specific annotations.
Interesting but sadly @Fetch(FetchMode.JOIN) doesn't work at all for me (Hibernate 4.2.15), in JPQL as in Criteria.
The Hibernate annotation do not seems to work at all for me either, using Spring JPA
@Aphax This might be because Hibernate uses different default strategies for JPAQL/Criteria vs em.find(). See vladmihalcea.com/2013/10/17/… and the reference documentation.
@vbezhenar (and othes reading his comment some time later) : JOIN query generate cartesian product in database so you should be sure that you want that cartesian product to be computed. Note that if you use the fetch join, even if you put LAZY it will be eager loaded.
r
rudolfson

"mxc" is right. fetchType just specifies when the relation should be resolved.

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.


This doesn't work for me with Hibernate 4.2.15, in JPQL or Criteria.
@Aphax I think that's because JPAQL and Criteria don't obey the Fetch specification. The Fetch annotation only works for em.find(), AFAIK. See vladmihalcea.com/2013/10/17/… Also, see the hibernate docos. I'm pretty sure this is covered somewhere.
@JoshuaDavis What I mean is that the \@Fetch annotation doesn't apply any kind of JOIN optimisation in the queries, wheter JPQL or em.find(), I just had another try on Hibernate 5.2.+ and it's still the same
T
Tomasz Nurkiewicz

The fetchType attribute controls whether the annotated field is fetched immediately when the primary entity is fetched. 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.

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. 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.

If you set fetchType=LAZY the field is only populated when it is accessed. If you have closed the entitymanager by then a lazy load exception will be thrown if you do a 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.

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 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. 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.


s
sinuhepop

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:


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

If you use EclipseLink instead of Hibernate you can optimize your queries by "query hints". See this article from the Eclipse Wiki: EclipseLink/Examples/JPA/QueryOptimization.

There is a chapter about "Joined Reading".


C
Community

to join you can do multiple things (using eclipselink)

in jpql you can do left join fetch

in named query you can specify query hint

in TypedQuery you can say something like 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


G
Gunslinger

I had exactly this problem with the exception that the Person class had a embedded key class. My own solution was to join them in the query AND remove

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

}

c
cletus

Two things occur to me.

First, are you sure you mean ManyToOne for address? 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).

Secondly, do you have any other eager relationships on the Person entity? If I recall correctly, Hibernate can only handle one eager relationship on an entity but that is possibly outdated information.

I say that because your understanding of how this should work is essentially correct from where I'm sitting.


This is a made up example that uses many-to-one. Person-address may not have been the best example. I don't see any other eager fetch types in my code.
My suggestion then is to reduce this to a simple example that runs and does what you're seeing and then post that. There may be other complications in your model that are causing unexpected behaviour.
I ran the code exactly as it appears above and it exhibits said behavior.
I heard from strange places (sometime called city) where multiple persons share the same address (here and there are buildings large enough for more than one person) and even multiple person in one apartment. - strange world we live in @cletus