J'ai trouvé quelques solutions à cela.
Utilisation d'entités mappées (JPA 2.0)
En utilisant JPA 2.0, il n'est pas possible de mapper une requête native à un POJO, cela ne peut être fait qu'avec une entité.
Par exemple:
Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();
Mais dans ce cas, Jedi
,, doit être une classe d'entité mappée.
Une alternative pour éviter l'avertissement non coché ici, serait d'utiliser une requête native nommée. Donc, si nous déclarons la requête native dans une entité
@NamedNativeQuery(
name="jedisQry",
query = "SELECT name,age FROM jedis_table",
resultClass = Jedi.class)
Ensuite, nous pouvons simplement faire:
TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
C'est plus sûr, mais nous sommes toujours limités à utiliser une entité mappée.
Cartographie manuelle
Une solution que j'ai expérimentée un peu (avant l'arrivée de JPA 2.1) consistait à faire du mapping contre un constructeur POJO en utilisant un peu de réflexion.
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);
}
}
Cette méthode prend essentiellement un tableau de tuple (tel que renvoyé par les requêtes natives) et le mappe sur une classe POJO fournie en recherchant un constructeur qui a le même nombre de champs et du même type.
Ensuite, nous pouvons utiliser des méthodes pratiques telles que:
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);
}
Et nous pouvons simplement utiliser cette technique comme suit:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
JPA 2.1 avec @SqlResultSetMapping
Avec l'arrivée de JPA 2.1, nous pouvons utiliser l'annotation @SqlResultSetMapping pour résoudre le problème.
Nous devons déclarer un mappage d'ensemble de résultats quelque part dans une entité:
@SqlResultSetMapping(name="JediResult", classes = {
@ConstructorResult(targetClass = Jedi.class,
columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
Et puis nous faisons simplement:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
Bien sûr, dans ce cas, Jedi
il n'est pas nécessaire d'être une entité mappée. Cela peut être un POJO ordinaire.
Utilisation du mappage XML
Je fais partie de ceux qui trouvent que l'ajout de tous ces éléments est @SqlResultSetMapping
assez invasif dans mes entités, et je n'aime pas particulièrement la définition des requêtes nommées au sein des entités, alors je fais tout cela dans le META-INF/orm.xml
fichier:
<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>
Et ce sont toutes les solutions que je connais. Les deux derniers sont le moyen idéal si nous pouvons utiliser JPA 2.1.