【问题标题】:JPA Projections query with GROUP BY not working with null fields使用 GROUP BY 的 JPA Projections 查询不使用空字段
【发布时间】:2020-12-14 21:14:52
【问题描述】:

我想复制一个在 MySQL 和 H2 中都能正常运行的查询:

SELECT 
    pedido_linha.produto_id AS material,
    COALESCE(pedido_linha.unidade_medida_id, "UN") AS uom, 
    pedido_linha.data_pedido  AS referenceDate,
    SUM(COALESCE(pedido_linha.quantidade, 0)) AS totalQuantity,
    SUM(COALESCE(pedido_linha.valor_total, 0)) AS totalGross,
    SUM(COALESCE(pedido_linha.valor_total, 0) - COALESCE(pedido_linha.valor_descontos, 0) - 
        COALESCE(pedido_linha.valor_impostos, 0)) AS totalNet,
    SUM(COALESCE(pedido_linha.valor_custo, 0)) AS totalCogs
FROM pedido_linha
GROUP BY pedido_linha.produto_id, pedido_linha.data_pedido, COALESCE(pedido_linha.unidade_medida_id, "UN");

例如,查询在 MySQL 中运行良好,即使某些 Group By 字段为空:

已尝试在 JPA 封闭投影中提取分组结果:

public interface AggregatedByMaterialUOMDate {

Produto getMaterial();
UnidadeMedida getUom();
LocalDate getReferenceDate();

Float getTotalQuantity();
Float getTotalGross();
Float getTotalNet();
Float getTotalCogs();

}

连同存储库查询:

@Repository
public interface PedidoLinhaRepository extends JpaRepository<PedidoLinha, PedidoLinha.PedidoLinhaCompositeKey> {

    @Query("SELECT pl.produto AS material, "
            + "COALESCE(pl.unidadeMedida, :unidadeMedidaPadrao) AS uom, "
            + "pl.dataPedido  AS referenceDate, "
            + "SUM(COALESCE(pl.quantidade, 0)) AS totalQuantity, "
            + "SUM(COALESCE(pl.valorTotal, 0)) AS totalGross, "
            + "SUM(COALESCE(pl.valorTotal, 0) - COALESCE(pl.valorDescontos, 0) - COALESCE(pl.valorImpostos, 0)) AS totalNet, "
            + "SUM(COALESCE(pl.valorCusto, 0)) AS totalCogs "
            + "FROM PedidoLinha pl "
            + "GROUP BY pl.produto, pl.dataPedido, COALESCE(pl.unidadeMedida, :unidadeMedidaPadrao)")
    List<AggregatedByMaterialUOMDate> consolidatedSelloutByMaterialUOMDayAtLocation(@Param("unidadeMedidaPadrao") UnidadeMedida unidadeMedidaPadrao);
}

上面的 JPQL 查询返回一个包含 0 个元素的列表,因此与前面显示的本机查询不一致。

我们尝试将 COALESCE(pl.unidadeMedida, :unidadeMedidaPadrao) 更改为简单的 pl.unidadeMedida(在 SELECT 和 GROUP BY 子句中),但没有成功。

只有在从两个子句中完全删除对 pl.unidadeMedida 的所有引用时,查询才能成功返回所有值。

下面是 PedidoLinha(在查询中由 pl 引用)类的示例,显示了对 UnidadeMedida 的引用:

@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode(of = "pedidoLinhaCompositeKey")
@Entity
public class PedidoLinha {

    @EmbeddedId
    @NonNull // torna campo obrigatório e parâmetro do construtor gerado pelo @Data (lombok)
    private PedidoLinhaCompositeKey pedidoLinhaCompositeKey;

    @Data // lombok: @ToString, @EqualsAndHashCode, @Getter on all fields @Setter on all non-final fields, and @RequiredArgsConstructor
    @NoArgsConstructor
    @RequiredArgsConstructor
    @Embeddable
    @EqualsAndHashCode
    public static class PedidoLinhaCompositeKey implements Serializable {

        @NonNull // torna campo obrigatório e parâmetro do construtor gerado pelo @Data (lombok)
        private String id;
        
        @ManyToOne(optional = false, fetch = FetchType.LAZY)
        @NonNull // torna campo obrigatório e parâmetro do construtor gerado pelo @Data (lombok)
        private Pedido pedido;
                
    }
    
    @ManyToOne(optional = false)
    private Produto produto;
    
    @ManyToOne
    private UnidadeMedida unidadeMedida;

    public UnidadeMedida getUnidadeMedida() {
        return (unidadeMedida == null) ? new UnidadeMedida("UN") : unidadeMedida;
    }

    // rest of the entity code
}

UnidadeMedida 课程

