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.
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.
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.
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.
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 beSELECT
(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) orJOIN
(use a SQLJOIN
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;
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With