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_,
entitya开发者_如何学运维0_.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.
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).
First of all, why are you even worrying about this? The point of using an ORM library is so you don't have to deal with details like this. If you see that some operation is running slowly, then you start thinking about optimizing queries.
I think that Hibernate's strategy makes sense, since the relationship is ManyToOne. This means that the number of EntityBs returned in the query will never be greater than the number of EntityAs, and will usually be less. So trying to fetch the data from EntityB via a join would probably mean sending redundant copies of the same data.
精彩评论