Ignore a FetchType.EAGER in a relationship
Update (09/06/2020):
The issue was resolved on the 5.4.11 version. I can't test right now, but is expected that the JPA entity-graphs attributes not included in the graph should stay unloaded, even if they are declared EAGER
.
Original answer
After all this years, override the EAGER mapping is not yet possible on Hibernate. From the latest Hibernate documentation (5.3.10.Final):
Although the JPA standard specifies that you can override an EAGER fetching association at runtime using the javax.persistence.fetchgraph hint, currently, Hibernate does not implement this feature, so EAGER associations cannot be fetched lazily. For more info, check out the HHH-8776 Jira issue.
When executing a JPQL query, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly, which can lead dto N+1 query issues.
For this reason, it’s better to use LAZY associations, and only fetch them eagerly on a per-query basis.
And:
The EAGER fetching strategy cannot be overwritten on a per query basis, so the association is always going to be retrieved even if you don’t need it. More, if you forget to JOIN FETCH an EAGER association in a JPQL query, Hibernate will initialize it with a secondary statement, which in turn can lead to N+1 query issues.
If you are using JPA 2.1 (Hibernate 4.3+) you can achieve what you want with @NamedEntityGraph.
Basically, you would annotate your entity like this:
@Entity
@NamedEntityGraph(name = "Persons.noAddress")
public class Person {
@Column
private String name;
@OneToMany(fetch=FetchType.EAGER)
private List<String> address;
}
And then use the hints to fetch Person without address, like this:
EntityGraph graph = this.em.getEntityGraph("Persons.noAddress");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return this.em.findAll(Person.class, hints);
More on the subject can be found here.
When you use the fetch graph only fields that you have put inside @NamedEntityGraph will be fetched eagerly.
All your existing queries that are executed without the hint will remain the same.
By default, Hibernate's HQL, Criteria and NativeSQL gives us the flexibility to EAGERly load a collection if it is mapped as LAZY in the domain model.
Regarding the other way round, ie., mapping the collection as as EAGER in the domain model and try to do LAZY load using HQL, Criteria or NativeSQL, I couldn't find a straight forward or simpler way in which we can meet this with HQL/Criteria/NativeSQL.
Although we have FetchMode.LAZY
that we can set on Criteria,it is deprecated and it is equivalent to FetchMode.SELECT
. And effectively FetchMode.LAZY actually results in firing an extra SELECT query and still eagerly loads the collection.
But, if we want to LAZY load a collection that is mapped as EAGER, you can try this solution: Make the HQL/Criteria/NativeSQL to return the scalar values and use a ResultTransformer(Transformers.aliasToBean(..))
to return the entity object (or DTO) with fields populated from scalar values.
In my scenario, I have a Forest entity that has a collection of Tree entities with oneToMany mapping of FetchType.EAGER
and FetchMode.JOIN
. To load only the Forest entity without loading any trees, I have used the following HQL query with scalar values and Transformers.aliasToBean(...). This works with Criteria and Native SQL as well as long as scalars and aliasToBean Transformer is used.
Forest forest = (Forest) session.createQuery("select f.id as id, f.name as name, f.version as version from Forest as f where f.id=:forest").setParameter("forest", i).setResultTransformer(Transformers.aliasToBean(Forest.class)).uniqueResult();
I have tested for above simple query and it might be working checking if this works for complex cases as well and fits all your use cases.
Would be keen to know if there is a better or simpler way of doing it especially without scalars and Transformers.