Projeções JPA/Hibernate

JPA/Hibernate Projections

1. Visão geral

Neste tutorial, aprenderemoshow to project entity properties using JPA and Hibernate.

2. A entidade

Primeiro, vamos dar uma olhada na entidade que usaremos ao longo deste artigo:

@Entity
public class Product {
    @Id
    private long id;

    private String name;

    private String description;

    private String category;

    private BigDecimal unitPrice;

    // setters and getters
}

Esta é uma classe de entidade simples que representa um produto com várias propriedades.

3. Projeções JPA

Embora a especificação JPA não mencione as projeções explicitamente, há muitos casos em que as encontramos no conceito.

Normalmente, uma consulta JPQL possui uma classe de entidade candidata. A consulta, em execução, cria objetos da classe candidata - preenchendo todas as suas propriedades usando os dados recuperados.

Mas, é possívelretrieve a subset of the properties of the entity, ou, isto é, aprojection dos dados da coluna.

Além dos dados da coluna,we can also project the results of grouping functions.

3.1. Projeções de coluna única

Suponhamos que queremos listar os nomes de todos os produtos. Em JPQL, podemos fazer isso incluindo apenasname na cláusulaselect:

Query query = entityManager.createQuery("select name from Product");
List resultList = query.getResultList();


Ou podemos fazer o mesmo comCriteriaBuilder:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(String.class);
Root product = query.from(Product.class);
query.select(product.get("name"));
List resultList = entityManager.createQuery(query).getResultList();

Porquewe are projecting a single column that happens to be of type String, we expect to get a list of Strings no resultado. Portanto, especificamos a classe candidata comoString no métodocreateQuery().

Como queremos projetar em uma única propriedade,we’ve used the Query.select() method. O que está aqui é qual propriedade queremos, então, em nosso caso, usaríamos a propriedadename de nossa entidadeProduct.

Agora, vamos dar uma olhada em um exemplo de saída gerado pelas duas consultas acima:

Product Name 1
Product Name 2
Product Name 3
Product Name 4

Observe queif we’d used the id property in the projection instead of name, the query would have returned a list of Long objects.

3.2. Projeções Multi-Colunas

Para projetar em várias colunas usando JPQL, só temos que adicionar todas as colunas necessárias à cláusulaselect:

Query query = session.createQuery("select id, name, unitPrice from Product");
List resultList = query.getResultList();

Mas, ao usar umCriteriaBuilder, teremos que fazer as coisas de maneira um pouco diferente:

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Object[].class);
Root product = query.from(Product.class);
query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice"));
List resultList = entityManager.createQuery(query).getResultList();

Aqui,we’ve used the method multiselect() instead of select(). Usando esse método, podemos especificar vários itens a serem selecionados.

Outra mudança significativa é o uso deObject[]. When we select multiple items, the query returns an object array com valor para cada item projetado. Este é o caso do JPQL também.

Vamos ver como ficam os dados quando os imprimimos:

[1, Product Name 1, 1.40]
[2, Product Name 2, 4.30]
[3, Product Name 3, 14.00]
[4, Product Name 4, 3.90]

Como podemos ver, os dados retornados são um pouco complicados de processar. Mas, felizmente,we can get JPA to populate this data into a custom class.

Além disso, podemos usarCriteriaBuilder.tuple() ouCriteriaBuilder.construct() para obter os resultados como uma lista de objetosTuple ou objetos de uma classe personalizada, respectivamente.

3.3. Projetando Funções Agregadas

Além dos dados da coluna, às vezes podemos querer agrupar os dados e usar funções de agregação, comocounteaverage.

Digamos que queremos encontrar o número de produtos em cada categoria. Podemos fazer isso usando a função agregadacount() em JPQL:

Query query = entityManager.createQuery("select p.category, count(p) from Product p group by p.category");

Ou podemos usarCriteriaBuilder:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Object[].class);
Root product = query.from(Product.class);
query.multiselect(product.get("category"), builder.count(product));
query.groupBy(product.get("category"));

Aqui, usamos o métodoCriteriaBuilder'scount().

O uso de qualquer um dos itens acima produzirá uma lista de matrizes de objetos:

[category1, 2]
[category2, 1]
[category3, 1]

Além decount(),CriteriaBuilder fornece várias outras funções de agregação:

  • avg - Calcula o valor médio de uma coluna em um grupo

  • max - Calcula o valor máximo para uma coluna em um grupo

  • min - Calcula o valor mínimo para uma coluna em um grupo

  • least - Encontra o menor dos valores da coluna (por exemplo, em ordem alfabética ou por data)

  • sum - Calcula a soma dos valores da coluna em um grupo

4. Projeções de hibernação

Ao contrário do JPA, o Hibernate forneceorg.hibernate.criterion.Projection for projecting with a Criteria query. Ele também fornece uma classe chamadaorg.hibernate.criterion.Projections, uma fábrica para instânciasProjection.

4.1. Projeções de coluna única

Primeiro, vamos ver como podemos projetar uma única coluna. Usaremos o exemplo que vimos anteriormente:

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(Projections.property("name"));

Usamos o métodoCriteria.setProjection() para especificar a propriedade que queremos no resultado da consulta. Projections.property() does the same work for us as Root.get() did ao indicar a coluna a ser selecionada.

4.2. Projeções Multi-Colunas

Para projetar várias colunas, primeiro teremos que criar umProjectionList. _ProjectionList * _ * é um tipo especial deProjection que envolve outras projeções para permitir a seleção de vários valores.

Podemos criar um métodoProjectionListusing the Projections.projectionList(), como mostrarProduct'sidename:

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(
  Projections.projectionList()
    .add(Projections.id())
    .add(Projections.property("name")));

4.3. Projetando Funções Agregadas

Assim comoCriteriaBuilder, a classeProjections também fornece métodos para funções de agregação.

Vamos ver como podemos implementar o exemplo de contagem que vimos anteriormente:

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(
  Projections.projectionList()
    .add(Projections.groupProperty("category"))
    .add(Projections.rowCount()));

É importante observar quewe didn’t directly specify the GROUP BY in the Criteria object. ChamargroupProperty aciona isso para nós.

Além da funçãorowCount(),Projections also provides the aggregate functions we saw earlier.

4.4. Usando um pseudônimo para uma projeção

Um recurso interessante da API Hibernate Criteria é o uso de um alias para uma projeção.

Isso é especialmente útil ao usar uma função agregada, já que podemos nos referir ao alias nas instânciasCriterioneOrder:

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(Projections.projectionList()
             .add(Projections.groupProperty("category"))
             .add(Projections.alias(Projections.rowCount(), "count")));
criteria.addOrder(Order.asc("count"));

5. Conclusão

Neste artigo, vimos como projetar propriedades de entidade usando JPA e Hibernate.

É importante notar queHibernate has deprecated its Criteria API from version 5.2 onwards in favor of the JPA CriteriaQuery API. Mas, isso ocorre apenas porque a equipe do Hibernate não tem tempo para manter duas APIs diferentes, que fazem praticamente a mesma coisa, em sincronia.

E, claro, o código usado neste artigo pode ser encontradoover on GitHub.