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

174

W swoim projekcie używam JPA.

Doszedłem do zapytania, w którym muszę wykonać operację łączenia na pięciu tabelach. Utworzyłem więc natywne zapytanie, które zwraca pięć pól.

Teraz chcę przekonwertować obiekt wynikowy na klasę POJO Java, która zawiera te same pięć ciągów.

Czy istnieje sposób w JPA na bezpośrednie rzutowanie tego wyniku na listę 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 jest rzeczywistą jednostką JPA? LUB Możemy go przekonwertować na dowolną klasę JAVA POJO, która zawiera te same nazwy kolumn?

Gunjan Shah
źródło
Sprawdź tę odpowiedź. Zawiera pełną odpowiedź: stackoverflow.com/a/50365522/3073945
Md. Sajedul Karim
używa jpa, a nie spring
on

Odpowiedzi:

103

JPA zapewnia, SqlResultSetMappingktóry umożliwia mapowanie wszystkiego, co zwraca z natywnego zapytania, na jednostkęlub klasę niestandardową.

EDYCJA JPA 1.0 nie zezwala na mapowanie do klas niebędących jednostkami. Tylko w JPA 2.1 ConstructorResult został dodany do odwzorowania wartości zwracanych w klasie java.

Również w przypadku problemu OP z uzyskaniem licznika powinno wystarczyć zdefiniowanie mapowania zestawu wyników za pomocą pojedynczego ColumnResult

Denis Tulskiy
źródło
1
Dziękuję za odpowiedź. Tutaj mapujemy nasz wynik na jednostkę z klasą encji java z adnotacjami „@EntityResult” i „@FieldResult”. W porządku. Ale tutaj potrzebuję większej jasności. Czy jest wymagane, aby klasa, którą mapujemy z wynikiem, była klasą jednostki JPA? A może możemy użyć prostej klasy POJO, która nie jest zakupem encji, która ma wszystkie wymagane zmienne jako kolumny w zestawie wynikowym.
Gunjan Shah
1
@GunjanShah: najlepszym sposobem, aby to wiedzieć, jest wypróbowanie :) również, byt jest tym samym pojo, tylko z kilkoma adnotacjami. tak długo, jak nie będziesz próbować go upierać, pozostanie to pojo.
Denis Tulskiy
2
Kiedy próbowałem tego, pojawił się błąd, że klasa nie była znaną jednostką. Skończyło się na używaniu tego podejścia stackoverflow.com/questions/5024533/ ... zamiast próbować używać natywnego zapytania.
FGreg
2
@EdwinDalorzo: to jest dobre dla jpa 1.0. w jpa 2.1 dodali ConstructorResultjako jeden z parametrów, SqlResultSetMappingktóry pozwala na użycie pojo ze wszystkimi polami ustawionymi w konstruktorze. Zaktualizuję odpowiedź.
Denis Tulskiy
4
Widzę kolejną gorzką prawdę: ConstructorResult może mapować do POJO .. ALE sam ConstructorResult musi znajdować się w klasie Entity, więc Entity nie można uniknąć ... i stąd trudniejszy fakt: potrzebujesz jakiegoś wyniku bez obaw do klucza podstawowego - nadal musisz mieć @Id w Entity ... śmieszne, prawda?
Arnab Dutta
210

Znalazłem kilka rozwiązań tego problemu.

Korzystanie z mapowanych jednostek (JPA 2.0)

Przy użyciu JPA 2.0 nie jest możliwe odwzorowanie natywnego zapytania na POJO, można to zrobić tylko z jednostką.

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, Jedimusi być mapowaną klasą jednostki.

Alternatywą pozwalającą uniknąć tutaj niezaznaczonego ostrzeżenia byłoby użycie nazwanego zapytania natywnego. Więc jeśli zadeklarujemy natywne zapytanie w encji

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

Następnie 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 zmapowanego obiektu.

Ręczne mapowanie

Rozwiązaniem, które trochę eksperymentowałem (przed pojawieniem się JPA 2.1) było mapowanie w konstruktorze POJO z użyciem odrobiny odbicia.

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 zasadniczo pobiera tablicę krotek (zwracaną przez natywne zapytania) i mapuje ją na podaną klasę POJO, szukając konstruktora, który ma taką samą liczbę pól i jest tego samego typu.

