Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does TypedQuery.getResultList() resolve every ManyToOne association with a seperate SELECT?

Tags:

hibernate

jpa

Considering the following simple entity association: (EntityA) *-1 (EntityB) made in database with a foreign key in EntityA (entityB_id).

The JPA Entities are mapping this relationship unidirectional:

@Entity
EntityA {
    @Id
    @GeneratedValue
    private long id;

    @Column(nullable=false,length=250)
    private String name;

    @ManyToOne(optional=false)
    private EntityB entityB;

    ... getter/setter ...
}

@Entity
EntityB {
    @Id
    @GeneratedValue
    private long id;

    @Column(nullable=false,length=250)
    private String name;

    ... getter/setter ...
}

If a simple query is made:

EntityManager em = ...;
TypedQuery<EntityA> tq = em.createQuery("from EntityA a", EntityA.class);
tq.getResultList();

I see in the SQL debug output of Hibernate, that an EntityB query is done for every row of EntityA:

Hibernate: 
    select
        entitya0_.id as id8_,
        entitya0_.entityB_id as entityB3_8_,
        entitya0_.name as name8_ 
    from
        EntityA entitya0_
Hibernate: 
    select
        entityb0_.id as id4_0_,
        entityb0_.name as name4_0_ 
    from
        EntityB entityb0_ 
    where
        entityb0_.id=?

Even if the default fetch strategy is EAGER (which seems to be the case), EntityB should be fetched via implizit join, shouldn't it? What is wrong?

But it's getting even more weird - if only a single EntityA object is loaded:

EntityA a = em.find(EntityA.class, new Long(1));

Then Hibernate seems to understand the job:

Hibernate: 
    select
        entitya0_.id as id1_1_,
        entitya0_.entityB_id as entityB3_1_1_,
        entitya0_.name as name1_1_,
        entityb1_.id as id12_0_,
        entityb1_.name as name12_0_ 
    from
        EntityA entitya0_ 
    inner join
        EntityB entityb1_ 
        on entitya0_.entityB_id=entityb1_.id 
    where
        entitya0_.id=?

The above tests have been made with Hibernate 3.5 and JPA 2.0.

like image 747
Zeemee Avatar asked Nov 11 '10 15:11

Zeemee


People also ask

How do I return multiple entities in JPA query?

3.1. In order to create a query returning multiple different entities, we need to do 2 things. Firstly, we need to list entities that we want to return in the SELECT part of the SQL Query, separated by a comma. Secondly, we need to connect them with each other by their primary and corresponding foreign keys.

How do I join two entities in JPA?

The only way to join two unrelated entities with JPA 2.1 and Hibernate versions older than 5.1, is to create a cross join and reduce the cartesian product in the WHERE statement. This is harder to read and does not support outer joins. Hibernate 5.1 introduced explicit joins on unrelated entities.

What is hibernate getResultList?

getResultList() Execute a SELECT query and return the query results as an untyped List. java.lang.Object. getSingleResult() Execute a SELECT query that returns a single untyped result.


2 Answers

Even if the default fetch strategy is EAGER (which seems to be the case), EntityB should be fetched via implicit join, shouldn't it? What is wrong?

Indeed, the default FetchType of a ManyToOne is EAGER. But this just says that One side should get loaded when the Many side is loaded, not how. The how is left at the discretion of the persistence provider (and JPA doesn't allow to tune the strategy).

Hibernate has a specific Fetch annotations allowing to tune the fetching mode though. From the documentation:

2.4.5.1. Lazy options and fetching modes

JPA comes with the fetch option to define lazy loading and fetching modes, however Hibernate has a much more option set in this area. To fine tune the lazy loading and fetching strategies, some additional annotations have been introduced:

  • [...]

  • @Fetch: defines the fetching strategy used to load the association. FetchMode can be SELECT (a select is triggered when the association needs to be loaded), SUBSELECT (only available for collections, use a subselect strategy - please refers to the Hibernate Reference Documentation for more information) or JOIN (use a SQL JOIN to load the association while loading the owner entity). JOIN overrides any lazy attribute (an association loaded through a JOIN strategy cannot be lazy).

You might want to try the following (if you don't mind using provider specific annotations):

@ManyToOne(optional=false)
@Fetch(FetchMode.JOIN)
private EntityB entityB;
like image 164
Pascal Thivent Avatar answered Sep 28 '22 08:09

Pascal Thivent


The solution that worked for the current use case is to include a fetch join in the statement:

select a from entityA left join fetch a.entityB

This will fetch all associated EntityBs (and override a FetchType.LAZY).

like image 29
Zeemee Avatar answered Sep 28 '22 10:09

Zeemee