Eu encontrei algumas soluções para isso.
Usando entidades mapeadas (JPA 2.0)
Usando o JPA 2.0, não é possível mapear uma consulta nativa para um POJO, isso só pode ser feito com uma entidade.
Por exemplo:
Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();
Mas, neste caso, Jedi
deve ser uma classe de entidade mapeada.
Uma alternativa para evitar o aviso desmarcado aqui seria usar uma consulta nativa nomeada. Portanto, se declararmos a consulta nativa em uma entidade
@NamedNativeQuery(
name="jedisQry",
query = "SELECT name,age FROM jedis_table",
resultClass = Jedi.class)
Então, podemos simplesmente fazer:
TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
Isso é mais seguro, mas ainda estamos restritos a usar uma entidade mapeada.
Mapeamento Manual
Uma solução que experimentei um pouco (antes da chegada do JPA 2.1) estava fazendo o mapeamento contra um construtor POJO usando um pouco de reflexão.
public static <T> T map(Class<T> type, Object[] tuple){
List<Class<?>> tupleTypes = new ArrayList<>();
for(Object field : tuple){
tupleTypes.add(field.getClass());
}
try {
Constructor<T> ctor = type.getConstructor(tupleTypes.toArray(new Class<?>[tuple.length]));
return ctor.newInstance(tuple);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Esse método basicamente pega uma matriz de tupla (retornada por consultas nativas) e a mapeia em uma classe POJO fornecida, procurando por um construtor que tenha o mesmo número de campos e o mesmo tipo.
Em seguida, podemos usar métodos convenientes, como:
public static <T> List<T> map(Class<T> type, List<Object[]> records){
List<T> result = new LinkedList<>();
for(Object[] record : records){
result.add(map(type, record));
}
return result;
}
public static <T> List<T> getResultList(Query query, Class<T> type){
@SuppressWarnings("unchecked")
List<Object[]> records = query.getResultList();
return map(type, records);
}
E podemos simplesmente usar esta técnica da seguinte maneira:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
JPA 2.1 com @SqlResultSetMapping
Com a chegada do JPA 2.1, podemos usar a anotação @SqlResultSetMapping para resolver o problema.
Precisamos declarar um mapeamento de conjunto de resultados em algum lugar de uma entidade:
@SqlResultSetMapping(name="JediResult", classes = {
@ConstructorResult(targetClass = Jedi.class,
columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
E então simplesmente fazemos:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
Obviamente, nesse caso Jedi
, não precisa ser uma entidade mapeada. Pode ser um POJO comum.
Usando mapeamento XML
Eu sou um daqueles que consideram a adição de todas essas informações @SqlResultSetMapping
bastante invasivas em minhas entidades, e particularmente não gosto da definição de consultas nomeadas dentro de entidades, então, alternativamente, faço tudo isso no META-INF/orm.xml
arquivo:
<named-native-query name="GetAllJedi" result-set-mapping="JediMapping">
<query>SELECT name,age FROM jedi_table</query>
</named-native-query>
<sql-result-set-mapping name="JediMapping">
<constructor-result target-class="org.answer.model.Jedi">
<column name="name" class="java.lang.String"/>
<column name="age" class="java.lang.Integer"/>
</constructor-result>
</sql-result-set-mapping>
E essas são todas as soluções que eu conheço. Os dois últimos são a maneira ideal se podemos usar o JPA 2.1.