JPA: jak przekonwertować natywny zestaw wyników zapytania na kolekcję klas POJO

Używam JPA w moim projekcie.

Doszedłem do zapytania, w którym muszę wykonać operację join na pięciu stołach. Więc stworzyłem natywne zapytanie, które zwraca pięć pól.

Teraz chcę przekonwertować obiekt wynikowy na klasę Java POJO, która zawiera te same pięć łańcuchów.

Czy Jest jakiś sposób w JPA, aby bezpośrednio wrzucić ten wynik do listy obiektów POJO ??

Doszedłem do następującego rozwiązania ..

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

Teraz w resultClass, czy musimy podać klasę, która czy rzeczywisty podmiot JPA ? Lub Możemy ją przekonwertować na dowolną klasę Java POJO, która zawiera te same nazwy kolumn ?

 127
Author: Mark, 2012-10-22

11 answers

JPA zapewnia SqlResultSetMapping pozwala to na mapowanie dowolnych zwrotów z natywnego zapytania do encji lub niestandardowej klasy .

EDIT JPA 1.0 nie pozwala na mapowanie do klas innych niż encje. Tylko w JPA 2.1 A ConstructorResult został dodany do zwracanych wartości map klasy java.

Ponadto, dla problemu OP z uzyskaniem count powinno wystarczyć zdefiniowanie zestawu wyników mapowania z pojedynczym ColumnResult

 79
Author: Denis Tulskiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-31 17:09:22

Znalazłem na to kilka rozwiązań.

Korzystanie z mapowanych encji (JPA 2.0)

Używając JPA 2.0 nie jest możliwe mapowanie natywnego zapytania do POJO, można to zrobić tylko za pomocą encji.

Na przykład:

Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();

Ale w tym przypadku, Jedi, musi być mapowaną klasą encji.

Alternatywą, aby uniknąć niezaznaczonego Ostrzeżenia, byłoby użycie nazwanego natywnego zapytania. Więc jeśli zadeklarujemy natywne zapytanie w entity

@NamedNativeQuery(
 name="jedisQry", 
 query = "SELECT name,age FROM jedis_table", 
 resultClass = Jedi.class)

Wtedy możemy po prostu zrobić:

TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();

Jest to bezpieczniejsze, ale nadal jesteśmy ograniczeni do używania zmapowanej istoty.

Ręczne Mapowanie

RozwiÄ ... zaniem, ktĂłre nieco eksperymentowaĺ 'em (przed POJO 2.1), byĹ' o mapowanie na konstruktorze POJO z odrobinÄ ... refleksjÄ....

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);
   }
}

Ta metoda pobiera tablicę krotek (zwracaną przez natywne zapytania) i mapuje ją przeciwko podanej klasie POJO, szukając konstruktora, który ma taką samą liczbę pól i tego samego typu.

Wtedy możemy użyć wygodnych metod, takich jak:

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);
}

I możemy po prostu użyć tej techniki w następujący sposób:

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);

JPA 2.1 z @SqlResultSetMapping

Wraz z pojawieniem się JPA 2.1, możemy użyć adnotacji @sqlresultsetmapping, aby rozwiązać problem.

Musimy zadeklarować odwzorowanie zbioru wyników gdzieś w encji:

