Ich habe ein paar Lösungen dafür gefunden.
Verwenden zugeordneter Entitäten (JPA 2.0)
Mit JPA 2.0 ist es nicht möglich, eine native Abfrage einem POJO zuzuordnen, sondern nur mit einer Entität.
Zum Beispiel:
Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();
In diesem Fall Jedi
muss es sich jedoch um eine zugeordnete Entitätsklasse handeln.
Eine Alternative, um die ungeprüfte Warnung hier zu vermeiden, wäre die Verwendung einer benannten nativen Abfrage. Wenn wir also die native Abfrage in einer Entität deklarieren
@NamedNativeQuery(
name="jedisQry",
query = "SELECT name,age FROM jedis_table",
resultClass = Jedi.class)
Dann können wir einfach tun:
TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
Dies ist sicherer, wir können jedoch weiterhin eine zugeordnete Entität verwenden.
Manuelle Zuordnung
Eine Lösung, die ich ein wenig experimentiert habe (vor der Ankunft von JPA 2.1), war das Mapping gegen einen POJO-Konstruktor mit ein wenig Reflexion.
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);
}
}
Diese Methode verwendet im Grunde genommen ein Tupel-Array (wie es von nativen Abfragen zurückgegeben wird) und ordnet es einer bereitgestellten POJO-Klasse zu, indem nach einem Konstruktor gesucht wird, der dieselbe Anzahl von Feldern und denselben Typ aufweist.
Dann können wir bequeme Methoden anwenden wie:
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);
}
Und wir können diese Technik einfach wie folgt anwenden:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
JPA 2.1 mit @SqlResultSetMapping
Mit der Einführung von JPA 2.1 können wir die Annotation @SqlResultSetMapping verwenden, um das Problem zu lösen.
Wir müssen eine Ergebnismengenzuordnung irgendwo in einer Entität deklarieren:
@SqlResultSetMapping(name="JediResult", classes = {
@ConstructorResult(targetClass = Jedi.class,
columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
Und dann machen wir einfach:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
In diesem Fall Jedi
muss es sich natürlich nicht um eine zugeordnete Entität handeln. Es kann ein reguläres POJO sein.
Verwenden der XML-Zuordnung
Ich bin einer von denen, die das Hinzufügen all dieser @SqlResultSetMapping
Elemente in meinen Entitäten als ziemlich invasiv empfinden, und ich mag die Definition benannter Abfragen innerhalb von Entitäten besonders nicht. Alternativ mache ich das alles in der META-INF/orm.xml
Datei:
<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>
Und das sind alle Lösungen, die ich kenne. Die letzten beiden sind der ideale Weg, wenn wir JPA 2.1 verwenden können.