I\'m trying to do this:
//...
class Person {
@ManyToMany(fetch = FetchType.EAGER)
@Fetch(FetchMode.JOIN)
private Set groups;
//...
Going through many forums and blogs to read for your problem (I guess you might have done that before posting it here) I too think that
@Fetch(FetchMode.JOIN) will be ignored if you use the Query interface (e.g.: session.createQuery()) but it will be properly used if you use the Criteria interface.
This is practically a bug in Hibernate which was never resolved. It is unfortunate because a lot of applications use the Query interface and cannot be migrated easily to the Criteria interface.
If you use the Query interface you always have to add JOIN FETCH statements into the HQL manually.
References Hibernate Forum Spring Forum Similar Question 1
I also couldn't get @Fetch(FetchMode.JOIN)
to work when using JPA (although it works fine when using the hibernate Criteria api) and I also couldn't find any examples explaining why, but I can think of a few workarounds.
The most straightforward way to load the Groups eagerly, is to use JPQL:
public interface PersonRepository extends JpaRepository<Person, String>{
@Query(value = "select distinct p from Person p left join fetch p.groups")
List<Person> getAllPersons();
}
As you are using spring-data-jpa, you could also load the Groups eagerly by using a Specification
. (As of 1.4.x you can chain specs that return null).
final Specification<Person> fetchGroups = new Specification<Person>() {
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
root.fetch("groups", JoinType.LEFT);
query.distinct(true);
return null;
}
};
If neither of these is an option for you, your best bet is probably to use @Fetch(FetchMode.SUBSELECT)
.
Another option is to use @Fetch(FetchMode.SELECT)
in combination with @BatchSize
. @BatchSize
helps to solve the problem of the n+1 queries. By tweaking the batch size you can reduce the amount of queries executed to CEIL(n/batch_size)+1.
@Entity
@Table(name = "persons")
public class Person {
@Id
String name;
@ManyToMany(fetch = FetchType.EAGER)
@BatchSize(size = 20)
Set<Group> groups = new HashSet<>();
}
@Entity
@Table(name = "groups")
public class Group {
@Id
String name;
@ManyToMany(mappedBy = "groups", fetch = FetchType.LAZY)
Set<Person> persons = new HashSet<>();
}
public interface PersonRepository extends JpaRepository<Person, String>{}
This mapping results in the following sql when you run personRepository.findAll();
on a database containing 10 persons and @BatchSize
set to 5.
Hibernate:
select
person0_.name as name1_
from
persons person0_
Hibernate:
select
groups0_.persons_name as persons1_1_1_,
groups0_.groups_name as groups2_1_,
group1_.name as name0_0_
from
persons_groups groups0_
inner join
groups group1_
on groups0_.groups_name=group1_.name
where
groups0_.persons_name in (
?, ?, ?, ?, ?
)
Hibernate:
select
groups0_.persons_name as persons1_1_1_,
groups0_.groups_name as groups2_1_,
group1_.name as name0_0_
from
persons_groups groups0_
inner join
groups group1_
on groups0_.groups_name=group1_.name
where
groups0_.persons_name in (
?, ?, ?, ?, ?
)
Note that @BatchSize
also works for collections mapped with FetchType.LAZY
.