java - the - Datenbankzeilen mit Spring-Data-jpa und Spring-Mvc filtern



the given jpa entity graph is not dynamic (1)

Für den Anfang sollten Sie aufhören, @RequestParam und alle Ihre Suchfelder in ein Objekt zu legen (möglicherweise das Travel-Objekt dafür wiederverwenden). Dann haben Sie zwei Optionen, mit denen Sie dynamisch eine Abfrage erstellen können

  1. Verwenden Sie den JpaSpecificationExecutor und schreiben Sie eine Specification
  2. Verwenden Sie den QueryDslPredicateExecutor und verwenden Sie QueryDSL , um ein Prädikat zu schreiben.

Verwenden von JpaSpecificationExecutor

JpaSpecificationExecutor Sie TravelRepository den JpaSpecificationExecutor zu Ihrem TravelRepository hinzu. JpaSpecificationExecutor Sie eine findAll(Specification) -Methode und Sie können Ihre benutzerdefinierten Finder-Methoden entfernen.

public interface TravelRepository extends JpaRepository<Travel, Long>, JpaSpecificationExecutor<Travel> {}

Dann können Sie eine Methode in Ihrem Repository erstellen, die eine Specification die im Grunde die Abfrage erstellt. Sehen Sie hierzu die JPA- documentation Spring Data.

Das einzige, was Sie tun müssen, ist eine Klasse zu erstellen, die die Specification implementiert und die Abfrage basierend auf den verfügbaren Feldern erstellt. Die Abfrage wird mithilfe der JPA-Kriterien-API-Verknüpfung erstellt.

public class TravelSpecification implements Specification<Travel> {

    private final Travel criteria;

    public TravelSpecification(Travel criteria) {
        this.criteria=criteria;
    }

    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        // create query/predicate here.
    }
}

Und schließlich müssen Sie Ihren Controller ändern, um die neue findAll Methode zu verwenden (ich habe mir die Freiheit genommen, es ein bisschen aufzuräumen).

@RequestMapping("/search")  
public String search(@ModelAttribute Travel search, Pageable pageable, Model model) {  
Specification<Travel> spec = new TravelSpecification(search);
    Page<Travel> travels  = travelRep.findAll(spec, pageable);
    model.addObject("page", new PageWrapper(travels, "/search"));
    return "travels/list";
}

Verwenden von QueryDslPredicateExecutor

QueryDslPredicateExecutor Sie TravelRepository den QueryDslPredicateExecutor zu Ihrem TravelRepository hinzu. QueryDslPredicateExecutor Sie eine findAll(Predicate) -Methode und Sie können Ihre benutzerdefinierten Finder-Methoden entfernen.

public interface TravelRepository extends JpaRepository<Travel, Long>, QueryDslPredicateExecutor<Travel> {}

Als Nächstes würden Sie eine Servicemethode implementieren, die das Travel Objekt zum Erstellen eines Prädikats mithilfe von QueryDSL verwendet.

@Service
@Transactional
public class TravelService {

    private final TravelRepository travels;

    public TravelService(TravelRepository travels) {
        this.travels=travels;
    }

    public Iterable<Travel> search(Travel criteria) {

        BooleanExpression predicate = QTravel.travel...
        return travels.findAll(predicate);
    }
}

Siehe auch diesen Moorpfosten .

Ich habe ein Spring-Mvc-Projekt, das Spring-Data-Jpa für den Datenzugriff verwendet. Ich habe ein Domain-Objekt namens Travel dem ich erlauben möchte, dass der Endbenutzer eine Anzahl von Filtern anwendet.

Dafür habe ich den folgenden Controller implementiert:

@Autowired
private TravelRepository travelRep;

@RequestMapping("/search")  
public ModelAndView search(
        @RequestParam(required= false, defaultValue="") String lastName, 
        Pageable pageable) {  
    ModelAndView mav = new ModelAndView("travels/list");  
    Page<Travel> travels  = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
    PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");
    mav.addObject("page", page);
    mav.addObject("lastName", lastName);
    return mav;
}

Dies funktioniert lastName : Der Benutzer hat ein Formular mit einem lastName Eingabefeld, mit dem die Reisen gefiltert werden können.

Jenseits von lastName hat mein Travel Domain-Objekt viel mehr Attribute, nach denen ich filtern möchte. Ich denke, wenn diese Attribute alle Zeichenfolgen wären, könnte ich sie als @RequestParam s hinzufügen und eine Spring-Data-JPA-Methode hinzufügen, um diese abzufragen. Zum Beispiel würde ich eine Methode findByLastNameLikeAndFirstNameLikeAndShipNameLike hinzufügen.

Ich weiß jedoch nicht, wie ich es machen soll, wenn ich nach Fremdschlüsseln filtern muss. Meine Travel hat also ein period , das ein Fremdschlüssel für das Domain-Objekt Period ist, das ich als Drop-Down für den Benutzer haben muss, um den Period auszuwählen.

Was ich tun möchte, ist, wenn der Zeitraum null ist. Ich möchte alle Reisen, die durch den Nachnamen gefiltert wurden, abrufen, und wenn der Zeitraum nicht null ist, möchte ich alle Reisen für diesen Zeitraum abrufen, gefiltert nach dem Nachnamen.

Ich weiß, dass dies getan werden kann, wenn ich zwei Methoden in meinem Repository implementiere und einen if zu meinem Controller verwende:

public ModelAndView search(
       @RequestParam(required= false, defaultValue="") String lastName,
       @RequestParam(required= false, defaultValue=null) Period period, 
       Pageable pageable) {  
  ModelAndView mav = new ModelAndView("travels/list");  
  Page travels = null;
  if(period==null) {
    travels  = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
  } else {
    travels  = travelRep.findByPeriodAndLastNameLike(period,"%"+lastName+"%", pageable);
  }
  mav.addObject("page", page);
  mav.addObject("period", period);
  mav.addObject("lastName", lastName);
  return mav;
}

Gibt es eine Möglichkeit dies zu tun ohne das if zu benutzen? Meine Reise hat nicht nur den Zeitraum, sondern auch andere Attribute, die über Drop-Downs gefiltert werden müssen !! Wie Sie verstehen, würde die Komplexität exponentiell steigen, wenn ich mehr Dropdowns verwenden muss, weil alle Kombinationen berücksichtigt werden müssen :(

Update 03/12/13 : Fortsetzung von M. Deinum hervorragende Antwort, und nach der tatsächlichen Implementierung, möchte ich einige Kommentare für die Vollständigkeit der Frage / asnwer zur Verfügung stellen:

  1. Anstatt JpaSpecificationExecutor implementieren, JpaSpecificationExecutor Sie JpaSpecificationExecutor<Travel> implementieren, um Typprüfungswarnungen zu vermeiden.

  2. Bitte werfen Sie einen Blick auf Kostjas ausgezeichnete Antwort auf diese Frage. Wirklich dynamischer JPA CriteriaBuilder, da Sie dies implementieren müssen, wenn Sie korrekte Filter haben wollen.

  3. Die beste Dokumentation, die ich für die Kriterien-API finden konnte, war http://www.ibm.com/developerworks/library/j-typesafejpa/ . Das ist eine ziemlich lange Lektüre, aber ich kann es nur empfehlen - nach dem Lesen wurden die meisten Fragen für Root und CriteriaBuilder beantwortet :)

  4. Die Wiederverwendung des Travel Objekts war nicht möglich, da es verschiedene andere Objekte enthielt (die auch andere Objekte enthielten), nach denen ich mit Like suchen musste. Stattdessen verwendete ich ein TravelSearch Objekt, das die Felder enthielt, nach denen ich suchen musste.

Update 10/05/15 : Auf folgende Anfrage von @ priyank habe ich das TravelSearch-Objekt implementiert:

public class TravelSearch {
    private String lastName;
    private School school;
    private Period period;
    private String companyName;
    private TravelTypeEnum travelType;
    private TravelStatusEnum travelStatus;
    // Setters + Getters
}

Dieses Objekt wurde von TravelSpecification verwendet (der meiste Code ist domänenspezifisch, aber ich lasse ihn als Beispiel hier):

public class TravelSpecification implements Specification<Travel> {
    private TravelSearch criteria;


    public TravelSpecification(TravelSearch ts) {
        criteria= ts;
    }

    @Override
    public Predicate toPredicate(Root<Travel> root, CriteriaQuery<?> query, 
            CriteriaBuilder cb) {
        Join<Travel, Candidacy> o = root.join(Travel_.candidacy);

        Path<Candidacy> candidacy = root.get(Travel_.candidacy);
        Path<Student> student = candidacy.get(Candidacy_.student);
        Path<String> lastName = student.get(Student_.lastName);
        Path<School> school = student.get(Student_.school);

        Path<Period> period = candidacy.get(Candidacy_.period);
        Path<TravelStatusEnum> travelStatus = root.get(Travel_.travelStatus);
        Path<TravelTypeEnum> travelType = root.get(Travel_.travelType);

        Path<Company> company = root.get(Travel_.company);
        Path<String> companyName = company.get(Company_.name);

        final List<Predicate> predicates = new ArrayList<Predicate>();
        if(criteria.getSchool()!=null) {
            predicates.add(cb.equal(school, criteria.getSchool()));
        }
        if(criteria.getCompanyName()!=null) {
            predicates.add(cb.like(companyName, "%"+criteria.getCompanyName()+"%"));
        }
        if(criteria.getPeriod()!=null) {
            predicates.add(cb.equal(period, criteria.getPeriod()));
        }
        if(criteria.getTravelStatus()!=null) {
            predicates.add(cb.equal(travelStatus, criteria.getTravelStatus()));
        }
        if(criteria.getTravelType()!=null) {
            predicates.add(cb.equal(travelType, criteria.getTravelType()));
        }
        if(criteria.getLastName()!=null ) {
            predicates.add(cb.like(lastName, "%"+criteria.getLastName()+"%"));
        }
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));

    }
}

Endlich, hier ist meine Suchmethode:

@RequestMapping("/search")  
public ModelAndView search(
        @ModelAttribute TravelSearch travelSearch,
        Pageable pageable) {  
    ModelAndView mav = new ModelAndView("travels/list");  

    TravelSpecification tspec = new TravelSpecification(travelSearch);

    Page<Travel> travels  = travelRep.findAll(tspec, pageable);

    PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");

    mav.addObject(travelSearch);

    mav.addObject("page", page);
    mav.addObject("schools", schoolRep.findAll() );
    mav.addObject("periods", periodRep.findAll() );
    mav.addObject("travelTypes", TravelTypeEnum.values());
    mav.addObject("travelStatuses", TravelStatusEnum.values());
    return mav;
}

Hoffe, ich habe geholfen!





spring-data-jpa