Następnie możemy skorzystać z 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ć mapowanie zestawu 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();

Oczywiście w tym przypadku Jedinie musi być mapowaną jednostką. Może to być zwykłe POJO.

Korzystanie z mapowania XML

Należę do tych, dla których dodawanie tych wszystkich danych jest @SqlResultSetMappingdość inwazyjne w moich obiektach, a szczególnie nie podoba mi się definiowanie nazwanych zapytań w jednostkach, więc alternatywnie robię to wszystko w META-INF/orm.xmlpliku:

<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>

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

Edwin Dalorzo
źródło
1
Uwaga: właśnie zastosowałem podejście JPA 2.0 z zależnością JPA2.1 i nie udało mi się. Więc prawdopodobnie nie jest to zgodne z
poprzednimi
1
co masz na myśli mówiąc „gdzieś w jednostce”? Moje Pojo nie jest podmiotem JPA czy nie mogę zadeklarować @SqlResultSetMapping w moim POJO? Interesują mnie rozwiązania JPA 2.1. Proszę, bądź bardziej precyzyjny.
Alboz
3
@Alboz Element @SqlResultSetMappingmusi być umieszczony w encji, ponieważ właśnie z tego JPA będzie czytać metadane. Nie możesz oczekiwać, że JPA sprawdzi twoje POJO. Jednostka, w której umieszczasz mapowanie, jest nieistotna, być może ta, która jest bardziej związana z wynikami POJO. Alternatywnie odwzorowanie mogłoby być wyrażone w XML, aby uniknąć łączenia z całkowicie niepowiązaną jednostką.
Edwin Dalorzo
1
Czy jest możliwe, aby konstruktorresult używał klasy, która ma klasę zagnieżdżoną?
chrismarx
5
Jeśli korzystasz z JPA 2.1 @SqlResultSetMapping, warto zauważyć, że Jediklasa będzie wymagać konstruktora złożonego z wszystkich argumentów, a @ColumnResultadnotacja może wymagać typedodania atrybutu do konwersji, co może nie być niejawne (musiałem dodać type = ZonedDateTime.classdla niektórych kolumn).
Glenn
11

Tak, z JPA 2.1 jest to łatwe. Masz bardzo przydatne adnotacje. Upraszczają Twoje życie.

Najpierw zadeklaruj swoje natywne zapytanie, a następnie mapowanie zestawu wyników (które definiuje mapowanie danych zwróconych przez bazę danych do Twoich obiektów POJO). Napisz swoją klasę POJO, do której chcesz się odwoływać (nie uwzględniono tutaj dla zwięzłości). Last but not least: utwórz metodę w DAO (na przykład), aby wywołać zapytanie. To zadziałało dla mnie w aplikacji dropwizard (1.0.0).

Najpierw zadeklaruj natywne zapytanie w klasie jednostki:

@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

Poniżej możesz dodać deklarację mapowania zestawu wyników:

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

Otóż ​​to.

GiulioDS
źródło
Dobra odpowiedź, ale myślę, że przegapiłeś nawias po pierwszej adnotacji @ColumnResult.
mwatzer
W kodzie są błędy, ale łatwe do poprawienia. Na przykład: „resulSetMapping =” powinno być „resultSetMapping =”
Zbyszek,
3
Widzę kolejną gorzką prawdę: NamedNativeQuery i SqlResultSetMapping muszą być w klasie
@Entity
10

Jeśli używasz Spring-jpa, jest to uzupełnienie odpowiedzi i tego pytania. Popraw to, jeśli są jakieś wady. Użyłem głównie trzech metod, aby osiągnąć „mapowanie wyniku Object[]do pojo” w zależności od praktycznej potrzeby, jaką spełniam:

  1. Wystarczy wbudowana metoda JPA.
  2. JPA zbudowany w sposób nie wystarczy, ale dostosowane sqlz jego Entitywystarczą.
  3. Poprzedni 2 zawiódł i muszę użyć nativeQuery. Oto przykłady. Oczekiwane 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;
        }
    }

Metoda 1 : Zmień 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 sql.