@SqlResultSetMapping(name="JediResult", classes = {
    @ConstructorResult(targetClass = Jedi.class, 
    columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})

A potem po prostu robimy:

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();

Z oczywiście, w tym przypadku Jedi nie musi być zmapowaną jednostką. To może być zwykłe POJO.

Korzystanie z mapowania XML

Jestem jednym z tych, którzy uważają dodawanie wszystkich tych @SqlResultSetMapping dość inwazyjne w moich encjach, a szczególnie nie lubię definicji nazwanych zapytań w encjach, więc alternatywnie robię to wszystko w pliku 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"/>
            <column name="age"/>
        </constructor-result>
    </sql-result-set-mapping>

I to są wszystkie rozwiązania, które znam. Dwa ostatnie są idealnym sposobem, jeśli możemy użyć JPA 2.1.

 158
Author: Edwin Dalorzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-05 16:23:06

Tak, z JPA 2.1 to proste. Masz bardzo przydatne adnotacje. Ułatwiają Ci życie.

Najpierw zadeklaruj natywne zapytanie, a następnie mapowanie zestawu wyników (które definiuje mapowanie danych zwracanych przez bazę danych do Twoich Pojo). Napisz swoją klasę POJO, aby odnieść się do (nie zawarte tutaj dla zwięzłości). Na koniec: utwórz metodę w DAO (na przykład), aby wywołać zapytanie. To działało dla mnie w aplikacji dropwizard (1.0.0).

Najpierw zadeklaruj natywne zapytanie w encji klasa:

@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

Pod spodem możesz dodać deklarację mapowania resultset:

@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)
          }
      )
   } 
)

Później w DAO możesz odnieść się do zapytania jako

public List<domain.io.MyMapping> findAll() {
        return (namedQuery("domain.io.MyClass.myQuery").list());
    }
To wszystko.
 9
Author: giulio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-18 12:24:01

Najpierw zadeklaruj następujące adnotacje:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultEntity {
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultColumn {
    int index();
}

Następnie opisz swoje POJO w następujący sposób:

@NativeQueryResultEntity
public class ClassX {
    @NativeQueryResultColumn(index=0)
    private String a;

    @NativeQueryResultColumn(index=1)
    private String b;
}

Następnie napisz procesor adnotacji:

public class NativeQueryResultsMapper {

    private static Logger log = LoggerFactory.getLogger(NativeQueryResultsMapper.class);

    public static <T> List<T> map(List<Object[]> objectArrayList, Class<T> genericType) {
        List<T> ret = new ArrayList<T>();
        List<Field> mappingFields = getNativeQueryResultColumnAnnotatedFields(genericType);
        try {
            for (Object[] objectArr : objectArrayList) {
                T t = genericType.newInstance();
                for (int i = 0; i < objectArr.length; i++) {
                    BeanUtils.setProperty(t, mappingFields.get(i).getName(), objectArr[i]);
                }
                ret.add(t);
            }
        } catch (InstantiationException ie) {
            log.debug("Cannot instantiate: ", ie);
            ret.clear();
        } catch (IllegalAccessException iae) {
            log.debug("Illegal access: ", iae);
            ret.clear();
        } catch (InvocationTargetException ite) {
            log.debug("Cannot invoke method: ", ite);
            ret.clear();
        }
        return ret;
    }

    // Get ordered list of fields
    private static <T> List<Field> getNativeQueryResultColumnAnnotatedFields(Class<T> genericType) {
        Field[] fields = genericType.getDeclaredFields();
        List<Field> orderedFields = Arrays.asList(new Field[fields.length]);
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].isAnnotationPresent(NativeQueryResultColumn.class)) {
                NativeQueryResultColumn nqrc = fields[i].getAnnotation(NativeQueryResultColumn.class);
                orderedFields.set(nqrc.index(), fields[i]);
            }
        }
        return orderedFields;
    }
}

Użyj powyższego frameworka w następujący sposób:

String sql = "select a,b from x order by a";
Query q = entityManager.createNativeQuery(sql);

List<ClassX> results = NativeQueryResultsMapper.map(q.getResultList(), ClassX.class);
 5
Author: riship89,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-07 20:53:27

Jeśli używasz Spring-jpa, jest to uzupełnienie odpowiedzi i tego pytania. Proszę poprawić to, jeśli jakieś wady. Wykorzystałem głównie trzy metody, aby osiągnąć "odwzorowanie wyniku Object[] do pojo"w oparciu o to, z jaką potrzebą praktyczną się spotykam:

  1. wbudowana metoda JPA wystarczy.
  2. wbudowana metoda JPA nie wystarczy, ale dostosowane sql z jej Entity są wystarczające.
  3. The former 2 failed, and I have to use a nativeQuery. Oto przykłady. Pojo oczekiwano:

    public class Antistealingdto {
    
        private String secretKey;
    
        private Integer successRate;
    
        // GETTERs AND SETTERs
    
        public Antistealingdto(String secretKey, Integer successRate) {
            this.secretKey = secretKey;
            this.successRate = successRate;
        }
    }
    

