Olá, Pablo. Como vai?
Excelente contribuição! Esse é um dos erros mais clássicos e assustadores para quem está começando a construir relatórios agregados com JPA/Hibernate e MySQL. O seu post é de utilidade pública para a comunidade da Alura.
O erro only_full_group_by acontece por conta de uma trava de segurança estrita que o MySQL implementa (por padrão a partir da versão 5.7). Ela exige uma consistência matemática rígida: **toda coluna que estiver no SELECT que não seja uma função de agregação (como SUM, AVG, MAX) deve obrigatoriamente constar na cláusula GROUP BY**.
A sua solução reescrevendo a JPQL ficou perfeita e seguiu a melhor abordagem possível! Em ambientes corporativos reais, o desenvolvedor raramente tem permissão de administrador (root) para alterar o sql_mode diretamente na configuração do servidor de banco de dados. Resolver isso refinando a query é a melhor prática de engenharia de software.
Para ajudar quem estiver lendo o seu tópico a entender a lógica por trás da sua correção, vale a pena visualizar o que o banco de dados tenta fazer por baixo dos panos. Imagine que temos duas tabelas relacionadas (Pedidos e Itens) sendo unidas e agrupadas para gerar a agregação:
Se você tentar colocar uma coluna como item.quantidade direto no SELECT sem o SUM(), o MySQL reclama porque, ao agrupar tudo pelo nome do produto, ele não sabe qual linha de quantidade exibir (a primeira? A última?). Ao aplicar a função de agregação SUM(item.quantidade) e ordenar pelo mesmo SUM(item.quantidade) DESC, você deu uma instrução matemática clara para o motor do banco de dados, eliminando a ambiguidade.
Outro ponto fantástico do seu código corrigido foi o uso correto do MAX(pedido.data). Em relatórios, se queremos saber a data da última venda daquele produto dentro do agrupamento, precisamos encapsulá-la em uma função agregadora como o MAX.
Uma dica extra de JPA para evoluir o seu código:
Como você está no curso de consultas avançadas, trabalhar com um retorno de List<Object[]> funciona perfeitamente, mas extrair os dados por índices (row[0], row[1]) pode ser um pouco desconfortável no ecossistema Java.
Para deixar essa solução ainda mais elegante, você pode transformar esse resultado em um DTO (Data Transfer Object) direto na JPQL usando o recurso de projeção com o construtor da classe.
- Crie uma classe simples para o relatório (pode ser até um
record se estiver no Java 16+):
public record RelatorioVendasVo(String nomeProduto, Long quantidadeVendida, LocalDate dataUltimaVenda) {}
- Ajuste a sua JPQL usando a palavra-chave
new:
public List<RelatorioVendasVo> relatorioDeVendas(){
String jpql = "SELECT new br.com.sualoja.vo.RelatorioVendasVo(produto.nome, SUM(item.quantidade), MAX(pedido.data)) " +
"FROM Pedido pedido " +
"JOIN pedido.itens item " +
"JOIN item.produto produto " +
"GROUP BY produto.nome " +
"ORDER BY SUM(item.quantidade) DESC";
return connEm.createQuery(jpql, RelatorioVendasVo.class).getResultList();
}
Dessa forma, em vez de um array de objetos genéricos, você recebe uma lista tipada, linda e pronta para ser consumida pela sua aplicação ou API!
Parabéns pelo diagnóstico preciso do erro e por compartilhar a solução com a comunidade!
Espero que possa ter lhe ajudado!