2015-03-26 17 views
9

Sto provando a utilizzare QueryDsl per scrivere una query con una clausola polimorfica.Polymorphic where clausola che utilizza QueryDsl

Dal momento che è un po 'difficile spiegare cosa voglio fare in astratto, sono cloned the spring-boot-sample-data-jpa project e lo ho modificato per mostrare un esempio di ciò che sto cercando di fare.

Ho these model classes, dove noterete che SpaHotel e SportHotel estendono l'entità Hotel.

Sto tentando di scrivere una query che restituisce tutte le città che contengono uno SpaHotel o uno SportHotel il cui sport principale è del tipo specificato.

Ho scritto un JPQL version of that query, che è un po 'brutto (non mi piace la parte sport is null per indicare che si tratta di un hotel Spa), ma sembra restituire quello che voglio.

Ma the QueryDsl version of that query non sembra funzionare:

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

mio test fallisce con:

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) 

Sembra che la mia domanda non restituisce "Montreal", che dovrebbe essere restituita, poiché contiene un SpaHotel.

Inoltre, mi chiedo se è normale che QueryDsl si tradurrebbe mia domanda in un cross join:

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=?) 

Le mie domande:

  1. Perché è che query non ritorno "Montreal", che contiene a SpaHotel?
  2. C'è un modo migliore di scrivere quella query?
  3. È normale che l'SQL generato esegua un cross-join? Non possiamo fare un left-join come faccio in JPQL?

risposta

3

la correttezza trasformazione della query JPQL

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

è

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

Nella tua query originale quest'uso proprietà

hotel.as(QSportHotel.class).mainSport 

fa sì che il cross join e vincoli la query a SportHotels.

Querydsl utilizza i join di sinistra impliciti solo per i percorsi utilizzati solo nell'ordine dalla parte della query, tutto causerà join interni impliciti.

+0

Molto interessante, non mi ero reso conto che si poteva usare 'as()' in un 'leftJoin()' per lanciare il percorso di un sottotipo. Risolve perfettamente il mio problema. Grazie mille per la risposta veloce e per lo sviluppo di QueryDsl. È meraviglioso. –