Ho trovato un paio di soluzioni a questo.
Utilizzo di entità mappate (JPA 2.0)
Utilizzando JPA 2.0 non è possibile mappare una query nativa a un POJO, può essere eseguita solo con un'entità.
Per esempio:
Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();
Ma in questo caso, Jedi
deve essere una classe entità mappata.
Un'alternativa per evitare l'avviso non selezionato qui, sarebbe quella di utilizzare una query nativa denominata. Quindi, se dichiariamo la query nativa in un'entità
@NamedNativeQuery(
name="jedisQry",
query = "SELECT name,age FROM jedis_table",
resultClass = Jedi.class)
Quindi, possiamo semplicemente fare:
TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
Questo è più sicuro, ma siamo ancora limitati a utilizzare un'entità mappata.
Mappatura manuale
Una soluzione che ho sperimentato un po '(prima dell'arrivo di JPA 2.1) è stata quella di mappare un costruttore POJO usando un po' di riflessione.
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);
}
}
Questo metodo fondamentalmente prende un array di tuple (come restituito da query native) e lo mappa su una classe POJO fornita cercando un costruttore che abbia lo stesso numero di campi e dello stesso tipo.
Quindi possiamo usare metodi convenienti come:
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 possiamo semplicemente usare questa tecnica come segue:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
JPA 2.1 con @SqlResultSetMapping
Con l'arrivo di JPA 2.1, possiamo usare l'annotazione @SqlResultSetMapping per risolvere il problema.
Dobbiamo dichiarare un set di risultati mappato da qualche parte in un'entità:
@SqlResultSetMapping(name="JediResult", classes = {
@ConstructorResult(targetClass = Jedi.class,
columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
E poi facciamo semplicemente:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
Naturalmente, in questo caso Jedi
non deve essere un'entità mappata. Può essere un POJO normale.
Utilizzo della mappatura XML
Sono uno di quelli che trovano l'aggiunta di tutti questi @SqlResultSetMapping
piuttosto invasivi nelle mie entità, e in particolare non mi piace la definizione di query con nome all'interno delle entità, quindi in alternativa faccio tutto questo nel META-INF/orm.xml
file:
<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 quelle sono tutte le soluzioni che conosco. Gli ultimi due sono il modo ideale se possiamo usare JPA 2.1.