web-dev-qa-db-ger.com

Dynamische jpa-Repository-Abfrage für Federdaten mit beliebigen AND-Klauseln

Ich benutze Spring data jpa repositories, Muss eine Suchfunktion mit verschiedenen Feldern angeben. Das Eingeben von Feldern vor der Suche ist optional. In 5 Feldern werden EmployeeNumber, Name, Married, Profession und DateOfBirth angezeigt.
Hier muss ich nur mit den angegebenen Werten nach Benutzer abfragen und andere Felder sollten ignoriert werden.

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%';  

Hier betrachten wir also eingegebene Werte und Abfragen. In diesem Fall haben Spring-Daten eine Einschränkung, wie in this post ( Nicht skalierbar und alle möglichen Abfragen sollten geschrieben werden ) Ich verwende Querydsl, aber das Problem besteht immer noch, da null -Felder ignoriert werden sollten und fast alle möglichen Abfragen entwickelt werden müssen. In diesem case 31 queries. Was ist, wenn Suchfelder 6,7,8... ??

Was ist der beste Ansatz, um eine Suchoption mit optionalen Feldern zu implementieren?

40
Mr.Chowdary

Bitte beachten Sie, dass möglicherweise Änderungen vorgenommen werden müssen, um die neue Hauptversion von QueryDSL (4.x) und querydsl-jpa zu verwenden


In einem unserer Projekte haben wir QueryDSL mit 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();
  }

Und wir könnten dies in den Repositories verwenden:

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

Wo DaoRepository ist

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

Denn dann können Sie Repository-Prädikatmethoden verwenden.

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

Um QClasses zu erhalten, müssen Sie das QueryDSL APT Maven-Plugin in Ihrer pom.xml angeben.

  <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>

Abhängigkeiten sind

    <!-- 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>

Oder für 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

Sie können die Spezifikationen verwenden, die Spring-data Ihnen standardmäßig zur Verfügung stellt. Sie können die Kriterien-API zum programmgesteuerten Erstellen von Abfragen verwenden. Zur Unterstützung von Spezifikationen können Sie die Repository-Schnittstelle mit der JpaSpecificationExecutor-Schnittstelle erweitern

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

}

Die zusätzliche Schnittstelle (JpaSpecificationExecutor) enthält Methoden, mit denen Sie Spezifikationen auf verschiedene Arten ausführen können.

Beispielsweise gibt die findAll-Methode alle Entitäten zurück, die der Spezifikation entsprechen:

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

Die Spezifikationsschnittstelle sieht wie folgt aus:

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

Okay, was ist der typische Anwendungsfall? Mithilfe von Spezifikationen kann auf einfache Weise ein erweiterbarer Satz von Prädikaten auf einer Entität erstellt werden, der dann mit JpaRepository kombiniert und verwendet werden kann, ohne dass für jede erforderliche Kombination eine Abfrage (Methode) deklariert werden muss. Hier ist ein Beispiel: Beispiel 2.15. Spezifikationen für einen Kunden

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
            }
        };
    }
}

Sie haben einige Kriterien für eine Abstraktionsebene für Geschäftsanforderungen angegeben und ausführbare Spezifikationen erstellt. So kann ein Client eine Spezifikation wie folgt verwenden:

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

Sie können auch Spezifikationsbeispiel 2.17 kombinieren. Kombinierte Spezifikationen

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

Wie Sie sehen können, bietet Specifications einige Klebecodemethoden zum Verketten und Kombinieren von Specifications. Die Erweiterung Ihrer Datenzugriffsebene ist daher nur eine Frage der Erstellung neuer Spezifikationsimplementierungen und ihrer Kombination mit bereits vorhandenen.

Und Sie können komplexe Spezifikationen erstellen, hier ein Beispiel

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[] {}));
            }
        };
    }
}

Hier ist das JPA Respositories docs

24
iamiddy

Aus Spring Data JPA 1.10 gibt es eine andere Option für diese ist Abfrage nach Beispiel . Ihr Repository sollte neben JpaRepository auch die Schnittstelle QueryByExampleExecutor implementieren, über die Sie Methoden wie die folgenden erhalten:

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

Dann erstellen Sie das Beispiel , um nach Folgendem zu suchen:

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

und dann:

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

Wenn einige Parameter null sind, werden sie nicht in die WHERE-Klausel übernommen, sodass Sie dynamische Abfragen erhalten.

Um den Abgleich von String-Attributen zu verfeinern, schauen Sie sich ExampleMatcher an

Ein ExampleMatcher, bei dem die Groß- und Kleinschreibung nicht berücksichtigt wird like ist zum Beispiel:

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

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

21
Robert Niestroj