Metoda 1 : Zmiana pojo na interfejs:

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

I repozytorium:

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

Metoda 2 : Repozytorium:

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

Uwaga: Sekwencja parametrów konstruktora POJO musi być identyczna zarówno w definicji POJO, jak i w sql.

Metoda 3 : Użyj @SqlResultSetMapping i @NamedNativeQuery w Entity jako przykład w odpowiedzi Edwina Dalorzo.

Pierwsze dwie metody wywołują wiele funkcji obsługi w środku, takich jak dostosowane Konwertery. Na przykład, AntiStealing definiuje secretKey, zanim zostanie ona utrzymana, wstawiany jest konwerter w celu jej zaszyfrowania. Spowoduje to, że pierwsze 2 metody zwrócą przekonwertowany back secretKey, który nie jest tym, czego chcę. Podczas gdy Metoda 3 pokonałaby konwerter, a zwrócona secretKey byłaby taka sama, jak jest przechowywana (zaszyfrowana).

 5
Author: Tiina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-03 12:03:28

Procedura Unwrap może być wykonana w celu przypisania wyników nie-podmiotowi (czyli Beans/POJO). Procedura jest następująca.

List<JobDTO> dtoList = entityManager.createNativeQuery(sql)
        .setParameter("userId", userId)
        .unwrap(org.hibernate.Query.class).setResultTransformer(Transformers.aliasToBean(JobDTO.class)).list();

Użycie jest dla implementacji JPA-Hibernate.

 2
Author: zawhtut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-26 10:24:43

Jeśli używasz sprężyny, możesz użyć org.springframework.jdbc.core.RowMapper

Oto przykład:

public List query(String objectType, String namedQuery)
{
  String rowMapper = objectType + "RowMapper";
  // then by reflection you can instantiate and use. The RowMapper classes need to follow the naming specific convention to follow such implementation.
} 
 0
Author: Pallab Rath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-29 11:58:08

Ponieważ inni już wspomniali o wszystkich możliwych rozwiązaniach, udostępniam moje rozwiązanie obejścia problemu.

W mojej sytuacji z Postgres 9.4, podczas pracy z Jackson,

//Convert it to named native query.
List<String> list = em.createNativeQuery("select cast(array_to_json(array_agg(row_to_json(a))) as text) from myschema.actors a")
                   .getResultList();

List<ActorProxy> map = new ObjectMapper().readValue(list.get(0), new TypeReference<List<ActorProxy>>() {});

Jestem pewien, że możesz znaleźć to samo dla innych baz danych.

Również FYI, JPA 2.0 natywne wyniki zapytań jako mapa

 0
Author: Darshan Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-11 05:38:14

Użyj DTO Design Pattern. Został użyty w EJB 2.0. / Align = "left" / DTO Design Pattern jest używany do rozwiązania tego problemu. Ale może być używany teraz, gdy aplikacja jest rozwijana Server Side i Client Side oddzielnie.{[9] } jest używany, gdy Server side nie chce przekazać/zwrócić Entity z adnotacją do Client Side.

DTO przykład:

PersonEntity.java

@Entity
public class PersonEntity {
    @Id
    private String id;
    private String address;

    public PersonEntity(){

    }
    public PersonEntity(String id, String address) {
        this.id = id;
        this.address = address;
    }
    //getter and setter

}

PersonDTO.java

public class PersonDTO {
    private String id;
    private String address;

    public PersonDTO() {
    }
    public PersonDTO(String id, String address) {
        this.id = id;
        this.address = address;
    }

    //getter and setter 
}

DTOBuilder.java

