JPA: comment convertir un jeu de résultats de requêtes natif en collection de classes POJO

J’utilise JPA dans mon projet.

Je suis venu à une requête dans laquelle je dois faire des opérations de jointure sur cinq tables. J’ai donc créé une requête native qui renvoie cinq champs.

Maintenant, je veux convertir l’object résultat en classe Java POJO qui contient les mêmes cinq chaînes.

Y at-il un moyen dans JPA de convertir directement ce résultat en liste d’objects POJO?

Je suis arrivé à la solution suivante ..

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

Maintenant, dans resultClass, devons-nous fournir une classe qui est l’entité JPA réelle? OU Nous pouvons le convertir en n’importe quelle classe JAVA POJO contenant les mêmes noms de colonne?

JPA fournit un SqlResultSetMapping qui vous permet de mapper tous les retours de votre requête native dans une entité. ou une classe personnalisée .

EDIT JPA 1.0 n’autorise pas le mappage vers des classes non-entité. Uniquement dans JPA 2.1, un ConstructorResult a été ajouté aux valeurs de retour d’une classe java.

En outre, pour le problème ColumnResult à l’obtention du compte dans OP, il devrait suffire de définir un mappage de jeu de résultats avec un seul ColumnResult

J’ai trouvé quelques solutions à cela.

Utilisation des entités mappées (JPA 2.0)

Avec JPA 2.0, il n’est pas possible de mapper une requête native à un object POJO, cela ne peut être fait qu’avec une entité.

Par exemple:

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

Mais dans ce cas, Jedi doit être une classe d’entités mappée.

Une alternative pour éviter l’avertissement non contrôlé ici serait d’utiliser une requête native nommée. Donc, si nous déclarons la requête native dans une entité

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

Ensuite, nous pouvons simplement faire:

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

C’est plus sûr, mais nous sums toujours limités à utiliser une entité mappée.

Cartographie manuelle

Une solution que j’ai expérimentée un peu (avant l’arrivée de JPA 2.1) consistait à effectuer une mise en correspondance avec un constructeur POJO en utilisant un peu de reflection.

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

Cette méthode prend essentiellement un tableau de tuple (renvoyé par les requêtes natives) et le mappe sur une classe POJO fournie en recherchant un constructeur qui possède le même nombre de champs et le même type.

Ensuite, nous pouvons utiliser des méthodes commodes comme:

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

Et nous pouvons simplement utiliser cette technique comme suit:

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

JPA 2.1 avec @SqlResultSetMapping

Avec l’arrivée de JPA 2.1, nous pouvons utiliser l’annotation @SqlResultSetMapping pour résoudre le problème.

Nous devons déclarer un ensemble de résultats quelque part dans une entité:

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

Et puis nous faisons simplement:

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

Bien sûr, dans ce cas, Jedi ne doit pas être une entité cartographiée. Cela peut être un POJO régulier.

Utiliser le mappage XML

Je suis un de ceux qui trouvent l’ajout de tous ces @SqlResultSetMapping assez invasifs dans mes entités, et je n’aime pas particulièrement la définition des requêtes nommées au sein des entités, alors je fais tout cela dans le fichier META-INF/orm.xml :

  SELECT name,age FROM jedi_table        

Et ce sont toutes les solutions que je connais. Les deux derniers sont le moyen idéal si nous pouvons utiliser JPA 2.1.

Oui, avec JPA 2.1, c’est facile. Vous avez des annotations très utiles. Ils simplifient votre vie.

Commencez par déclarer votre requête native, puis votre résultat (qui définit le mappage des données renvoyées par la firebase database vers vos POJO). Écrivez votre classe POJO à laquelle vous référer (non incluse ici pour des raisons de concision). Last but not least: créer une méthode dans un DAO (par exemple) pour appeler la requête. Cela a fonctionné pour moi dans une application dropwizard (1.0.0).

Déclarez d’abord une requête native dans une classe d’entité:

 @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 

En dessous, vous pouvez append la déclaration de mappage de résultats:

 @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 = Ssortingng.class) } ) } ) 

Plus tard dans un DAO, vous pouvez vous référer à la requête comme

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

C’est tout.

Déclarez d’abord les annotations suivantes:

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

Ensuite, annotez votre POJO comme suit:

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

Ensuite, écrivez le processeur d’annotation:

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

Utilisez le cadre ci-dessus comme suit:

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

Si vous utilisez Spring-jpa , ceci est un complément aux réponses et à cette question. S’il vous plaît corriger cela si des défauts. J’ai principalement utilisé trois méthodes pour réaliser “un Object[] résultat de mappage Object[] sur un pojo” en fonction de mes besoins pratiques:

  1. La méthode JPA intégrée est suffisante.
  2. La méthode JPA intégrée n’est pas suffisante, mais un fichier sql personnalisé avec son Entity suffit.
  3. L’ancien 2 a échoué et je dois utiliser une nativeQuery . Voici les exemples. Le pojo attendu:

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

