QueryDSL,Hibernate,JPA - 使用.fetchJoin()并在第一个SELECT中获取数据,那么为什么N + 1查询后呢?

时间:2017-10-06 16:27:57

标签: java hibernate jpa spring-data querydsl

我正在尝试查询具有到几个简单子实体的映射的实体列表(MyOrder s):每个MyOrder只与一个Store相关联,零个或多个Transaction s,最多一个Tender。生成的SELECT显示正确 - 它检索所有四个连接表中的所有列 - 但之后,每个MyOrder执行两个SELECT,一个用于Transaction,一个用于Tender

我正在使用QueryDSL 4.1.3,Spring Data 1.12,JPA 2.1和Hibernate 5.2。

在QueryDSL中,我的查询是:

... = new JPAQuery<MyOrder>(entityManager)
    .from(qMyOrder)
    .where(predicates)
    .join(qMyOrder.store).fetchJoin()
    .leftJoin(qMyOrder.transactions).fetchJoin()
    .leftJoin(qMyOrder.tender).fetchJoin()
    .orderBy(qMyOrder.orderId.asc())
    .transform(GroupBy
        .groupBy(qMyOrder.orderId)
        .list(qMyOrder));

执行为:

SELECT myorder0_.ord_id AS col_0_0_,
    myorder0_.ord_id AS col_1_0_,
    store1_.sto_id AS sto_id1_56_1_, -- store's PK
    transactions3_.trn_no AS trn_no1_61_2_, -- transaction's PK
    tender4_.tender_id AS pos_trn_1_48_3_, -- tender's PK
    myorder0_.ord_id AS ord_id1_39_0_,
    myorder0_.app_name AS app_name3_39_0_, -- {app_name, ord_num} is unique
    myorder0_.ord_num AS ord_num8_39_0_,
    myorder0_.sto_id AS sto_id17_39_0_,
    store1_.division_num AS div_nu2_56_1_,
    store1_.store_num AS store_nu29_56_1_,
    transactions3_.trn_cd AS trn_cd18_61_2_,
    tx2myOrder2_.app_name AS app_name3_7_0__, -- join table
    tx2myOrder2_.ord_no AS ord_no6_7_0__,
    tx2myOrder2_.trn_no AS trn_no1_7_0__,
    tender4_.app_name AS app_name2_48_3_,
    tender4_.ord_num AS ord_num5_48_3_,
    tender4_.tender_cd AS tender_cd_7_48_3_,
FROM data.MY_ORDER myorder0_
INNER JOIN data.STORE store1_ ON myorder0_.sto_id=store1_.sto_id
LEFT OUTER JOIN data.TX_to_MY_ORDER tx2myOrder2_
    ON myorder0_.app_name=tx2myOrder2_.app_name
    AND myorder0_.ord_num=tx2myOrder2_.ord_no
LEFT OUTER JOIN data.TRANSACTION transactions3_ ON tx2myOrder2_.trn_no=transactions3_.trn_no
LEFT OUTER JOIN data.TENDER tender4_
    ON myorder0_.app_name=tender4_.app_name
    AND myorder0_.ord_num=tender4_.ord_num
ORDER BY myorder0_.ord_id ASC

这几乎是我所期待的。 (为简洁起见,我删除了大部分数据列,但我需要的是SELECTed。)

在查询内存中的H2数据库(使用Spring的@DataJpaTest注释设置)时,执行此查询后,将针对Tender表进行第二次查询,但不会Transaction 。查询MS SQL数据库时,初始查询是相同的,但对TenderTransaction进行了其他查询。无法进行额外调用以加载Store

我发现的所有来源都表明.fetchJoin()应该足够了(例如Opinionated JPA with Query DSL;向上滚动几行),如果删除它们,确实只是初始查询从MY_ORDER中选择列。因此,似乎.fetchJoin()强制生成一次性获取所有边表的查询,但由于某种原因没有使用额外信息。真正奇怪的是,我确实在没有第二个查询的情况下看到Transaction数据被附加在我的H2准单元测试中(当且仅当我使用.fetchJoin()时),而不是在使用MS SQL时。

我尝试使用@Fetch(FetchMode.JOIN)注释实体映射,但是辅助查询仍然会触发。我怀疑可能有一个涉及扩展CrudRepository<>的解决方案,但我甚至没有成功获得正确的初始查询。

我的主要实体映射,使用Lombok的@Data注释,其他字段为了简洁而修剪。 (StoreTransactionTender都有@Id个简单的数字和字符串字段列映射,没有@Formula或{{1 s或其他任何东西。)

@OneToOne

我的问题是:为什么我会获得多余的SELECT,我怎么能不这样做呢?

1 个答案:

答案 0 :(得分:0)

我对答案有点太晚了,但是今天我也遇到了同样的问题。此响应可能对您没有帮助,但至少可以使我们免于头痛。

问题出在实体之间的关系上,而不是在查询中。我尝试使用QueryDSL,JPQL甚至本机SQL,但问题始终相同。

解决方案是通过在这些联接字段上使用@Id注释子类,从而使JPA相信关系存在。

基本上,您需要像这样设置Tender的id并从MyOrder使用它,就像它是正常的关系一样。

public class Tender {
    @EmbeddedId
    private TenderId id;
}

@Embeddable
public class TenderId {
    @Column(name = "APP_NAME")
    private String appName;

    @Column(name = "ORD_NUM")
    private String orderNumber;
}

Transaction实体也是如此。