Metoda 3 : Użyj @SqlResultSetMappingi @NamedNativeQueryw Entityjako przykład w odpowiedzi na Edwina Dalorzo.

Pierwsze dwie metody wywoływałyby wiele pośrednich programów obsługi, takich jak niestandardowe konwertery. Na przykład AntiStealingdefiniuje a secretKey, zanim zostanie utrwalony, zostanie wstawiony konwerter, aby go zaszyfrować. Spowodowałoby to, że pierwsze dwie metody zwracałyby przekonwertowany powrót, secretKeyco nie jest tym, czego chcę. Chociaż metoda 3 pokonałaby konwerter, a zwrócona secretKeybyłaby taka sama, jak jest przechowywana (zaszyfrowana).

Tiina
źródło
Metoda 1 w rzeczywistości nie wymaga Springa i działa z czystą Hibernate.
Martin Vysny
@MartinVysny tak, M1 to JPQL. każdy projekt implementujący JPQL powinien go obsługiwać. W ten sposób może M2 jest również szeroko obsługiwany?
Tiina
8

Można wykonać procedurę rozpakowywania, aby przypisać wyniki do obiektu niebędącego jednostką (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 dotyczy implementacji JPA-Hibernate.

zawhtut
źródło
zauważ, że JobDTOpowinien mieć domyślny konstruktor. Lub możesz zaimplementować własny transformator w oparciu o AliasToBeanResultTransformerimplementację.
Lu55,
4

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ższej struktury 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);
riship89
źródło
Który pakiet jest BeanUtilsw środku?
Harish
4

Najprościej skorzystać z tak rzutów . Może mapować wyniki zapytania bezpośrednio do interfejsów i jest łatwiejszy do wdrożenia niż użycie SqlResultSetMapping.

Przykład jest pokazany poniżej:

@Repository
public interface PeopleRepository extends JpaRepository<People, Long> {

    @Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
        "FROM people p INNER JOIN dream_people dp " +
        "ON p.id = dp.people_id " +
        "WHERE p.user_id = :userId " +
        "GROUP BY dp.people_id " +
        "ORDER BY p.name", nativeQuery = true)
    List<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId);

    @Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
        "FROM people p INNER JOIN dream_people dp " +
        "ON p.id = dp.people_id " +
        "WHERE p.user_id = :userId " +
        "GROUP BY dp.people_id " +
        "ORDER BY p.name", nativeQuery = true)
    Page<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId, Pageable pageable);

}



// Interface to which result is projected
public interface PeopleDTO {

    String getName();

    Long getCount();

}

Pola z przewidywanego interfejsu muszą być zgodne z polami w tej encji. W przeciwnym razie mapowanie pól może się zepsuć.

Również jeśli używasz SELECT table.column notacji, zawsze definiuj aliasy pasujące do nazw z encji, jak pokazano w przykładzie.

Thanthu
źródło
1
natywne zapytanie i projekcje nie pasują do siebie.
Kevin Rave
1
Nie mogłem dostać odwzorowanie pola do pracy rację - większość wartości wracali za nieważną
Ayang
4

W hibernacji możesz użyć tego kodu, aby łatwo zmapować swoje natywne zapytanie.

private List < Map < String, Object >> getNativeQueryResultInMap() {
String mapQueryStr = "SELECT * FROM AB_SERVICE three ";
Query query = em.createNativeQuery(mapQueryStr);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List < Map < String, Object >> result = query.getResultList();
for (Map map: result) {
    System.out.println("after request  ::: " + map);
}
return result;}
Yaswanth raju Vysyaraju
źródło
2

Korzystanie z hibernacji:

@Transactional(readOnly=true)
public void accessUser() {
EntityManager em = repo.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u").addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE).addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE).addScalar("loginType", IntegerType.INSTANCE)
        .setResultTransformer(Transformers.aliasToBean(User2DTO.class));

    List<User2DTO> userList = q.list();
}
Rubens
źródło
2

Stary styl przy użyciu zestawu wyników