Méthode 1 : Modifier le pojo dans une interface:

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

Et repository:

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

Méthode 2 : Référentiel:

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

Remarque: la séquence de parameters du constructeur POJO doit être identique dans les deux définitions POJO et sql.

Méthode 3 : Utilisez @SqlResultSetMapping et @NamedNativeQuery dans Entity comme exemple dans la réponse d’Edwin Dalorzo.

Les deux premières méthodes appellent de nombreux gestionnaires intermédiaires, comme les convertisseurs personnalisés. Par exemple, AntiStealing définit un secretKey , avant qu’il ne soit persisté, un convertisseur est inséré pour le chiffrer. Cela se traduirait par les 2 premières méthodes retournant un secretKey reconverti qui n’est pas ce que je veux. Bien que la méthode 3 secretKey le convertisseur et que secretKey soit renvoyée, secretKey serait identique à celle stockée (cryptée).

Si vous utilisez Spring, vous pouvez utiliser org.springframework.jdbc.core.RowMapper

Voici un exemple:

 public List query(Ssortingng objectType, Ssortingng namedQuery) { Ssortingng 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. } 

Comme d’autres ont déjà mentionné toutes les solutions possibles, je partage ma solution de contournement.

Dans ma situation avec Postgres 9.4 , tout en travaillant avec 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>() {}); 

Je suis sûr que vous pouvez trouver la même chose pour d’autres bases de données.

Aussi, FYI, JPA 2.0 résultats de requête natifs comme carte

Une procédure de défilement peut être effectuée pour atsortingbuer des résultats à une entité autre que l’entité (qui est Beans / POJO). La procédure est la suivante.

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

L’utilisation est pour l’implémentation JPA-Hibernate.

Utilisez le DTO Design Pattern . Il a été utilisé dans EJB 2.0 . L’entité était gérée par conteneur. DTO Design Pattern est utilisé pour résoudre ce problème. Mais, il pourrait être utilisé maintenant, lorsque l’application est développée Server Side et Client Side séparément. DTO est utilisé lorsque le Server side ne veut pas passer / retourner une Entity avec une annotation au Client Side .

Exemple de DTO:

PersonEntity.java

 @Entity public class PersonEntity { @Id private Ssortingng id; private Ssortingng address; public PersonEntity(){ } public PersonEntity(Ssortingng id, Ssortingng address) { this.id = id; this.address = address; } //getter and setter } 

PersonDTO.java

 public class PersonDTO { private Ssortingng id; private Ssortingng address; public PersonDTO() { } public PersonDTO(Ssortingng id, Ssortingng 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 <- ça se passe

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

Voir l’exemple ci-dessous pour utiliser un object POJO en tant que pseudo-entité pour extraire le résultat d’une requête native sans utiliser SqlResultSetMapping complexe. Vous avez juste besoin de deux annotations, un @Enity nu et un @Id mannequin dans votre POJO. @Id peut être utilisé sur n’importe quel champ de votre choix, un champ @Id peut avoir des clés en double mais pas des valeurs nulles.

Puisque @Enity ne correspond à aucune table physique, ce POJO est appelé une pseudo-entité.

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

Vous pouvez télécharger le projet complet maven ici

La requête native est basée sur les employés de l’exemple mysql db http://dev.mysql.com/doc/employee/en/employees-installation.html

persistence.xml

   org.moonwave.jpa.model.pojo.Employee        

Employee.java

 package org.moonwave.jpa.model.pojo; @Entity public class Employee { @Id protected Long empNo; protected Ssortingng firstName; protected Ssortingng lastName; protected Ssortingng title; public Long getEmpNo() { return empNo; } public void setEmpNo(Long empNo) { this.empNo = empNo; } public Ssortingng getFirstName() { return firstName; } public void setFirstName(Ssortingng firstName) { this.firstName = firstName; } public Ssortingng getLastName() { return lastName; } public void setLastName(Ssortingng lastName) { this.lastName = lastName; } public Ssortingng getTitle() { return title; } public void setTitle(Ssortingng title) { this.title = title; } public Ssortingng toSsortingng() { SsortingngBuilder sb = new SsortingngBuilder(); sb.append("empNo: ").append(empNo); sb.append(", firstName: ").append(firstName); sb.append(", lastName: ").append(lastName); sb.append(", title: ").append(title); return sb.toSsortingng(); } } 

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.toSsortingng()); } } public static void main( Ssortingng[] args ) { EmployeeNativeQuery test = new EmployeeNativeQuery(); try { test.setUp(); test.query(); test.tearDown(); } catch (Exception e) { System.out.println(e); } } } 

Un moyen simple de convertir une requête SQL en collection de classes POJO,

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