JPA - 带左连接和计数的 TypedQuery

JPA - TypedQuery with Left Join and Count

我一直在尝试构建下面的 SQL,将 TypedQuery 与 Criteria Builder 结合使用:

select
        a.id,
        a.numeroAvisoPagamento,
        a.industria_id,
        a.varejo_id,
        a.dataAvisoPagamento,
        a.statusAvisoPagamento,
        a.dataUploadArquivo,
        a.dataImportacaoArquivo,
        a.dataConciliacaoAviso,
        count(c.avisoPagamento_id) as qtdeNotas, 
    from
        AvisoPagamento a  
    left join
        LoteAvisoPagamento l 
            ON l.codigoAviso = a.numeroAvisoPagamento 
    left join
        Cobranca c 
            ON c.avisoPagamento_id = l.id 
    where
        a.industria_id = ? 
        and a.varejo_id = ? 
        and a.numeroAvisoPagamento = ? 
        and a.dataAvisoPagamento between ? and ? 
    group by
        a.id,
        a.numeroAvisoPagamento,
        a.numeroAvisoPagamento,
        a.industria_id,
        a.varejo_id,
        a.dataAvisoPagamento,
        a.statusAvisoPagamento,
        a.dataUploadArquivo,
        a.dataImportacaoArquivo,
        a.dataConciliacaoAviso

型号

AvisoPagamento

