Is there a way to use @NamedEntityGraph
for eager fetch of multiple collections of entities? I'm using Spring boot with Spring data and hibernate.
I have 2 entities- grandfather and father who have OneToMany
relation. I need to get all of the grandfather fathers and then use a function on each of them (for simplicity let's say .toString()
)
The problem is that the Father
entity has 25 different type of children and each one is a collection with FetchType.LAZY
and relations of ManyToMany
, so calling toString
loades all of the children collections and make the function very slow.
The entities looks like that:
public class GrandFather() {
private Integer id;
@OneToMany(fetch = FetchType.LAZY)
private Set<Father> fathers;
}
public class Father() {
private Integer id;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "father_child_type_one"
joinColumns = @JoinColumn(name = "fk_father_id_child_type_one", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "fk_father_child_type_one_id", referencedColumnName = "id")
)
private Set<Child1> typeOneChildren;
.
.
.
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "father_child_type_twenty_five"
joinColumns = @JoinColumn(name = "fk_father_id_child_type_twenty_five", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "fk_father_child_type_twenty_five_id", referencedColumnName = "id")
)
private Set<Child25> typeTwentyFiveChildren;
public void toString() {
typeOneChildren.stream().forEach(child -> child.toString());
.
.
.
typeTwentyFiveChildren.stream().forEach(child -> child.toString());
}
}
Each one of the children are a different Entity with different properties.
I tried adding @NamedEntityGraph
to GrandFather
like this:
@NamedEntityGraph(name = "fetchGrandFatherWithChildren",
attributeNodes = {
@NamedAttributeNode(value = "fathers", subgraph = "father_subgraph")
},
subgraphs = {
@NamedSubgraph(name = "father_subgraph", type = Father.class, attributeNodes = {
@NamedAttributeNode("typeOneChildren"),
.
.
.
@NamedAttributeNode("typeTwentyFiveChildren")
})
})
but this EntityGraph only worked when I put it on findById
(most of the app uses this function and doesn't need the graph) in GrandFatherRepository
, every other name and it didn't work - either just kept on doing all the lazy fetching for each father on each child type or throwing MultipleBagFetchException
(which make sense with all of the collections and Cartesian results).
public class GrandFatherService {
GrandFatherRepository grandFatherRepository;
public void toString(Integer grandfatherId) {
Set<Father> fathers = grandFatherRepository.findById(grandfatherId);
fathers.stream().forEach(Father::toString);
}
}
The other solution I found was based on the answer for this qestion
and it as follows (it works but very messy):
in my GrandFatherService
in the function that I try to fetch the GrandFather with all of his grandchildren eagerly, I get the GrandFather with the fathers eagerly, and then I fetch each type of child for all of the fathers and set them.
(findWithFathersById
uses a basic entity graph)
public class GrandFatherService {
GrandFatherRepository grandFatherRepository;
FatherRepository fatherRepository;
public void toString(Integer grandfatherId) {
Set<Father> fathers = grandFatherRepository.findWithFathersById(grandfatherId);
Map<Interer, Father> fatherChildOne = fatherRepository.findAllWithChildTypeOne(grandfatherId);
.
.
.
Map<Interer, Father> fatherChildTwentyFive = fatherRepository.findAllWithChildTypeTwentyFive(grandfatherId);
fathers.stream().forEach(father -> {
Integer fatherId = father.getId();
father.setChildTypeOne(fatherChildOne.get(fatherId).getChildOne);
.
.
.
father.setChildTypeTwentyFive(fatherChildTwentyFive.get(fatherId).getChildTwentyFive);
father.toString();
}
}
}
public interface FatherRepository extends JpaRepository<Father, Integer> {
@Query(value = "select f from Father f LEFT JOIN FETCH f.typeOneChildren")
Set<Father> findAllWithChildTypeOne(Integer trialId);
.
.
.
@Query(value = "select f from Father f LEFT JOIN FETCH f.typeTwentyFiveChildren")
Set<Father> findAllWithChildTypeTwentyFive(Integer trialId);
}
For conclusion, my question is: Is there a less messy way to eager fetch all of the grand children/ to make the first entity graph to work not with findById?