Hibernate et fort probablement les autres ORM sont limité dans la capacité de ramener tout une structure d'object imbriqué.
@Entity
private class Student{
@Id
private Long studentId;
private String firstname;
private String lastname;
@OneToMany(
mappedBy =
"student")
private List<Course> courses
@OneToMany(
mappedBy =
"student")
private List<Book> books
}
MultipleBagFetchException
Si vous tentez de lancer cette requête provenant d'un repository
@Query("""
select s
from Student s
join fetch s.courses
join fetch s.books
""")
List<Student> findStudentWithCoursesBooks();
vous obtiendrez une errreur de type MultipleBagFetchException. Il n'est pas possible de fetcher plus qu'une entité qui va généré un produit cartésien.
Il pourrait être possible d'éviter cette erreur en changeant les list pour des set dans l'entité Student, cependant le produit cartésien se produira toujours.
Solution avec transaction
@Query("""
select s
from Student s
join fetch s.courses
""")
List<Student> findStudentWithCoursesBooks(); Dans un service
@Transactional
public List<Student> getStudent(){
List<Student> students = studentRepository.findStudentWithCoursesBooks();
for(Student student: students){
student.getBooks().size();
}
}
Il y aura chargement des livres, puisque l'annotation Transactional a été utilisé l'erreur LazyInitializationException ne survientdra pas. Cependant pour chaque étudiant, une requête sql pour aller chercher les Book. C'est le problème n+1 souvent mentionné dans le domaine des orm.
S'il y a que très peu de student, et que la méthode getStudent() est très peu utilisé. Cela pourrais être une solution possible. Il y a toujours possibilité d'ajouter du cache dans l'application afin de limiter les dégats
Solution avec multiple requêtes
Il est possible de combiner de multiple requete, une pour chaque fetch que vous désirez.
Le problème du n+1 est ainsi évite.
Il faut cependant que les deux retournes les même Students dans notre cas. Il faut donc ajouter une condition
@Query("""
select distinct(s)
from Student s
join fetch s.courses
where s.studentid < 10
""")
List<Student> findStudentWithCourses();@Query("""
select distinct(s)
from Student s
join fetch s.books
where s.studentid < 10
""")
List<Student> findStudentWithBooks(List<Student> students); Dans une classe au niveau du service
public StudentService{
private StudentRepository studentRepository;
@Transactional
public List<Students> getStudentWithCoursesBooks(){
List<Student> students = studentRepository.findStudentWithCourses();
return !students.isEmpty() ?
studentRepository.findStudentWithBooks(
minId,
maxId
) :
students;
}
}
Au niveau des requêtes seul deux requetes sont exécutés .
Aucun commentaire:
Enregistrer un commentaire