@Entity(name = "AvisoPagamento")
public class AvisoPagamento {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@OneToMany(mappedBy = "avisoPagamento", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<CobrancaAvisoPagamento> cobrancas;

@OneToMany(mappedBy = "avisoPagamento", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@OrderBy("dataAcao ASC")
@JsonIgnore(accept={"AvisoPagamentoController.*"})
private List<LogAvisoPagamento> logAvisoPagamento;
}

LoteAvisoPagamento

@Entity(name = "LoteAvisoPagamento")
public class LoteAvisoPagamento {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@Column(nullable = false)
private Long codigoAviso;

}

科布兰卡

public class Cobranca {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@ManyToOne(fetch = FetchType.LAZY, optional = true)
@JoinColumn(name = "avisoPagamento_id")
@JsonIgnore(accept = { "CobrancaLoteController.listaCobrancas", "CobrancaAdmController.*",
        "ConciliacaoController.*", "CobrancaIndController.*" })
private LoteAvisoPagamento avisoPagamento;
}

我有两个问题:

  1. 实体 LoteAvisoPagamento 和 AvisoPagamento 之间没有关系,所以我 "forced" 联合两列:ON LoteAvisoPagamento.codigoAviso = AvisoPagamento.numeroAvisoPagamento。适用于 SQL 原生。
  2. 我需要计算来自 Table Cobranca 的记录,所以我使用了 count(c.avisoPagamento_id)

我想将此 SQL 重新发送到 TypedQuery 和 CriteriaBuilder,所以我尝试了这个:

//Create Criteria Builder
        final CriteriaBuilder builder = manager.getCriteriaBuilder();
        //Create CriteriaQuery da Classe AvisoPagamento
        final CriteriaQuery<AvisoPagamento> query = builder.createQuery(AvisoPagamento.class);
        //Create from
        final Root<AvisoPagamento> rootAviso = query.from(AvisoPagamento.class);

        //Left Join Lote Aviso Pagamento
        Root<LoteAvisoPagamento> rootLoteAviso = query.from(LoteAvisoPagamento.class);

        final Predicate predicateLeftJoin = builder.equal(rootAviso.get("numeroAvisoPagamento"), rootLoteAviso.get("codigoAviso"));

        //Conditions
        Predicate predicateAvisoPagamento = builder.and();

        //Join Selects
        Predicate criteria = builder.conjunction();
        criteria = builder.and(criteria, predicateAvisoPagamento);
        criteria = builder.and(criteria, predicateLeftJoin);

        //Passou a Industria
        if (industria != null){
             predicateAvisoPagamento = builder.and(predicateAvisoPagamento, builder.equal(rootAviso.get("industria"), industria));
        }
        //Passou o Varejo
        if (varejo != null){
            predicateAvisoPagamento = builder.and(predicateAvisoPagamento, builder.equal(rootAviso.get("varejo"), varejo));
        }
        //Passou o numero do Aviso
        if (numeroAviso != null){
            predicateAvisoPagamento = builder.and(predicateAvisoPagamento, builder.equal(rootAviso.get("numeroAvisoPagamento"), numeroAviso));
        }

        //Passou as Datas De e Ate
        if (dataDe != null && dataAte != null){
            predicateAvisoPagamento = builder.between(rootAviso.<Date>get("dataAvisoPagamento"), dataDe , dataAte);
        }


        //TypedQuery eh mais robusto, a checagem de tipo é feito na compilacao, eliminando alguns
        //tipos de erros
        final TypedQuery<AvisoPagamento> typedQuery = manager.createQuery(
            query.select(rootAviso).distinct(true)
            .where( criteria )
            .orderBy(builder.desc(rootAviso.get("dataConciliacaoAviso")))
        );


        //return List
        final List<AvisoPagamento> results = typedQuery.getResultList();

        return results;

然后 JPA 生成了这个 SQL:

select
        distinct avisopagam0_.id as id1_9_,
        avisopagam0_.arquivoFisico as arquivoF2_9_,
        avisopagam0_.dataAvisoPagamento as dataAvis3_9_,
        avisopagam0_.dataConciliacaoAviso as dataConc4_9_,
        avisopagam0_.dataImportacaoArquivo as dataImpo5_9_,
        avisopagam0_.dataUploadArquivo as dataUplo6_9_,
        avisopagam0_.industria_id as industri9_9_,
        avisopagam0_.numeroAvisoPagamento as numeroAv7_9_,
        avisopagam0_.statusAvisoPagamento as statusAv8_9_,
        avisopagam0_.usuario_id as usuario10_9_,
        avisopagam0_.varejo_id as varejo_11_9_ 
    from
        AvisoPagamento avisopagam0_ cross 
    join
        LoteAvisoPagamento loteavisop1_ 
    where
        1=1 
        and 1=1 
        and avisopagam0_.numeroAvisoPagamento=loteavisop1_.codigoAviso 
    order by dataAvisoPagamento desc

如何使用 TypedQuery 计算来自 Table Cobranca 的记录以及如何解决此问题:

 where
            1=1 
            and 1=1

很奇怪,我已经阅读了很多有关 TypedQuery 的内容,但我被卡住了

我认为 ON 子句仅适用于 JPA 2.1 版本中的关系。

所以直到现在你还不能使用

与两列合并:ON LoteAvisoPagamento.codigoAviso = AvisoPagamento.numeroAvisoPagamento

因为 JPA 2.1(最新版本)不支持。

因此它不适用于 Criteria 或 JPQL

Note: CROSS JOIN doesn't need ON clause, and that's why you are seeing it inside generated query also you can't do LEFT JOIN in criteria with that way you are using (impossible) it will be always generated as CROSS JOIN

INNER JOIN and LEFT JOIN needs a relation between entities

尝试下一个 JPQL 并测试它是否有效(我认为它不会起作用),它很简单但它应该与你想要的相似做(到目前为止至少在一个条件上相似)

SELECT aviso.id, aviso.numeroAvisoPagamento, loteAviso.id
FROM AvisoPagamento aviso
LEFT JOIN LoteAvisoPagamento loteAviso 
ON loteAviso.codigoAviso = aviso.numeroAvisoPagamento
WHERE aviso.numeroAvisoPagamento = :numeroAviso

:numeroAviso 替换为任何有效值,然后将其作为 entityManager.createQuery(在此处输入查询)

无论如何,我在我这边针对不同的实体但相同的逻辑进行了测试,结果出现了我预期的异常

注意: 我正在使用 JPAHibernate 提供者

这是我遇到的例外情况

Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Path expected for join!

所以它期待类似的东西(你的实体不支持)

LEFT JOIN aviso.loteAvisoPagamento loteAviso

在下一个查询中

SELECT aviso.id, aviso.numeroAvisoPagamento, loteAviso.id
FROM AvisoPagamento aviso
LEFT JOIN aviso.loteAvisoPagamento loteAviso
ON loteAviso.codigoAviso = aviso.numeroAvisoPagamento
WHERE aviso.numeroAvisoPagamento = :numeroAviso

由于@mibrahim.iti 告诉 LEFT JOIN 或 INNER 需要实体之间的关系,所以我使用 Native SQL 来做到这一点,这是我的最终解决方案:

//Sql Log
        final StringBuilder sqlLog = new StringBuilder();
        sqlLog.append("select l from LogAvisoPagamento l where l.avisoPagamento.id = :idAviso  order by l.dataAcao ");

        final Query queryLog = manager.createQuery(sqlLog.toString());

        //Sql Aviso
        final StringBuilder sql = new StringBuilder();
        sql.append("select a.id, ")
        .append(" a.numeroAvisoPagamento, ")
        .append(" a.industria_id, ")
        .append(" a.varejo_id, ")
        .append(" a.dataAvisoPagamento, ")
        .append(" a.statusAvisoPagamento, ")
        .append(" a.dataUploadArquivo, ")
        .append(" a.dataImportacaoArquivo, ")
        .append(" a.dataConciliacaoAviso, ")
        .append(" count(c.avisoPagamento_id) as qtdeNotas, ")
        .append(" r.valorTotalBruto ")
        .append(" from AvisoPagamento a ")
        .append(" left join ResumoAvisoPagamento r ON r.avisoPagamento_id = a.id ")
        .append(" left join LoteAvisoPagamento l ON l.codigoAviso = a.numeroAvisoPagamento")
        .append(" left join Cobranca c ON c.avisoPagamento_id = l.id");

        boolean where = false;

        //Passou a Industria
        if (industria != null){
            sql.append(" where a.industria_id = :idIndustria");
            where = true;
        }

        //Passou o Varejo
        if (varejo != null){
            if (!where){
                sql.append(" where a.varejo_id = :idVarejo");
                where = true;
            }else{
                sql.append(" and a.varejo_id = :idVarejo");
            }
        }

        //Passou o numero do Aviso
        if (numeroAviso != null){
            if (!where){
                sql.append(" where a.numeroAvisoPagamennto = :numeroAvisoPagamento");
                where = true;
            }else{
                sql.append(" and a.numeroAvisoPagamento = :numeroAvisoPagamento");
            }

        }

        //Passou as Datas De e Ate
        if (dataDe != null && dataAte != null){
            if (!where){
                where = true;
                sql.append(" where a.dataAvisoPagamento between :dataDe and :dataAte");
            }else{
                sql.append(" and a.dataAvisoPagamento between :dataDe and :dataAte");
            }
        }

        sql.append(" group by a.id, a.numeroAvisoPagamento, ")
        .append(" a.numeroAvisoPagamento, ")
        .append(" a.industria_id, ")
        .append(" a.varejo_id, ")
        .append(" a.dataAvisoPagamento, ")
        .append(" a.statusAvisoPagamento, ")
        .append(" a.dataUploadArquivo, ")
        .append(" a.dataImportacaoArquivo, ")
        .append(" a.dataConciliacaoAviso, ")
        .append(" r.valorTotalBruto ");

        final Query query = manager.createNativeQuery(sql.toString());

        //Passou a Industria
        if (industria != null){
            query.setParameter("idIndustria", industria.getId());
        }

        //Passou o Varejo
        if (varejo != null){
            query.setParameter("idVarejo", varejo.getId());
        }

        //Passou o numero do Aviso
        if (numeroAviso != null){
            query.setParameter("numeroAvisoPagamento", numeroAviso);
        }

        //Passou as Datas De e Ate
        if (dataDe != null && dataAte != null){
            query.setParameter("dataDe", dataDe);
            query.setParameter("dataAte", dataAte);
        }

        final List<AvisoPagamento> avisosPagamentos = new ArrayList<AvisoPagamento>();
        //Percorrendo os Registros
        for (final Object array : query.getResultList()){

            final Object[] arrayAviso = (Object[]) array;

            final ResumoAvisoPagamento resumo = new ResumoAvisoPagamento();
            resumo.setValorTotalBruto((BigDecimal) arrayAviso[10]);

            final AvisoPagamento avisoPagamento = new AvisoPagamento();

            final BigInteger id = new BigInteger(arrayAviso[0].toString());
            avisoPagamento.setId(id.longValue());
            avisoPagamento.setNumeroAvisoPagamento((String) arrayAviso[1]);
            avisoPagamento.setDataAvisoPagamento((Date) arrayAviso[4]);
            avisoPagamento.setStatusAvisoPagamento(StatusAvisoPagamento.valueOf(arrayAviso[5].toString()));
            avisoPagamento.setDataUploadArquivo((Date) arrayAviso[6]);
            avisoPagamento.setDataImportacaoArquivo((Date) arrayAviso[7]);
            avisoPagamento.setDataConciliacaoAviso((Date) arrayAviso[8]);

            final BigInteger qtde = new BigInteger(arrayAviso[9].toString());
            avisoPagamento.setQtdeNotas(qtde.intValue());

            queryLog.setParameter("idAviso", avisoPagamento.getId());
            //Get Log
            final List<LogAvisoPagamento> logs = queryLog.getResultList();

            avisoPagamento.setLogAvisoPagamento(logs);
            avisoPagamento.setResumoAvisoPagamento(resumo);
            avisosPagamentos.add(avisoPagamento);
        }

        return avisosPagamentos;