使用 QueryDsl 的多态 where 子句

Posted

技术标签:

【中文标题】使用 QueryDsl 的多态 where 子句【英文标题】:Polymorphic where clause using QueryDsl 【发布时间】:2015-03-26 11:18:54 【问题描述】:

我正在尝试使用 QueryDsl 编写带有多态 where 子句的查询。

由于在摘要中解释我想要做什么有点困难,我cloned the spring-boot-sample-data-jpa project 并对其进行了修改以显示我正在尝试做的示例。

我有these model classes,您会注意到SpaHotelSportHotel 扩展了Hotel 实体。

我正在尝试编写一个查询,返回包含SpaHotelSportHotel 的所有城市,其主要运动属于给定类型。

我写了一个JPQL version of that query,有点丑(我不喜欢sport is null 部分表示它是一家温泉酒店),但似乎返回了我想要的。

但the QueryDsl version of that query 似乎不起作用:

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

我的test 失败:

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)

我的查询似乎没有返回应该返回的“蒙特利尔”,因为它包含一个 SpaHotel。

另外,我想知道 QueryDsl 将我的查询转换为交叉连接是否正常:

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

我的问题:

    为什么该查询不返回“蒙特利尔”,其中包含一个 温泉酒店? 有没有更好的方法来编写该查询? 生成的 SQL 进行交叉连接是否正常?我们不能像在 JPQL 中那样做一个左连接吗?

【问题讨论】:

【参考方案1】:

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

在您的原始查询中,此属性的用法

hotel.as(QSportHotel.class).mainSport

导致交叉连接并将查询约束到 SportHotels。

Querydsl 仅对仅在查询的 orderBy 部分中使用的路径使用隐式左连接,一切都会导致隐式内连接。

【讨论】:

非常有趣,我没有意识到您可以在leftJoin() 中使用as() 将路径转换为子类型。它完美地解决了我的问题。非常感谢您的快速回答以及开发 QueryDsl。太棒了。

以上是关于使用 QueryDsl 的多态 where 子句的主要内容,如果未能解决你的问题,请参考以下文章

多态关系中的 where 子句

在where子句中查询dsl案例

QueryDsl - 如何使用 maven 创建 Q 类?

如何在 WHERE 子句(MySQL)中使用 VIEW?

querydsl生成的q源码没有正确导入

始终选择表中所有记录的 WHERE 子句