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

