web-dev-qa-db-fra.com

Requête de référentiel jpa de données de ressort dynamique avec des clauses AND arbitraires

J'utilise Spring data jpa repositories _, Vous devez obligatoirement donner une fonction de recherche avec différents champs. La saisie des champs avant la recherche est facultative. J'ai 5 champs, à savoir EmployeeNumber, Name, Married, Profession et DateOfBirth.
Ici, je dois interroger uniquement les valeurs données par utilisateur et les autres champs doivent être ignorés.Ex,

Input : EmployeeNumber: ,Name:St,Married: ,Professsion:IT,DateOfBirth: 
Query : Select * from Employee e where Name like 'St%' and Profession like 'IT%';  

Input : EmployeeNumber:10,Name: ,Married: ,Professsion:IT,DateOfBirth:
Query : Select * from Employee e where EmployeeNumber like '10%' and Profession like 'IT%';  

Nous examinons donc ici les valeurs saisies et les requêtes. Dans ce cas, les données de printemps ont une limitation comme mentionné dans cet article ( non évolutif et toutes les requêtes possibles doivent être écrites ) J'utilise Querydsl, mais le problème persiste, car les champs null doivent être ignorés et presque toutes les requêtes possibles doivent être développées. Dans ce case 31 queries. Et si les champs de recherche sont 6,7,8... ??

Quelle est la meilleure approche pour implémenter une option de recherche avec des champs optionnels?

40
Mr.Chowdary

Veuillez noter que des modifications peuvent être apportées pour utiliser la nouvelle version majeure de QueryDSL (4.x) et querydsl-jpa


Dans l'un de nos projets, nous avons utilisé QueryDSL avec QueryDslPredicateExecutor<T>.

  public Predicate createPredicate(DataEntity dataEntity) {
    QDataEntity qDataEntity = QDataEntity.dataEntity;
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    if (!StringUtils.isEmpty(dataEntity.getCnsiConsumerNo())) {
      booleanBuilder
        .or(qDataEntity.cnsiConsumerNo.contains(dataEntity.getCnsiConsumerNo()));
    }
    if (!StringUtils.isEmpty(dataEntity.getCnsiMeterNo())) {
      booleanBuilder.or(qDataEntity.cnsiMeterNo.contains(dataEntity.getCnsiMeterNo()));
    }

    return booleanBuilder.getValue();
  }

Et nous pourrions utiliser cela dans les dépôts:

@Repository
public interface DataEntityRepository
  extends DaoRepository<DataEntity, Long> {

DaoRepository est

@NoRepositoryBean
public interface DaoRepository<T, K extends Serializable>
  extends JpaRepository<T, K>,
  QueryDslPredicateExecutor<T> {
}

Dans ce cas, vous pouvez utiliser des méthodes de prédicat de référentiel.

Iterable<DataEntity> results = dataEntityRepository.findAll(dataEntityPredicateCreator.createPredicate(dataEntity));

Pour obtenir QClasses, vous devez spécifier le plug-in QueryDSL APT Maven dans votre pom.xml.

  <build>
    <plugins>
      <plugin>
        <groupId>com.mysema.maven</groupId>
        <artifactId>maven-apt-plugin</artifactId>
        <version>1.0.4</version>
        <executions>
          <execution>
            <phase>generate-sources</phase>
            <goals>
              <goal>process</goal>
            </goals>
            <configuration>
              <outputDirectory>target/generated-sources</outputDirectory>
              <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
          </execution>
        </executions>
      </plugin>

Les dépendances sont

    <!-- querydsl -->
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-core</artifactId>
        <version>${querydsl.version}</version>
    </dependency>
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>${querydsl.version}</version>
    </dependency>
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>${querydsl.version}</version>
    </dependency>

Ou pour Gradle:

sourceSets {
    generated
}
sourceSets.generated.Java.srcDirs = ['src/main/generated']
configurations {
    querydslapt
}
dependencies {
    // other deps ....
    compile "com.mysema.querydsl:querydsl-jpa:3.6.3"
    compile "com.mysema.querydsl:querydsl-apt:3.6.3:jpa"
}
task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
    source = sourceSets.main.Java
    classpath = configurations.compile + configurations.querydslapt
    options.compilerArgs = [
            "-proc:only",
            "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
    ]
    destinationDir = sourceSets.generated.Java.srcDirs.iterator().next()
}

compileJava {
    dependsOn generateQueryDSL
    source generateQueryDSL.destinationDir
}

compileGeneratedJava {
    dependsOn generateQueryDSL
    classpath += sourceSets.main.runtimeClasspath
}
24
EpicPandaForce

Vous pouvez utiliser les spécifications que Spring-data vous donne prêtes à l'emploi. et être capable d'utiliser des API de critères pour créer des requêtes par programme. Pour prendre en charge les spécifications, vous pouvez étendre votre interface de référentiel avec l'interface JpaSpecificationExecutor.

public interface CustomerRepository extends SimpleJpaRepository<T, ID>, JpaSpecificationExecutor {

}

L’interface supplémentaire (JpaSpecificationExecutor) contient des méthodes qui vous permettent d’exécuter les spécifications de différentes façons.

Par exemple, la méthode findAll renverra toutes les entités qui correspondent à la spécification:

