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:

ididadenomesobrenome
136Ana Luciada Silva
230CarlaSantos
326VivianeMoraes
425RicardoGomes
546CristianoFerreira

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:

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...