We are moving from Hibernate native criteria to JPA criteria queries in scope of upgrading a hibernate from 4.3.11 to 5.2.12 and found out different behavior. Previously hibernate criteria use a single query with joins to eager fetch one-to-many associated entities, but JPA use separate queries to fetch the associated entities for each root entity.
I know I can explicitly set fetch mode like entityRoot.fetch("attributes", JoinType.INNER);
but we need to do it in some AbstractDao implementation that should work for any eager one-to-many association so can't explicitly set this.
So can I somehow tell JPA criteria to eager fetch associated entities in a single query using joins by default instead of separate queries for each root entity?
The code example:
CriteriaBuilder builder = createCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = builder.createQuery(getEntityClass());
Root<T> entityRoot = criteriaQuery.from(getEntityClass());
criteriaQuery.select(entityRoot);
criteriaQuery.where(builder.equal(entityRoot.get("param1"), "value"));
return getEntityManager().createQuery(criteriaQuery).getResultList();
You can't configure it in such a way, but you may implement the necessary behavior.
As you may read in Hibernate 5.2 User Guide, there are several ways to apply a fetching strategy:
@Fetch
annotation is a static way to apply fetching strategy, and the FetchMode.JOIN
works exactly as you've described:
Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of an SQL outer join.
The problem is, even if you would mark your attributes
collection with the @Fetch(FetchMode.JOIN)
annotation, it would be overridden:
The reason why we are not using a JPQL query to fetch multiple Department entities is because the FetchMode.JOIN strategy would be overridden by the query fetching directive.
To fetch multiple relationships with a JPQL query, the JOIN FETCH directive must be used instead.
Therefore, FetchMode.JOIN is useful for when entities are fetched directly, via their identifier or natural-id.
JPA Criteria query without FetchParent::fetch
would do the same.
Since you need a universal solution for an abstract DAO, the possible way is to process all eager one-to-many associations with reflection:
Arrays.stream(getEntityClass().getDeclaredFields())
.filter(field ->
field.isAnnotationPresent(OneToMany.class))
.filter(field ->
FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
.forEach(field ->
entityRoot.fetch(field.getName(), JoinType.INNER));
Of course, calling reflection for every query would be inefficient. You may obtain all loaded @Entity
classes from Metamodel, process them, and store results for further use:
Metamodel metamodel = getEntityManager().getMetamodel();
List<Class> entityClasses = metamodel.getEntities().stream()
.map(Type::getJavaType)
.collect(Collectors.toList());
Map<Class, List<String>> fetchingAssociations = entityClasses.stream()
.collect(Collectors.toMap(
Function.identity(),
aClass -> Arrays.stream(aClass.getDeclaredFields())
.filter(field ->
field.isAnnotationPresent(OneToMany.class))
.filter(field ->
FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
.map(Field::getName)
.collect(Collectors.toList())
));
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