@Transactional(readOnly=true)
public void accessUser() {
    EntityManager em = this.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    session.doWork(new Work() {
        @Override
        public void execute(Connection con) throws SQLException {
            try (PreparedStatement stmt = con.prepareStatement(
                    "SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")) {
                ResultSet rs = stmt.executeQuery();
                ResultSetMetaData rsmd = rs.getMetaData();
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    System.out.print(rsmd.getColumnName(i) + " (" + rsmd.getColumnTypeName(i) + ") / ");
                }
                System.out.println("");
                while (rs.next()) {
                    System.out.println("Found username " + rs.getString("USERNAME") + " name " + rs.getString("NAME") + " email " + rs.getString("EMAIL") + " passe " + rs.getString("PASSE") + " email " + rs.getInt("LOGIN_TYPE"));
                }
            }
        }
    });
}
Rubens
źródło
1

Ponieważ inni już wymienili wszystkie możliwe rozwiązania, udostępniam moje rozwiązanie tymczasowe.

W mojej sytuacji z Postgres 9.4, pracując 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ż natywne wyniki zapytań FYI, JPA 2.0 jako mapa

Darshan Patel
źródło
1

Nie jestem pewien, czy to tutaj pasuje, ale miałem podobne pytanie i znalazłem dla mnie następujące proste rozwiązanie / przykład:

private EntityManager entityManager;
...
    final String sql = " SELECT * FROM STORE "; // select from the table STORE
    final Query sqlQuery = entityManager.createNativeQuery(sql, Store.class);

    @SuppressWarnings("unchecked")
    List<Store> results = (List<Store>) sqlQuery.getResultList();

W moim przypadku musiałem użyć części SQL zdefiniowanych w Strings w innym miejscu, więc nie mogłem po prostu użyć NamedNativeQuery.

Andreas L.
źródło
gdy tylko powracamy byt. nic fajnego. Problem występuje, gdy próbujesz zmapować wynik do niezarządzanego POJO.
Olgun Kaya,
1

Stary styl przy użyciu zestawu wyników

@Transactional(readOnly=true)
public void accessUser() {
    EntityManager em = this.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    session.doWork(new Work() {
        @Override
        public void execute(Connection con) throws SQLException {
            try (PreparedStatement stmt = con.prepareStatement(
                    "SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")) {
                ResultSet rs = stmt.executeQuery();
                ResultSetMetaData rsmd = rs.getMetaData();
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    System.out.print(rsmd.getColumnName(i) + " (" + rsmd.getColumnTypeName(i) + ") / ");
                }
                System.out.println("");
                while (rs.next()) {
                    System.out.println("Found username " + rs.getString("USERNAME") + " name " + rs.getString("NAME") + " email " + rs.getString("EMAIL") + " passe " + rs.getString("PASSE") + " email " + rs.getInt("LOGIN_TYPE"));
                }
            }
        }
    });
}
Rubens
źródło
1

Rozwiązaliśmy problem w następujący sposób:

   //Add actual table name here in Query
    final String sqlQuery = "Select a.* from ACTORS a"
    // add your entity manager here 
    Query query = entityManager.createNativeQuery(sqlQuery,Actors.class);
    //List contains the mapped entity data.
    List<Actors> list = (List<Actors>) query.getResultList();
Akash
źródło
0

Zobacz przykład poniżej, jak używać POJO jako pseudo encji do pobierania wyników z natywnego zapytania bez użycia złożonego SqlResultSetMapping. Potrzebujesz tylko dwóch adnotacji, gołego @Enity i fikcyjnego @Id w swoim POJO. @Id można użyć w dowolnym wybranym polu, pole @Id może mieć zduplikowane klucze, ale nie może zawierać wartości null.

Ponieważ @Enity nie mapuje na żadną fizyczną tabelę, więc ten POJO nazywany jest pseudoobiektem.

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

Możesz pobrać cały projekt maven tutaj

Natywne zapytanie jest oparte na przykładowych pracownikach mysql db http://dev.mysql.com/doc/employee/en/employees-installation.html

persistence.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>

