2016-07-22 1 views
2

Mit Spring Daten jpa und Spezifikationen arbeiten, muss ich eine Filter/Suche-Funktion in Spring mvc implementieren. Das Backend empfängt ein Objekt (ReportTemplateBean), bei dem es sich im Wesentlichen um eine Bean mit einigen Feldern handelt, die die Filter im Frontend darstellen.Dynamische Abfragen in Spring Data JPA. Refactoring

public class ReportTemplateBean implements Serializable { 

private static final long serialVersionUID = -3915391620260021813L; 

private Long id; 

private String property; 

private String city; 

private String state; 

private String zipCode; 

private String propertyStatus; 

private String realEstateRep; 
//more code 

Wir haben den Controller

@RequestMapping(value = "/search", method = RequestMethod.GET) 
@ResponseBody 
public ReportBean search(@AuthenticationPrincipal ActiveUser activeUser, 
    @ModelAttribute("templateForm") ReportTemplateBean template, 
    Pageable pageable) throws GenericException { 
LOGGER.info("Pulling report requested"); 

ReportBean report = reportService.searchProperties(template, 
    pageable.getPageNumber(), pageable.getPageSize()); 

return report; 
} 

Der Service

@Override 
@Transactional(readOnly = true, timeout = 20) 
public ReportBean searchProperties(ReportTemplateBean template, 
    Integer pageNumber, Integer pageSize) throws GenericException, 
    TransactionTimedOutException { 
LOGGER.info("searchProperties({})", template); 

try { 
    // pageNumber = (pageNumber == null ? 0 : pageNumber); 
    // pageSize = (pageSize == null ? 10 : pageSize); 
    ReportTemplate t = reportTemplateMapper.beanToEntity(template); 
    List<PropertyBean> beans = new ArrayList<PropertyBean>(); 
    PropertySpecification spec = new PropertySpecification(t); 
    Page<Property> properties = propertyRepository.findAll(spec, 
     new PageRequest(pageNumber, pageSize, Sort.Direction.ASC, 
      "name")); 

Und dann baut er die Abfrage dynamisch, aber eine lange IF-Kette mit, dass ich es nicht mögen. Dies ist die Spezifikation.

@SuppressWarnings("unchecked") 
@Override 
public Predicate toPredicate(Root<Property> root, CriteriaQuery<?> query, 
    CriteriaBuilder cb) { 
Path<String> propertyName = root.get(Property_.name); 
Path<String> city = root.get(Property_.city); 
Path<String> state = root.get(Property_.state); 
Path<String> zipCode = root.get(Property_.zipCode); 
final List<Predicate> orPredicates = new ArrayList<Predicate>(); 
final List<Predicate> andPredicates = new ArrayList<Predicate>(); 
if (template.getProperty() != null 
    && template.getProperty().length() > 0) { 
    andPredicates.add(cb.equal(propertyName, template.getProperty())); 
} 
if (template.getCity() != null && template.getCity().length() > 0) { 
    andPredicates.add(cb.equal(city, template.getCity())); 
} 
if (template.getState() != null && template.getState().length() > 0) { 
    andPredicates.add(cb.equal(state, template.getState())); 
} 
if (template.getZipCode() != null && template.getZipCode().length() > 0) { 
    andPredicates.add(cb.equal(zipCode, template.getZipCode())); 
} 
if (template.getRealEstateRep() != null) { 
    Join<Property, User> pu = null; 
    if (query.getResultType().getName().equals("java.lang.Long")) { 
    pu = (Join<Property, User>) root.fetch(Property_.createdBy); 
    } else { 
    pu = root.join(Property_.createdBy); 
    } 
    Path<Long> userId = pu.get(User_.id); 
    andPredicates.add(cb.equal(userId, template.getRealEstateRep())); 
} 
if (template.getProjectType() != null 
    && template.getProjectType().length() > 0) { 
    Join<Property, Project> pp = null; 
    if (query.getResultType().getName().equals("java.lang.Long")) { 
    pp = root.join(Property_.projects); 
    } else { 
    pp = (Join<Property, Project>) root.fetch(Property_.projects); 
    } 
    Path<String> projectType = pp.get(Project_.projectName); 
    andPredicates.add(cb.equal(projectType, template.getProjectType())); 
} 
//more IF's 
return query.getRestriction(); 
} 

Wie Sie die Spezifikation feststellen kann, scheint hässlich und neben diesem SONAR beschwert sich über die zyklomatische Komplexität dieser Methode (was gut ist).

Also Frage ist, wie kann ich die Spezifikation (IFs) zu mehr OO-Code? Refaktor. Vielen Dank im Voraus. UPDATE - Ich möchte so etwas wie die neue Funktion in Spring Data JPA (Query by Example) Es scheint, verwenden/implementieren, wenn Sie eine Bohne die ExampleMatcher Klasse übergeben wird den Nullwert in den Bohnenfelder ignorieren, die ist fast, was Ich suche nach. Ignoriere Null- und Leerwerte.

+0

Möchten Sie eine Lösung nur mit Spezifikation? Ich hatte ein ähnliches Problem, aber ich löste es Schreiben der Abfrage mit @ Query Annotation mit optionalen Parametern in meinem Repository – amicoderozer

+0

Wäre schön mit Spezifikation, aber nicht zwingend, kann ich einen Blick auf Ihre Lösung? – TheProgrammer

Antwort

0

Ich schreibe meine Lösung, um Ihnen eine andere Option zu geben, aber wie ich im Kommentar sage ich nicht Spezifikation verwendet, und ich bin gespannt, ob jemand eine andere Möglichkeit, dynamische Abfragen im Frühjahr jpa.

Sie könnten Ihre eigene Abfrage mit der @Query Annotation innerhalb einer @Repository Schnittstelle schreiben. In Ihrem Fall (unter der Annahme ReportTemplateBean ist Ihr Unternehmen und seine Primärschlüssel ist von Lange Typ), es wäre wie:

@Repository 
public interface ReportTemplateRepo extends JpaRepository<ReportTemplateBean, Long>{ 

    @Query("SELECT rb FROM ReportBeanTemplate rb JOIN ExampleTable et WHERE et.idTemplate = rb.id AND (:id is null OR :id = rb.id) AND (:city is null OR :city = rb.city) AND (:state is null OR :state = rb.state)") 
    public List<ReportTemplateBean> findTemplates(@Param("id") Long id, @Param("city") String city, @Param("state") String state); 
} 

Sie alle Parameter hinzufügen können, die Sie wollen, übergeben Sie es als null, wenn Sie Rufen Sie die Methode auf.

Beispiel Methodenaufruf (in Ihnen Service-Klasse):

@Autowire 
ReportTemplateRepo templateRepo; 

public void invocation(ReportTemplateBean template){ 
    List<ReportTemplateBean> templateRepo.findTemplates(
     template.getId(), template.getCity(), template.getState()); 
    } 

Dies ist die einzige Art, wie ich diese Art der Abfrage tun gefunden.

+0

Wie sieht es aus, wenn Sie Beziehungen haben? Ich meine, meine Bohne hat OneToOne und OneToMany Beziehungen. Kann Ihre Lösung das schaffen? Wie verwalten dann wann ist null? Vielen Dank. – TheProgrammer

+0

Ich habe nicht getestet, weil ich noch nicht OneToMany Beziehung in meinem Projekt habe, aber werfen Sie einen Blick auf diese Frage http://StackOverflow.com/Questions/38614247/default-Sort-on-Aspring-Data -jpa-repository-method-with-custom-query-und-pageab. Er verwendet Beziehung und Abfrage mit dynamischen Parametern und er sagt, dass es funktioniert. Aber ich weiß nicht, wie man diese Art von Beziehung zu verwalten, wenn null ist – amicoderozer

+0

Dank @amicoderozer, diese Info ist sehr nützlich. – TheProgrammer