JPA Query Filter: Extension JPA pour la recherche multi-critères

4min read • 2025-04-29Dev Full-stack IconTechnology
JPA Query Filter: Extension JPA pour la recherche multi-critères

Introduction

JPA Query Filter est une extension de la spécification JPA qui simplifie considérablement la construction des requêtes de recherche multi-critères en ajoutant une couche d’abstraction intuitive sous le Criteria API.

Installation

1 - Requirements

  • Java 17+
  • Spring Boot 3.0.0+

2 - Existant

L’API Criteria est une API permettant de générer des requêtes avec des objets Java™ , comme alternative à la génération de chaînes pour les requêtes JPQL (Java Persistence Query Language). Néanmoins l’API requis beaucoup de boilerplate et de répétition pour les cas courants.

JPA Query Filter vient justement pour exposer une API plus abstraite qui gère le boilerplate de manière dynamique, facilitant ainsi l’utilisation et la création des repositories de recherche multi-critères.

3 - Utilisation

Considérant comme exemple qu’on a un schéma de base de données représenté par les entités JPA suivantes :

@Entity
public class School extends Organisation {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(mappedBy = "school")
    private List<Student> students;
    @OneToMany(mappedBy = "school")
    private List<SchoolCourse> schoolCourses;
}
@MappedSuperclass
public class Organisation {
    private RegionEnum region;
}
@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToOne
    @JoinColumn(name = "school_id")
    private School school;
    @OneToMany(mappedBy = "student")
    private List<StudentCourse> studentCourses;
}
@Entity
public class StudentCourse {
    @EmbeddedId
    private StudentCourseKey studentCourseKey = new StudentCourseKey();

    @ManyToOne
    @MapsId("studentId")
    @JoinColumn(name = "student_id")
    Student student;

    @ManyToOne
    @MapsId("courseId")
    @JoinColumn(name = "course_id")
    Course course;
}
@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(mappedBy = "course")
    private List<SchoolCourse> schoolCourses;
    @OneToMany(mappedBy = "course")
    private List<StudentCourse> studentCourses;
}
@Entity
public class SchoolCourse {
    @EmbeddedId
    private SchoolCourseKey schoolCourseKey = new SchoolCourseKey();

    @ManyToOne
    @MapsId("schoolId")
    @JoinColumn(name = "school_id")
    School school;

    @ManyToOne
    @MapsId("courseId")
    @JoinColumn(name = "course_id")
    Course course;
  }

Ajout de la dépendance:

<dependency>
    <groupId>ma.neoxia</groupId>
    <artifactId>jpa-query-filter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

Extension du repository:

Le repository JPA doit hériter de l’interface JpaQueryFilter qui est une interface générique prenant la classe de l’entité en question comme type générique.

@Repository
public interface SchoolRepository extends JpaRepository<School, Long>, JpaQueryFilter<School> {
    
}

Création de la classe Query:

Il est nécessaire de créer une classe Query qui va contenir les champs de recherche. Cette classe doit implémenter l’interface QueryFilterCriteria.

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class SchoolQueryDto implements QueryFilterCriteria {
    private Long id;
    private String name;
    private RegionEnum region;
}

ou bien:

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class OrganisationQueryDto implements QueryFilterCriteria {
    private RegionEnum region;
}
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class SchoolQueryDto extends OrganisationQueryDto {
    private Long id;
    private String name;
}

JPA Query Filter mappe automatiquement le nom des attributs de la classe Query avec le nom des champs de l’entité.

L’interface JpaQueryFilter, que notre repository implémente, nous donne accès à une implémentation par défaut de la méthode de recherche appelée findByQuery.

@Override
default Page<School> findByQuery(QueryFilterCriteria jpaQuery, Pageable pageable) {
    return JpaQueryFilter.super.findByQuery(jpaQuery, pageable);
}

L’interface nous donne aussi accès à la méthode buildPredicates qui nous expose les Predicates du Criteria API générés dynamiquement.

