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
.