Employee.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);
    }
}
}
Jonathan L.
źródło
1
Skoro twoja listjest rzekomo listą Employee, dlaczego twoja pętla for-each iteruje po typie Object? Jeśli napiszesz pętlę for-each, ponieważ for(Employee emp : list)wtedy odkryjesz, że twoja odpowiedź jest błędna, a zawartość listy nie dotyczy pracowników, a to ostrzeżenie, które ukryłeś, miało na celu ostrzeżenie cię o tym potencjalnym błędzie.
Edwin Dalorzo
@SuppressWarnings („unchecked”) służy do pomijania ostrzeżeń, ponieważ opcja List<Employee> list = (List<Employee>) query.getResultList();Zmień for (Object emp : list)na for (Employee emp : list)jest lepsza, ale nie ma błędów, jeśli jest przechowywana, Object empponieważ lista jest wystąpieniem List<Employee>. Zmieniłem kod w projekcie git, ale nie tutaj, aby Twój komentarz odnosił się do oryginalnego postu
Jonathan L
Problem polega na tym, że twoje zapytanie nie zwraca listy pracowników, ale tablicę obiektów. Twoje stłumione ostrzeżenie to ukrywa. W momencie, gdy spróbujesz przekonwertować którekolwiek z nich na pracownika, pojawi się błąd, wyjątek rzutowania.
Edwin Dalorzo
Spójrz na Query query = em.createNativeQuery("select * ...", Employee.class);i persistence.xml, natywne zapytanie zwraca listę Pracownik. Właśnie sprawdziłem i uruchomiłem projekt bez problemu. Jeśli ustawisz lokalnie bazę danych pracowników przykładowych mysql, powinieneś być w stanie uruchomić projekt
Jonathan L
Och, rozumiem, co masz teraz na myśli. Ale w tym przypadku twoja odpowiedź nie odpowiada na pytanie, ponieważ chodziło o użycie zwykłego POJO jako obiektu docelowego, a twoja odpowiedź to użycie, Employeejak przypuszczam, bytu. Prawda?
Edwin Dalorzo
0

jeśli używasz Spring, 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.
} 
Pallab Rath
źródło
0

Korzystanie z hibernacji:

@Transactional(readOnly=true)
public void accessUser() {
    EntityManager em = repo.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")
        .addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE)
        .addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE)
        .addScalar("loginType", IntegerType.INSTANCE)
        .setResultTransformer(Transformers.aliasToBean(User2DTO.class));

    List<User2DTO> userList = q.list();
}
Rubens
źródło
-1

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;
Parth Solanki
źródło
-1

Wszystko czego potrzebujesz to DTO z konstruktorem:

public class User2DTO implements Serializable {

    /** pode ser email ou id do Google comecando com G ou F para Facebook */
    private String username;

    private String password;

    private String email;

    private String name;

    private Integer loginType;

    public User2DTO(Object...fields) {
        super();
        this.username = (String) fields[0];
        this.name = (String) fields[1];
        this.email = (String) fields[2];
        this.password = (String) fields[3];
        this.loginType = (Integer) fields[4];
    }

i nazwij to:

EntityManager em = repo.getEntityManager();
        Query q = em.createNativeQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u");
        List<Object[]> objList = q.getResultList();
        List<User2DTO> ooBj = objList.stream().map(User2DTO::new).collect(Collectors.toList());
Rubens
źródło
Dodaj nową kolumnę, a kod się zepsuje.
Danie
-2

Użyj DTO Design Pattern. Został użyty w EJB 2.0. Jednostka była zarządzana przez kontener. DTO Design Patternsłuży do rozwiązania tego problemu. Ale może być używany teraz, gdy aplikacja jest rozwijana Server Sidei Client Sideoddzielnie. DTOjest używany, gdy Server sidenie chce przekazać / powrócić Entityz adnotacją do Client Side.

Przykład DTO:

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 <- to mide be need

public class EntityBuilder() {
    public static PersonEntity buildPersonEntity(PersonDTO person) {
        return new PersonEntity(person.getId(). person.getAddress());
    }
}
Zaw Than oo
źródło
4
Dzięki za odpowiedź, tutaj nie potrzebuję wzorca DTO. Moim wymaganiem jest, aby nie ukrywać szczegółów adnotacji przed klientem. Więc nie muszę tworzyć jeszcze jednego POJO w mojej aplikacji. Moim wymaganiem jest rzutowanie zestawu wyników na qa pojo, który nie jest jednostką JAVA, ale prostą klasą POJO, która ma takie same pola jak kolumny zestawu wyników.
Gunjan Shah