public class DTOBuilder() {
    public static PersonDTO buildPersonDTO(PersonEntity person) {
        return new PersonDTO(person.getId(). person.getAddress());
    }
}

EntityBuilder.java

public class EntityBuilder() {
    public static PersonEntity buildPersonEntity(PersonDTO person) {
        return new PersonEntity(person.getId(). person.getAddress());
    }
}
 -1
Author: CycDemo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-22 14:00:39

Zobacz poniższy przykład użycia POJO jako pseudo encji do pobierania wyniku z natywnego zapytania bez użycia złożonego SqlResultSetMapping. Potrzebuję tylko dwóch adnotacji, gołego @Enity i dummy @ Id w Twoim POJO. @Id może być używany w dowolnym wybranym polu, pole @Id może mieć zduplikowane klucze, ale nie wartości null.

Ponieważ @Enity nie mapuje do żadnej fizycznej tabeli, więc to POJO nazywa się pseudo bytem.

Środowisko: eclipselink 2.5.0-RC1, jpa-2.1.0, mysql-connector-java-5.1.14

Możesz pobrać kompletny projekt maven tutaj

Natywne zapytanie bazuje na mysql sample employees db http://dev.mysql.com/doc/employee/en/employees-installation.html

Wytrwałość.xml
<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" 
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="jpa-mysql" transaction-type="RESOURCE_LOCAL">
    <class>org.moonwave.jpa.model.pojo.Employee</class>
    <properties>
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/employees" />
        <property name="javax.persistence.jdbc.user" value="user" />
        <property name="javax.persistence.jdbc.password" value="***" />
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
    </properties>
</persistence-unit>

Pracownik.java
package org.moonwave.jpa.model.pojo;

@Entity
public class Employee {

@Id
protected Long empNo;

protected String firstName;
protected String lastName;
protected String title;

public Long getEmpNo() {
    return empNo;
}
public void setEmpNo(Long empNo) {
    this.empNo = empNo;
}
public String getFirstName() {
    return firstName;
}
public void setFirstName(String firstName) {
    this.firstName = firstName;
}
public String getLastName() {
    return lastName;
}
public void setLastName(String lastName) {
    this.lastName = lastName;
}   
public String getTitle() {
    return title;
}
public void setTitle(String title) {
    this.title = title;
}
public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("empNo: ").append(empNo);
    sb.append(", firstName: ").append(firstName);
    sb.append(", lastName: ").append(lastName);
    sb.append(", title: ").append(title);
    return sb.toString();
}
}

EmployeeNativeQuery.java

public class EmployeeNativeQuery {
private EntityManager em;
private EntityManagerFactory emf;

public void setUp() throws Exception {
    emf=Persistence.createEntityManagerFactory("jpa-mysql");
    em=emf.createEntityManager();
}
public void tearDown()throws Exception {
    em.close();
    emf.close();
}

@SuppressWarnings("unchecked")
public void query() {
    Query query = em.createNativeQuery("select e.emp_no as empNo, e.first_name as firstName, e.last_name as lastName," + 
            "t.title from employees e join titles t on e.emp_no = t.emp_no", Employee.class);
    query.setMaxResults(30);
    List<Employee> list = (List<Employee>) query.getResultList();
    int i = 0;
    for (Object emp : list) {
        System.out.println(++i + ": " + emp.toString());
    }
}

public static void main( String[] args ) {
    EmployeeNativeQuery test = new EmployeeNativeQuery();
    try {
        test.setUp();
        test.query();
        test.tearDown();
    } catch (Exception e) {
        System.out.println(e);
    }
}
}
 -1
Author: Jonathan L,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-18 22:06:17

Prosty sposób na konwersję zapytania SQL do kolekcji klas POJO,

Query query = getCurrentSession().createSQLQuery(sqlQuery).addEntity(Actors.class);
List<Actors> list = (List<Actors>) query.list();
return list;
 -1
Author: Parth Solanki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-21 07:14:46