JPA: come convertire un set di risultati di query native in una raccolta di classi POJO

Sto usando JPA nel mio progetto.

Sono venuto a una query in cui ho bisogno di fare operazione di join su cinque tavoli. Così ho creato una query nativa che restituisce cinque campi.

Ora voglio convertire l’object risultato in una class POJO java che contiene le stesse cinque stringhe.

C’è un modo in JPA per trasmettere direttamente quel risultato alla lista degli oggetti POJO ??

Sono venuto alla seguente soluzione ..

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

Ora qui in resultClass, dobbiamo fornire una class che sia un’entity framework JPA effettiva? O Possiamo convertirlo in qualsiasi class JAVA POJO che contenga gli stessi nomi di colonna?

JPA fornisce un SqlResultSetMapping che ti consente di mappare qualsiasi ritorno dalla tua query nativa in un’ quadro o una lezione personalizzata .

EDIT JPA 1.0 non consente il mapping a classi non quadro. Solo in JPA 2.1 è stato aggiunto ConstructorResult per mappare i valori restituiti una class java.

Inoltre, per il problema dell’OP relativo al conteggio, dovrebbe essere sufficiente definire una mapping del set di risultati con un singolo ColumnResult

Ho trovato un paio di soluzioni a questo.

Utilizzo di entity framework mappate (JPA 2.0)

Utilizzando JPA 2.0 non è ansible mappare una query nativa a un POJO, può essere fatto solo con un’ quadro.

Per esempio:

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

Ma in questo caso, Jedi , deve essere una class quadro mappata.

Un’alternativa per evitare l’avviso non controllato qui, sarebbe utilizzare una query nativa con nome. Quindi se dichiariamo la query nativa in un’entity framework

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

Quindi, possiamo semplicemente fare:

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

Questo è più sicuro, ma siamo ancora costretti a utilizzare un’ quadro mappata.

Mappatura manuale

Una soluzione che ho sperimentato un po ‘(prima dell’arrivo di JPA 2.1) stava facendo il mapping con un costruttore POJO usando un po’ di riflessione.

 public static  T map(Class type, Object[] tuple){ List> tupleTypes = new ArrayList<>(); for(Object field : tuple){ tupleTypes.add(field.getClass()); } try { Constructor ctor = type.getConstructor(tupleTypes.toArray(new Class< ?>[tuple.length])); return ctor.newInstance(tuple); } catch (Exception e) { throw new RuntimeException(e); } } 

Questo metodo utilizza fondamentalmente un array di tuple (restituito da query native) e lo associa a una class POJO fornita cercando un costruttore con lo stesso numero di campi e dello stesso tipo.

Quindi possiamo usare metodi convenienti come:

 public static  List map(Class type, List records){ List result = new LinkedList<>(); for(Object[] record : records){ result.add(map(type, record)); } return result; } public static  List getResultList(Query query, Class type){ @SuppressWarnings("unchecked") List records = query.getResultList(); return map(type, records); } 

E possiamo semplicemente usare questa tecnica come segue:

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

JPA 2.1 con @SqlResultSetMapping

Con l’arrivo di JPA 2.1, possiamo usare l’annotazione @SqlResultSetMapping per risolvere il problema.

Dobbiamo dichiarare una mapping dei set di risultati da qualche parte in un’entity framework:

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

E poi facciamo semplicemente:

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

Naturalmente, in questo caso Jedi non ha bisogno di essere un’ quadro mappata. Può essere un POJO regolare.

Utilizzo della mapping XML

Sono uno di quelli che trovano l’aggiunta di tutte queste @SqlResultSetMapping piuttosto invasive nelle mie quadro, e in particolare non mi piace la definizione delle query denominate all’interno delle entity framework, quindi in alternativa faccio tutto ciò nel file META-INF/orm.xml :

  SELECT name,age FROM jedi_table        

E quelle sono tutte le soluzioni che conosco. Gli ultimi due sono il modo ideale se possiamo usare JPA 2.1.

Sì, con JPA 2.1 è facile. Hai annotazioni molto utili. Semplificano la tua vita.

Prima dichiarare la propria query nativa, quindi il mapping del set di risultati (che definisce la mapping dei dati restituiti dal database ai POJO). Scrivi la tua class POJO a cui fare riferimento (non inclusa qui per brevità). Ultimo ma non meno importante: creare un metodo in un DAO (ad esempio) per chiamare la query. Questo ha funzionato per me in un’app dropwizard (1.0.0).

Prima dichiarare una query nativa in una class di quadro:

 @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 

Sotto puoi aggiungere la dichiarazione di mapping dei risultati:

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

Più tardi in un DAO puoi fare riferimento alla query come

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

Questo è tutto.

Prima dichiarare le seguenti annotazioni:

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

Quindi annota il tuo POJO come segue:

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

