Вопрос-ответ

JPA : How to convert a native query result set to POJO class collection

JPA : Как преобразовать собственный набор результатов запроса в коллекцию классов POJO

Я использую JPA в своем проекте.

Я пришел к запросу, в котором мне нужно выполнить операцию объединения для пяти таблиц. Итак, я создал собственный запрос, который возвращает пять полей.

Теперь я хочу преобразовать результирующий объект в класс java POJO, который содержит те же пять строк.

Есть ли в JPA какой-либо способ напрямую преобразовать этот результат в список объектов POJO??

Я пришел к следующему решению..

@NamedNativeQueries({  
@NamedNativeQuery(
name = "nativeSQL",
query = "SELECT * FROM Actors",
resultClass = db.Actor.class),
@NamedNativeQuery(
name = "nativeSQL2",
query = "SELECT COUNT(*) FROM Actors",
resultClass = XXXXX) // <--------------- problem
})

Теперь здесь, в resultClass, нужно ли нам предоставлять класс, который является фактической сущностью JPA ?
или
Мы можем преобразовать его в любой класс JAVA POJO, который содержит те же имена столбцов?

Переведено автоматически
Ответ 1

Я нашел пару решений для этого.

Использование сопоставленных объектов (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);

JPA 2.1 с @SqlResultSetMapping

С появлением 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 довольно инвазивно для моих сущностей, и мне особенно не нравится определение именованных запросов внутри сущностей, поэтому в качестве альтернативы я делаю все это в 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>

И это все решения, которые я знаю. Последние два - идеальный способ, если мы можем использовать JPA 2.1.

Ответ 2

JPA предоставляет функцию, SqlResultSetMapping которая позволяет вам преобразовывать все, что возвращается из вашего собственного запроса, в сущность или пользовательский класс.

РЕДАКТИРОВАТЬ JPA 1.0 не допускает сопоставления с классами, не являющимися сущностями. Только в JPA 2.1 был добавлен ConstructorResult для сопоставления возвращаемых значений с классом java.

Кроме того, для решения проблемы OP с получением count должно быть достаточно определить сопоставление результирующего набора с помощью одного ColumnResult

Ответ 3

Да, с JPA 2.1 это просто. У вас есть очень полезные аннотации. Они упрощают вашу жизнь.

Сначала объявите свой собственный запрос, затем сопоставление вашего результирующего набора (которое определяет сопоставление данных, возвращаемых базой данных, вашему POJO). Напишите свой класс POJO, на который вы будете ссылаться (для краткости не включен здесь). Последнее, но не менее важное: создайте метод в DAO (например) для вызова запроса. У меня это сработало в приложении dropwizard (1.0.0).

Сначала объявите собственный запрос в классе сущностей (@Entity):

@Entity
@NamedNativeQuery (
name = "domain.io.MyClass.myQuery",
query = "Select a.colA, a.colB from Table a",
resultSetMapping = "mappinMyNativeQuery") // must be the same name as in the SqlResultSetMapping declaration

Ниже вы можете добавить объявление сопоставления результирующего набора:

@SqlResultSetMapping(
name = "mapppinNativeQuery", // same as resultSetMapping above in NativeQuery
classes = {
@ConstructorResult(
targetClass = domain.io.MyMapping.class,
columns = {
@ColumnResult( name = "colA", type = Long.class),
@ColumnResult( name = "colB", type = String.class)
}
)
}
)

Позже в DAO вы можете ссылаться на запрос как

public List<domain.io.MyMapping> findAll() {
return (namedQuery("domain.io.MyClass.myQuery").list());
}

Вот и все.

Ответ 4

Если вы используете Spring-jpa, это дополнение к ответам и этому вопросу. Пожалуйста, исправьте это, если есть недостатки. В основном я использовал три метода для достижения "результата сопоставления Object[] с pojo" в зависимости от того, какие практические потребности я удовлетворяю:


  1. Достаточно встроенного метода JPA.

  2. Встроенного метода JPA недостаточно, но настроенного sql с его Entity использованием достаточно.

  3. Предыдущие 2 завершились неудачей, и я должен использовать nativeQuery. Вот примеры. Ожидаемый результат pojo:


    public class Antistealingdto {

    private String secretKey;

    private Integer successRate;

    // GETTERs AND SETTERs

    public Antistealingdto(String secretKey, Integer successRate) {
    this.secretKey = secretKey;
    this.successRate = successRate;
    }
    }

Метод 1: Преобразовать pojo в интерфейс:

public interface Antistealingdto {
String getSecretKey();
Integer getSuccessRate();
}

И репозиторий:

interface AntiStealingRepository extends CrudRepository<Antistealing, Long> {
Antistealingdto findById(Long id);
}

Метод 2:
Репозиторий:

@Query("select new AntistealingDTO(secretKey, successRate) from Antistealing where ....")
Antistealing whatevernamehere(conditions);

Примечание: последовательность параметров конструктора POJO должна быть идентичной как в определении POJO, так и в sql.

Метод 3: Используйте @SqlResultSetMapping и @NamedNativeQuery в Entity качестве примера в ответе Эдвина Далорцо.

Первые два метода вызвали бы множество промежуточных обработчиков, таких как настраиваемые преобразователи. Например, AntiStealing определяет secretKey, перед сохранением вставляется преобразователь для его шифрования. Это привело бы к тому, что первые 2 метода вернули бы преобразованное обратно secretKey что не то, чего я хочу. В то время как метод 3 преодолевает преобразование и возвращается secretKey таким же, каким оно сохранено (зашифрованным).

2023-04-02 02:01 java jpa