@Override
default List<Predicate> buildPredicates(QueryFilterCriteria query, Root<School> root, CriteriaBuilder criteriaBuilder) {
    return JpaQueryFilter.super.buildPredicates(query, root, criteriaBuilder);
}

Notre api de test sera comme suite:

@GetMapping("find-by-query")
public Page<SchoolDto> findByQuery(SchoolQueryDto schoolQuery) {
    var schoolPage = schoolRepository.findByQuery(schoolQuery, 0, 20);

    return schoolMapper.mapToDto(schoolPage);
}

C’est tout pour un bootstrap. On peut tester notre API qui nous donnera le résultat des recherches.

4 - Personalisation

JPA Query Filter fournit une multitude d’annotations pour personnaliser la recherche.

@JpaQueryOperation

Cette annotation nous permet de spécifier le type d’opération de recherche sur un champ donné.

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class SchoolQueryDto implements QueryFilterCriteria {
    private Long id;
    @JpaQueryOperation(JpaQueryOperationEnum.LIKE)
    private String name;
    @JpaQueryOperation(JpaQueryOperationEnum.IN)
    private List<RegionEnum> region;
}

@JpaQueryFieldsOperations

Cette annotation nous permet de donner la main à l’utilisateur du repository pour spécifier l’opération de recherche pour les différents champs en temps d’exécution.

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class SchoolQueryDto implements QueryFilterCriteria {
    private Long id;
    @JpaQueryOperation(JpaQueryOperationEnum.LIKE)
    private String name;
    @JpaQueryOperation(JpaQueryOperationEnum.IN)
    private List<RegionEnum> region;

    @JpaQueryFieldsOperations
    private Map<String, JpaQueryOperationEnum> op;
}

@JpaQueryPath

Cette annotation sert à spécifier un mappage des noms de champs avec l’entité manuellement.

@JpaQueryOperation(JpaQueryOperationEnum.IN)
@JpaQueryPath("region")
private List<RegionEnum> regions;

JPA Query Filter supporte également la recherche entre différentes tables ayant des relations SQL entre elles. On peut accomplir ceci en nommant le champ de recherche selon la nomenclature des champs des entités à rechercher, séparés par des underscores :

private String students_name;

Ce champ, par exemple, effectue une recherche par le champ “name” de l’entité Student qui est liée à l’entité School par le champ “students”.

Voici un autre exemple :

private String students_studentCourses_course_name;

5 - Divers usage

On peut remarquer que les champs “students_name” ou bien “students_studentCourses_course_name” ne respectent pas les bonnes normes de nomenclature. On peut remédier à ceci en utilisant l’annotation @JpaQueryPath comme suit :

//    private String students_name;
    @JpaQueryPath({"students", "name"})
    private String studentName;

//    private String students_studentCourses_course_name;
    @JpaQueryPath({"students", "studentCourses", "course", "name"})
    private String studentCourseName;

Comme on peut définir plusieurs champs référençant le même attribut dans notre entité :

@JpaQueryPath({"students", "studentCourses", "course", "name"})
@JpaQueryOperation(JpaQueryOperationEnum.IN)
private List<String> studentCourseNames;
@JpaQueryPath({"students", "studentCourses", "course", "name"})
private String studentCourseName;

6 - Conclusion

JPA Query Filter est une extension de la spécification JPA facilitant la recherche multi-critère en utilisant le Criteria API. Une multitude d’outils est fournie dans le but de minimiser la répétition et le boilerplate.

L’objectif n’est pas de remplacer le Criteria API, mais plutôt de l’enrichir en ajoutant une couche d’abstraction supplémentaire tout en offrant la possibilité d’accéder au mécanisme interne de l’API.

lien vers le repository :

https://github.com/xelops-technology/jpa-query-filter

Abdessamia ArouayWritten By Abdessamia Arouay Full-Stack DeveloperXelops Technology