Tôi đã tìm thấy một vài giải pháp cho việc này.
Sử dụng các thực thể đã ánh xạ (JPA 2.0)
Sử dụng JPA 2.0, không thể ánh xạ truy vấn gốc tới POJO, chỉ có thể được thực hiện với một thực thể.
Ví dụ:
Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();
Nhưng trong trường hợp này Jedi
, phải là một lớp thực thể được ánh xạ.
Một cách khác để tránh cảnh báo không được kiểm tra ở đây, sẽ là sử dụng truy vấn gốc có tên. Vì vậy, nếu chúng ta khai báo truy vấn gốc trong một thực thể
@NamedNativeQuery(
name="jedisQry",
query = "SELECT name,age FROM jedis_table",
resultClass = Jedi.class)
Sau đó, chúng ta có thể chỉ cần làm:
TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();
Điều này an toàn hơn, nhưng chúng tôi vẫn bị hạn chế sử dụng thực thể được ánh xạ.
Ánh xạ thủ công
Một giải pháp tôi đã thử nghiệm một chút (trước khi JPA 2.1 xuất hiện) là thực hiện ánh xạ dựa trên hàm tạo POJO bằng một chút phản xạ.
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);
}
}
Phương thức này về cơ bản lấy một mảng tuple (như được trả về bởi các truy vấn gốc) và ánh xạ nó tới một lớp POJO được cung cấp bằng cách tìm kiếm một hàm tạo có cùng số lượng trường và cùng loại.
Sau đó, chúng ta có thể sử dụng các phương pháp thuận tiện như:
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);
}
Và chúng ta chỉ cần sử dụng kỹ thuật này như sau:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);
JPA 2.1 với @SqlResultSetMapping
Với sự xuất hiện của JPA 2.1, chúng ta có thể sử dụng chú thích @SqlResultSetMapping để giải quyết vấn đề.
Chúng ta cần khai báo ánh xạ tập kết quả ở đâu đó trong một thực thể:
@SqlResultSetMapping(name="JediResult", classes = {
@ConstructorResult(targetClass = Jedi.class,
columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})
Và sau đó chúng tôi chỉ cần làm:
Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();
Tất nhiên, trong trường hợp này Jedi
không cần phải là một thực thể được ánh xạ. Nó có thể là một POJO thông thường.
Sử dụng ánh xạ XML
Tôi là một trong những người tìm thấy thêm tất cả những điều này @SqlResultSetMapping
khá xâm lấn vào các thực thể của mình và tôi đặc biệt không thích định nghĩa của các truy vấn được đặt tên trong các thực thể, vì vậy, thay vào đó tôi làm tất cả điều này trong META-INF/orm.xml
tệp:
<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>
Và đó là tất cả những giải pháp tôi biết. Hai cách cuối cùng là cách lý tưởng nếu chúng ta có thể sử dụng JPA 2.1.