私はこれに対するいくつかの解決策を見つけました。
マップされたエンティティの使用(JPA 2.0)
JPA 2.0を使用すると、ネイティブクエリをPOJOにマップすることはできません。これは、エンティティでのみ実行できます。
例えば:
Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();
ただし、この場合、Jedi
はマップされたエンティティクラスである必要があります。
ここでチェックされていない警告を回避する別の方法は、名前付きネイティブクエリを使用することです。したがって、エンティティでネイティブクエリを宣言すると
@NamedNativeQuery(
name="jedisQry",
query = "SELECT name,age FROM jedis_table",
resultClass = Jedi.class)
次に、次のことを簡単に行うことができます。
TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
これはより安全ですが、マップされたエンティティの使用には制限があります。
手動マッピング
私が少し実験した解決策(JPA 2.1が登場する前)は、少しのリフレクションを使用してPOJOコンストラクターに対してマッピングを行うことでした。
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);
}
}
このメソッドは基本的にタプル配列(ネイティブクエリによって返される)を受け取り、同じ数のフィールドと同じタイプのコンストラクターを探すことによって、提供されたPOJOクラスに対してマップします。
次に、次のような便利な方法を使用できます。
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);
}
そして、この手法を次のように簡単に使用できます。
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
@SqlResultSetMappingを使用したJPA2.1
JPA 2.1の登場により、@SqlResultSetMappingアノテーションを使用して問題を解決できるようになりました。
エンティティのどこかに結果セットマッピングを宣言する必要があります。
@SqlResultSetMapping(name="JediResult", classes = {
@ConstructorResult(targetClass = Jedi.class,
columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
そして、私たちは単に次のことを行います。
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
もちろん、この場合Jedi
、マップされたエンティティである必要はありません。通常のPOJOにすることができます。
XMLマッピングの使用
私は自分のエンティティにこれらすべてを@SqlResultSetMapping
かなり侵襲的に追加することに気付いた人の1人であり、エンティティ内の名前付きクエリの定義が特に嫌いなので、代わりにMETA-INF/orm.xml
ファイルでこれをすべて行います。
<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>
そして、それらは私が知っているすべての解決策です。最後の2つは、JPA2.1を使用できる場合の理想的な方法です。