2015-03-26 11 views
9

Ich versuche QueryDsl zu verwenden, um eine Abfrage mit einem polymorphen where-Klausel zu schreiben.Polymorphe wo Klausel QueryDsl

Da es ein wenig schwierig ist zu erklären, was ich in der Zusammenfassung tun möchte, habe ich cloned the spring-boot-sample-data-jpa project geändert und es um ein Beispiel zu zeigen, was ich versuche zu tun.

Ich habe these model classes, wo Sie beachten Sie werden feststellen, dass SpaHotel und SportHotel die Hotel Einheit erstrecken.

Ich versuche, eine Abfrage zu schreiben, die alle Städte zurückgibt, die entweder SpaHotel oder SportHotel enthalten, deren Hauptsport vom gegebenen Typ ist.

Ich schrieb eine JPQL version of that query, die ein wenig hässlich ist (ich mag nicht die sport is null Teil, um anzuzeigen, dass es ein Spa-Hotel ist), aber scheint zurück, was ich will.

Aber the QueryDsl version of that query nicht zu funktionieren scheint:

public List<City> findAllCitiesWithSpaOrSportHotelQueryDsl(SportType sportType) { 
    QCity city = QCity.city; 
    QHotel hotel = QHotel.hotel; 

    return queryFactory.from(city) 
     .join(city.hotels, hotel) 
     .where(
      hotel.instanceOf(SpaHotel.class).or(
       hotel.as(QSportHotel.class).mainSport.type.eq(sportType) 
     ) 
    ).list(city); 
} 

Mein test schlägt mit:

test_findAllCitiesWithSpaOrSportHotelQueryDsl(sample.data.jpa.service.CityRepositoryIntegrationTests) Time elapsed: 0.082 sec <<< FAILURE! 
java.lang.AssertionError: 
Expected: iterable over [<Montreal,Canada>, <Aspen,United States>, <'Neuchatel','Switzerland'>] in any order 
    but: No item matches: <Montreal,Canada> in [<Aspen,United States>, <'Neuchatel','Switzerland'>] 
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20) 
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8) 
    at sample.data.jpa.service.CityRepositoryIntegrationTests.test_findAllCitiesWithSpaOrSportHotelQueryDsl(CityRepositoryIntegrationTests.java:95) 

Es ist wie meine Frage scheint nicht zurück "Montreal", die zurückgegeben werden sollen, da es ein SpaHotel enthält.

Auch frage ich mich, ob es normal ist, dass QueryDsl würde meine Abfrage in eine Cross-Join übersetzen:

select city0_.id as id1_0_, city0_.country as country2_0_, city0_.name as name3_0_ 
from city city0_ 
inner join hotel hotels1_ 
on city0_.id=hotels1_.city_id 
cross join sport sport2_ 
where hotels1_.main_sport_id=sport2_.id and (hotels1_.type=? or sport2_.type=?) 

Meine Fragen:

  1. Warum ist diese Abfrage nicht "Montreal" Rückkehr, die enthält ein Spahotel?
  2. Gibt es eine bessere Art und Weise, dass die Abfrage des Schreibens?
  3. Ist es normal, dass die erzeugte SQL tut eine Quer beitreten? Können wir nicht einen Links-Join erstellen, wie ich es in JPQL mache?

Antwort

3

Die korrekte Umsetzung Ihrer JPQL Abfrage

String jpql = "select c from City c" 
    + " join c.hotels hotel" 
    + " left join hotel.mainSport sport" 
    + " where (sport is null or sport.type = :sportType)"; 

ist

return queryFactory.from(city) 
    .join(city.hotels, hotel) 
    .leftJoin(hotel.as(QSportHotel.class).mainSport, sport) 
    .where(sport.isNull().or(sport.type.eq(sportType))) 
    .list(city); 

In Ihrer ursprünglichen Abfrage diese Eigenschaft Nutzung

hotel.as(QSportHotel.class).mainSport 

das Kreuz verbinden und Einschränkungen verursacht die Abfrage zu Sporthotels.

Querydsl verwenden implizite links nur für Pfade verbinden, die nur im orderBy Teil der Abfrage verwendet werden, alles, was implizit verursacht innere verbindet.

+0

Sehr interessant, ich wusste nicht, dass Sie 'wie()' in a 'leftJoin()' werfen den Pfad zu einem Subtyp nutzen könnten. Es löst mein Problem perfekt. Vielen Dank für die schnelle Antwort und für die Entwicklung von QueryDsl. Es ist toll. –