Projeção com JPA
Tempos atrás fui questionado se era possível com JPA retornar apenas algumas colunas de uma tabela. Ou então projetar o retorno da consulta concatenando duas colunas como um único resultado. Então, resolvi escrever esse pequeno tutorial para abordar tais exemplos:
Projetando resultados com JPA
Esse processo pode ser realizado de diferentes formas e, por algum motivo, a mais comentada em busca no Google seria criar uma classe que represente o resultado desejado. Ou seja, criar uma classe no estilo DTO com os atributos referentes aquilo que a consulta deve retornar. Entretanto, a especificação JPA 2.0+ fornece a interface javax.persistence.Tuple
para esse fim e assim, não seria necessário criar uma classe DTO.
Vejamos agora um exemplo básico de consulta sobre a tabela pessoas
:
id | idade | nome | sobrenome |
1 | 36 | Ana Lucia | da Silva |
2 | 30 | Carla | Santos |
3 | 26 | Viviane | Moraes |
4 | 25 | Ricardo | Gomes |
5 | 46 | Cristiano | Ferreira |
A ideia é via JPQL realizar uma consulta que retorne apenas os registros das colunas nome
e sobrenome
. Acredito que você já deve ter tentando algo desse tipo usando JPA:
public List<Pessoa> buscarTodos() {
String query = "SELECT p.nome, p.sobrenome FROM Pessoa p";
entityManager = JPAUtil.getEntityManager();
List<Pessoa> result = entityManager
.createQuery(query, Pessoa.class)
.getResultList();
entityManager.close();
return result;
}
A consulta deveria retornar uma lista do tipo pessoas, entretanto, como o retorno desta JPQL está projetando apenas duas colunas das quatro existentes na tabela, a JPA não vai reconhecer o retorno da consulta como um objeto Pessoa
para cada linha retornada. Por isso, você teria uma exceção como esta:
java.lang.IllegalArgumentException: Cannot create TypedQuery
for query with more than one return using requested result
type [com.mballem.projecaojpa.entity.Pessoa]
É neste ponto que normalmente o iniciante se perde e acaba buscando por uma solução e chega ao DTO. O DTO seria uma classe que teria apenas os atributos nome
e sobrenome
e essa classe seria o tipo de retorno esperado: List<PessoaDTO>
.
Mas, podemos usar a interface Tuple
e o método de consulta passaria a ser este:
public List<Tuple> buscarTodos() {
String query = "SELECT p.nome AS nome, p.sobrenome AS sobrenome FROM Pessoa p";
entityManager = JPAUtil.getEntityManager();
List<Tuple> result = entityManager
.createQuery(query, Tuple.class)
.getResultList();
entityManager.close();
return result;
}
Observe alguns pontos importantes. O primeiro é na clausula select
, onde usamos o alias para nomear o objeto de retorno para cada coluna da tabela. Esse alias será usado mais a frente para encontrar os valores retornados pela consulta dentro da lista.
Temos também o objeto java.util.List
com o tipo genérico Tuple
ao invés de Pessoa
. O mesmo acontece na declaração do parâmetro no método createQuery()
, onde foi substituído o tipo Pessoa
por Tuple
.
Por fim, para recuperar os resultados usamos o método get()
da interface Tuple
, passando como parâmetro uma string com o alias adicionado no select
: get("nome")
e get("sobrenome")
. Veja a seguir um exemplo baseado em um foreach para imprimir no console o retorno da consulta:
List<Tuple> result = new PessoaDao().buscarTodos();
for (Tuple tuple : result) {
System.out.printf("%s %s \n",
tuple.get("nome"), tuple.get("sobrenome"));
}
No console teríamos a seguinte saída:
Ana Lucia da Silva
Carla Santos
Viviane Moraes
Ricardo Gomes
Cristiano Ferreira
A interface Tuple
possui algumas sobrecargas para o método get()
, onde cada uma delas te permitirá recuperar os valores de diferentes formas. Leia a documentação para saber mais sobre isso.
Agora, veremos o exemplo referente a concatenação, onde estou simulando uma operação com o MySQL:
public List<Tuple> concat() {
String query = "SELECT CONCAT(p.nome,' ', p.sobrenome) AS nome_completo FROM Pessoa p";
entityManager = JPAUtil.getEntityManager();
List<Tuple> result = entityManager
.createQuery(query, Tuple.class)
.getResultList();
entityManager.close();
return result;
}
O alias neste exemplo será nome_completo
e para recuperar os resultados e imprimi-los nos console teremos uma operação similar a anteriormente abordada:
List<Tuple> concat = new PessoaDao().concat();
for (Tuple tuple : concat) {
System.out.println(tuple.get("nome_completo"));
}
Muito simples não é?
Referencia:
- Documentação interface Tuple – https://docs.oracle.com/javaee/6/api/javax/persistence/Tuple.html