Quindi scrivere il processore di annotazione:

 public class NativeQueryResultsMapper { private static Logger log = LoggerFactory.getLogger(NativeQueryResultsMapper.class); public static  List map(List objectArrayList, Class genericType) { List ret = new ArrayList(); List 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  List getNativeQueryResultColumnAnnotatedFields(Class genericType) { Field[] fields = genericType.getDeclaredFields(); List 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; } } 

Usa la struttura sopra come segue:

 String sql = "select a,b from x order by a"; Query q = entityManager.createNativeQuery(sql); List results = NativeQueryResultsMapper.map(q.getResultList(), ClassX.class); 

Se usi Spring-jpa , questo è un supplemento alle risposte e questa domanda. Si prega di correggere questo se eventuali difetti. Ho usato principalmente tre metodi per ottenere ” Object[] risultato mapping Object[] in un pojo” in base a quali esigenze pratiche ho incontrato:

  1. Il metodo integrato JPA è sufficiente.
  2. Il metodo integrato JPA non è sufficiente, ma è sufficiente un sql personalizzato con la relativa Entity .
  3. Il primo 2 non è riuscito e devo usare un nativeQuery . Ecco gli esempi. Il pojo atteso:

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

Metodo 1 : cambia il pojo in un’interfaccia:

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

E repository:

 interface AntiStealingRepository extends CrudRepository { Antistealingdto findById(Long id); } 

Metodo 2 : repository:

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

Nota: la sequenza di parametri del costruttore POJO deve essere identica sia nella definizione POJO che in sql.

Metodo 3 : utilizzare @SqlResultSetMapping e @NamedNativeQuery in Entity come esempio nella risposta di Edwin Dalorzo.

I primi due metodi chiamerebbero molti gestori in-the-middle, come i convertitori personalizzati. Ad esempio, AntiStealing definisce un secretKey , prima che venga mantenuto, viene inserito un convertitore per crittografarlo. Ciò risulterebbe nei primi 2 metodi che restituiscono un secretKey posteriore convertito che non è quello che voglio. Mentre il metodo 3 avrebbe superato il convertitore e restituito secretKey sarebbe lo stesso come è memorizzato (uno crittografato).

se si utilizza Spring, è ansible utilizzare org.springframework.jdbc.core.RowMapper

Ecco un esempio:

 public List query(String objectType, String namedQuery) { String rowMapper = objectType + "RowMapper"; // then by reflection you can instantiate and use. The RowMapper classs need to follow the naming specific convention to follow such implementation. } 

Poiché altri hanno già menzionato tutte le possibili soluzioni, sto condividendo la mia soluzione alternativa.

Nella mia situazione con Postgres 9.4 , mentre lavoravo con Jackson ,

 //Convert it to named native query. List list = em.createNativeQuery("select cast(array_to_json(array_agg(row_to_json(a))) as text) from myschema.actors a") .getResultList(); List map = new ObjectMapper().readValue(list.get(0), new TypeReference>() {}); 

Sono sicuro che puoi trovare lo stesso per altri database.

Anche FYI, risultati della query nativa JPA 2.0 come mappa

La procedura Unwrap può essere eseguita per assegnare risultati a non quadro (che è Beans / POJO). La procedura è la seguente.

 List dtoList = entityManager.createNativeQuery(sql) .setParameter("userId", userId) .unwrap(org.hibernate.Query.class).setResultTransformsr(Transformsrs.aliasToBean(JobDTO.class)).list(); 

L’utilizzo è per l’implementazione JPA-Hibernate.

Usa DTO Design Pattern . È stato utilizzato in EJB 2.0 . L’ quadro era gestita dal contenitore. DTO Design Pattern è usato per risolvere questo problema. Ma potrebbe essere usato ora, quando l’applicazione è sviluppata Server Side e Client Side separatamente. DTO viene utilizzato quando il Server side non vuole passare / restituire Entity con annotazione sul Client Side .

Esempio 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 < - è necessario

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

Vedere l’esempio seguente per utilizzare un POJO come pseudoentity framework per recuperare il risultato dalla query nativa senza utilizzare SqlResultSetMapping complesso. Servono solo due annotazioni, una nuda @Enity e una fittizia @Id nel POJO. @Id può essere utilizzato su qualsiasi campo di tua scelta, un campo @Id può avere chiavi duplicate ma non valori nulli.

Poiché @Enity non esegue il mapping su alcuna tabella fisica, quindi questo POJO viene chiamato una pseudo quadro.

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

È ansible scaricare qui il progetto Maven completo

La query nativa si basa su dipendenti di MySQL db http://dev.mysql.com/doc/employee/en/employees-installation.html

persistence.xml

 < ?xml version="1.0" encoding="UTF-8"?>  org.moonwave.jpa.model.pojo.Employee        

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 list = (List) 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); } } } 

Modo semplice per convertire query SQL in raccolte di classi POJO,

 Query query = getCurrentSession().createSQLQuery(sqlQuery).addEntity(Actors.class); List list = (List) query.list(); return list;