@Getter
@Setter
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
@Entity
public class UnidadeMedida {

    @Id
    private String id;

    private String descricao;
    
    public UnidadeMedida(String id) {
        this.id = id;
    }
    
}

会发生什么?我相信这可能是一个 Hibernate 错误,但找不到任何关于此问题的参考。

【问题讨论】:

  • 能否请您出示UnidadeMedida 班级。
  • 我已经包含了这个类:我们在所有实体中都使用了 Lombok,到目前为止没有任何问题。刚试过包括@AllArgsConstructor,没有效果
  • 如果您希望有人帮助您,您需要提供所有必需的信息。未提供以下定义:ProductoPedido// rest of the entity code 之类的评论无济于事。一个想要帮助你的人需要自己创造一些东西,并且可能会错过一些重要的细节。
  • @dotore,感谢您的提示。最后,问题与依赖实体无关,而是与 JPQL 查询转换为 SQL 代码的方式有关

标签: spring hibernate spring-data-jpa spring-data


【解决方案1】:

原来 JPQL 查询没有产生任何记录的关键原因是在数据库调用中进行隐式连接的方式。

以下代码表示运行上述 JPQL 查询后在数据库 (How to show the last queries executed on MySQL?) 中执行的实际 SQL 代码:

SELECT 
    pedidolinh0_.produto_id as col_0_0_, 
    coalesce(pedidolinh0_.unidade_medida_id, 'UN') as col_1_0_, 
    pedidolinh0_.data_pedido as col_2_0_, 
    sum(coalesce(pedidolinh0_.quantidade, 0)) as col_3_0_, 
    sum(coalesce(pedidolinh0_.valor_total, 0)) as col_4_0_, 
    sum(coalesce(pedidolinh0_.valor_total, 0)-coalesce(pedidolinh0_.valor_descontos, 0)-coalesce(pedidolinh0_.valor_impostos, 0)) as col_5_0_, 
    sum(coalesce(pedidolinh0_.valor_custo, 0)) as col_6_0_, 
    produto1_.id as id1_88_, produto1_.ativo as ativo2_88_, 
    produto1_.data_descontinuacao as data_des3_88_, 
    produto1_.data_introducao as data_int4_88_, 
    produto1_.descricao as descrica5_88_, 
    produto1_.ean as ean6_88_, 
    produto1_.lote_minimo_requisicoes as lote_min7_88_, 
    produto1_.multiplo_requisicoes as multiplo8_88_, 
    produto1_.ncm as ncm9_88_, 
    produto1_.unidade_medida_padrao_id as unidade10_88_ 
from pedido_linha pedidolinh0_ 
    inner join produto produto1_ on pedidolinh0_.produto_id=produto1_.id 
    cross join unidade_medida unidademed2_ 
    cross join pedido pedido3_ 
where pedidolinh0_.unidade_medida_id=unidademed2_.id
group by 
    pedidolinh0_.produto_id , 
    pedidolinh0_.data_pedido , 
    coalesce(pedidolinh0_.unidade_medida_id, 'UN')

问题是到 SQL 的转换自动在查询中包含 cross join + WHERE 子句 pedidolinh0_.unidade_medida_id=unidademed2_.id,有效地忽略了该字段为空的每个实例。 即使coalescing 应用在select 语句中,这些记录已经被where 子句忽略,因此没有任何作用。

解决方法是在 JPQL 查询中强制进行左连接,如下所示:

SELECT 
    pl.produto AS material,
    COALESCE(um, :unidadeMedidaPadrao) AS uom,
    pl.dataPedido AS referenceDate,
    SUM(COALESCE(pl.quantidade, 0)) AS totalQuantity,
    SUM(COALESCE(pl.valorTotal, 0)) AS totalGross,
    SUM(COALESCE(pl.valorTotal, 0) - COALESCE(pl.valorDescontos, 0) - COALESCE(pl.valorImpostos, 0)) AS totalNet,
    SUM(COALESCE(pl.valorCusto, 0)) AS totalCogs
FROM PedidoLinha pl
    LEFT JOIN pl.unidadeMedida um
GROUP BY pl.produto, pl.dataPedido, pl.unidadeMedida

因此,cross join 语句在数据库中执行的最终 SQL 中被 left outer join 替换,并且不包含在 where 子句中。

任何关于为什么在 Hibernate 中做出此设计决策(使用交叉联接 + where 语句而不是左联接)的见解将不胜感激。

【讨论】:

    猜你喜欢
    • 2011-03-27
    • 2022-01-26
    • 2016-10-11
    • 2015-07-25
    • 1970-01-01
    • 1970-01-01
    • 2021-04-05
    • 2021-08-10
    • 1970-01-01
    相关资源
    最近更新 更多