2015-03-05 16 views
22

Ich benutze Spring data jpa repositories, bekam eine Anforderung, Suchfunktion mit verschiedenen Feldern zu geben. Felder vor der Suche eingeben ist optional. Ich habe 5 Felder sagen EmployeeNumber, Name, Married, Profession und DateOfBirth.
Hier muss ich nur mit den angegebenen Werten durch den Benutzer und andere Felder abfragen sollte also hier denken wir Werte eingegeben und anfragende ignored.Ex,Dynamische Frühjahr Daten jpa Repository Abfrage mit beliebigen UND-Klauseln

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

sein. In diesem Fall haben Spring Daten eine Einschränkung wie in this post (nicht skalierbar und alle möglichen Abfragen sollten geschrieben) Ich bin mit Querydsl, aber immer noch das Problem besteht als null Felder sollten ignoriert werden und fast alle möglichen Abfragen müssen entwickelt werden. In diesem case 31 queries. Was ist, wenn Suchfelder ??

Was ist der beste Ansatz zum Implementieren der Suchoption mit optionalen Feldern?

Antwort

17

Bitte beachten Sie, dass es möglicherweise Änderungen, die neue Hauptversion von QueryDSL (4.x) und querydsl-jpa


In einem unserer Projekte verwenden zu tun, 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ädikat Methoden verwenden.

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

Um QClasses zu erhalten, müssen Sie die QueryDSL APT Maven plugin in Ihrem 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 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 
} 
+0

Wir haben 3.2.5 Version zu diesem Zeitpunkt. – EpicPandaForce

+0

Es gab auch eine Anleitung zu diesem Thema hier (zusätzliche Informationen zum Testen, zum Beispiel): http: //www.petrikainulainen.net/programming/spring-framework/feder-daten-jpa-tutorial-part-five-querydsl/und ursprünglich hier: http://spring.io/blog/2011/04/26/advanced-spring-data-jpa- specifications-and-querydsl/ – EpicPandaForce

+1

Excellent .. Es funktioniert ganz gut .. Ich wusste nicht von 'BooleanBuilder' .. Nach einem guten Beispiel damit, Jetzt kam zu wissen, was Querydsl tun kann .. Danke für Ihre wertvolle Zeit .. –

21

Sie können Spezifikationen verwenden, die Frühlings-Daten, die Sie aus dem Kasten heraus gibt. und in der Lage sein, API verwenden Kriterien Abfragen programmatically.To Unterstützung Spezifikationen bauen Sie Ihre Repository-Schnittstelle mit dem JpaSpecificationExecutor Schnittstelle erweitern können

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

} 

Die zusätzliche Schnittstelle (JpaSpecificationExecutor) trägt Methoden, mit denen Sie Daten in einer Vielzahl von auszuführen Wege.

Zum Beispiel wird die findAll Methode alle Objekte zurück, die die Spezifikation entsprechen:

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

Die Spezifikation Schnittstelle ist wie folgt:

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

Okay, so was ist der typische Anwendungsfall? Spezifikationen können leicht verwendet werden, um eine erweiterbare Gruppe von Prädikaten über einer Entität zu erstellen, die dann mit JpaRepository kombiniert und verwendet werden kann, ohne dass eine Abfrage (Methode) für jede benötigte Kombination deklariert werden muss. Hier 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 äußerten einige Kriterien auf einer Abstraktionsebene Geschäftsanforderungen und erstellt ausführbare Spezifikationen. So könnte ein Client eine Spezifikation wie folgt verwenden:

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

Sie auch 2,17 Spezifikation Beispiel 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, Spezifikationen bietet einige Kleber-Code Methoden zur Ketten und Spezifikationen kombinieren. Daher ist die Erweiterung Ihrer Datenzugriffsschicht nur eine Frage der Erstellung neuer Spezifikationen Implementierungen und kombiniert sie mit denen bereits vorhandenen.

Und Sie komplexe Daten erstellen kann, ist 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 die JPA Respositories docs

+0

Ich bin mir ziemlich sicher, dass Sie ein Metamodell erzeugen können, so dass Sie keine Eigenschaften mit Strings angeben müssen. – EpicPandaForce

+1

Sie haben absolut Recht, QueryDSL ist eine Überlegung wert für typsichere Abfragen – iamiddy

+2

Nicht nur in QueryDsl, ich denke, Sie können ein Metamodell für die Wurzeln in Criteria API erstellen. Wie es im Blogpost http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/ scheint, scheinen sie ein Metamodell zu erstellen, das 'root.get (WorkInProgress_ .agileTeam) 'anstelle von' root.get ("agileTeam") '. String ist schließlich problematisch. – EpicPandaForce

14

Von Spring Data JPA 1.10 gibt es eine weitere Option für diese Query By Example ist. Ihr Repository sollte abgesehen von JpaRepository implementieren auch die QueryByExampleExecutor Schnittstelle, wo Sie Methoden erhalten wie:

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

Dann legen Sie die Example für wie 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 dynamisches que erhalten Ries.

Um Anpassung der String zu veredeln Attribute einen Blick auf ExampleMatcher ‚nehmen

Ein ExampleMatcher die like zum Beispiel ist ein Groß- und Kleinschreibung macht:

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

+4

Query By Example sieht sehr gut aus und passt zu vielen Fällen, aber leider hat es einen Nachteil, den ich erwähnen sollte. Es ist nicht möglich, Bedingungen wie WENIGER ALS, GRÖSSER ALS, ZWISCHEN usw. mit Hilfe der Abfrage nach Beispiel zu definieren. Sie können also nicht festlegen, dass ein bestimmtes Datum oder eine bestimmte Zahl in bestimmten Grenzen liegen soll. Es wird in der [offiziellen Dokumentation] (http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example) im Abschnitt Einschränkungen erwähnt: 'Unterstützt nur Starts/enthält/ends/regex passend für Strings und genau passend für andere Eigenschaftstypen'. – luke

+0

QueryByExample führt auch keinen Join durch, wenn es eins zu viele Beziehungsschiffe gibt. Wie zum Beispiel habe ich eine Entity POST und ich habe noch eine andere POSTCommements, wo ein Post viele Kommentare haben kann. In diesem Fall und sagen wir, ich suche POST mit einem Status aktiv einige Felder ich suche auf POST-Entity.It zunächst alle Beiträge mit den Kriterien und macht dann eine Abfrage auf das Kind-Objekt .. Wie PostComments wo PostID ist das .. wenn es wählt, lässt sagen, dass 100 Beiträge dann werden weitere 100 ausgewählt, um die untergeordneten Entitäten zu erhalten, was ineffizient zu sein scheint – swati

Verwandte Themen