List<T> findAll(Specification<T> spec);

L'interface de spécification est la suivante:

public interface Specification<T> {
     Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder);
}

Bon, alors quel est le cas d'utilisation typique? Les spécifications peuvent facilement être utilisées pour construire un ensemble extensible de prédicats sur une entité, qui peut ensuite être combinée et utilisée avec JpaRepository sans qu'il soit nécessaire de déclarer une requête (méthode) pour chaque combinaison requise. Voici un exemple: Exemple 2.15. Spécifications pour un client

public class CustomerSpecs {
    public static Specification<Customer> isLongTermCustomer() {
        return new Specification<Customer>() {
            public Predicate toPredicate(
                Root<Customer> root, CriteriaQuery<?> query,
                CriteriaBuilder builder) {
                LocalDate date = new LocalDate().minusYears(2);
                return builder.lessThan(root.get('dateField'), date);
            }
        };
    }

    public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
        return new Specification<Customer>() {
            public Predicate toPredicate(
                Root<T> root, CriteriaQuery<?> query,
                CriteriaBuilder builder) {
                // build query here
            }
        };
    }
}

Vous avez exprimé certains critères sur un niveau d'abstraction des exigences métier et créé des spécifications exécutables. Ainsi, un client peut utiliser une spécification comme suit:

List customers = customerRepository.findAll(isLongTermCustomer());

Vous pouvez également combiner Specification Example 2.17. Spécifications combinées

    MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
    List<Customer> customers = customerRepository.findAll(
        where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));

Comme vous pouvez le constater, Spécifications propose des méthodes à code collé pour chaîner et combiner des Spécifications. Pour étendre votre couche d'accès aux données, il vous suffit de créer de nouvelles implémentations de Spécification et de les combiner avec celles déjà existantes.

Et vous pouvez créer des spécifications complexes, voici un exemple

public class WorkInProgressSpecification {
    public static Specification<WorkInProgress> findByCriteria(final SearchCriteria searchCriteria) {

        return new Specification<WorkInProgress>() {

            @Override
            public Predicate toPredicate(
                Root<WorkInProgress> root,
                CriteriaQuery<?> query, CriteriaBuilder cb) {

                List<Predicate> predicates = new ArrayList<Predicate>();

                if (searchCriteria.getView() != null && !searchCriteria.getView().isEmpty()) {
                    predicates.add(cb.equal(root.get("viewType"), searchCriteria.getView()));
                }
                if (searchCriteria.getFeature() != null && !searchCriteria.getFeature().isEmpty()) {
                    predicates.add(cb.equal(root.get("title"), searchCriteria.getFeature()));
                }
                if (searchCriteria.getEpic() != null && !searchCriteria.getEpic().isEmpty()) {
                    predicates.add(cb.equal(root.get("epic"), searchCriteria.getEpic()));
                }
                if (searchCriteria.getPerformingGroup() != null && !searchCriteria.getPerformingGroup().isEmpty()) {
                    predicates.add(cb.equal(root.get("performingGroup"), searchCriteria.getPerformingGroup()));
                }
                if (searchCriteria.getPlannedStartDate() != null) {
                    System.out.println("searchCriteria.getPlannedStartDate():" + searchCriteria.getPlannedStartDate());
                    predicates.add(cb.greaterThanOrEqualTo(root.<Date>get("plndStartDate"), searchCriteria.getPlannedStartDate()));
                }
                if (searchCriteria.getPlannedCompletionDate() != null) {
                    predicates.add(cb.lessThanOrEqualTo(root.<Date>get("plndComplDate"), searchCriteria.getPlannedCompletionDate()));
                }
                if (searchCriteria.getTeam() != null && !searchCriteria.getTeam().isEmpty()) {
                    predicates.add(cb.equal(root.get("agileTeam"), searchCriteria.getTeam()));
                }

                return cb.and(predicates.toArray(new Predicate[] {}));
            }
        };
    }
}

Voici le documentation JPA Respositories

24
iamiddy

Dans Spring Data JPA 1.10, il existe une autre option. Requête par exemple . Votre référentiel doit implémenter en dehors de JpaRepository également l'interface QueryByExampleExecutor où vous obtenez des méthodes telles que:

<S extends T> Iterable<S> findAll(Example<S> example)

Ensuite, vous créez le Exemple à rechercher comme:

Employee e = new Employee();
e.setEmployeeNumber(getEmployeeNumberSomewherFrom());
e.setName(getNameSomewhereFrom());
e.setMarried(getMarriedSomewhereFrom());
e.setProfession(getProfessionSomewhereFrom());
e.setDateOfBirth(getDateOfBirthSomewhereFrom());

et alors:

employeeRepository.findAll(Example.of(e));

Si certains paramètres sont nuls, ils ne seront pas pris en compte dans la clause WHERE afin que vous obteniez des requêtes dynamiques.

Pour affiner la correspondance des attributs de chaîne, jetez un œil à ExampleMatcher

Un ExampleMatcher qui fait un like insensible à la casse est par exemple:

ExampleMatcher matcher = ExampleMatcher.matching().
          withMatcher("profession", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase());

Exemples QBE: https://github.com/spring-projects/spring-data-examples/tree/master/jpa/query-by-example

21
Robert Niestroj