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

174

J'utilise JPA dans mon projet.

Je suis arrivé à une requête dans laquelle je dois effectuer une opération de jointure sur cinq tables. J'ai donc créé une requête native qui renvoie cinq champs.

Maintenant, je veux convertir l'objet de résultat en classe java POJO qui contient les cinq mêmes chaînes.

Existe-t-il un moyen dans JPA de convertir directement ce résultat en liste d'objets POJO?

Je suis venu à 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, ici dans resultClass, devons-nous fournir une classe qui est une entité JPA réelle? OU Nous pouvons le convertir en n'importe quelle classe JAVA POJO qui contient les mêmes noms de colonnes?

Gunjan Shah
la source
Vérifiez cette réponse. Il a une réponse complète: stackoverflow.com/a/50365522/3073945
Md. Sajedul Karim
il utilise jpa, pas printemps
lui

Réponses:

103

JPA fournit un SqlResultSetMappingqui 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é pour mapper les valeurs de retour d'une classe java.

En outre, pour le problème de l'OP avec l'obtention du nombre, il devrait être suffisant de définir un mappage d'ensemble de résultats avec un seul ColumnResult

Denis Tulskiy
la source
1
Merci pour la réponse. Ici, nous mappons notre résultat avec l'entité avec la classe d'entité java avec les annotations "@EntityResult" et "@FieldResult". C'est très bien. Mais ici, j'ai besoin de plus de clarté. Faut-il que la classe que nous mappons avec le résultat soit une classe d'entité JPA? OU pouvons-nous utiliser une classe POJO simple qui n'est pas un achat d'entité qui a toutes les variables requises comme colonnes dans le jeu de résultats.
Gunjan Shah
1
@GunjanShah: le meilleur moyen de le savoir est de faire un essai :) aussi, une entité est juste le même pojo, juste avec quelques annotations. tant que vous n'essayez pas de le persister, cela restera un pojo.
Denis Tulskiy
2
Quand j'ai essayé cela, j'ai eu une erreur indiquant que la classe n'était pas une entité connue. J'ai fini par utiliser cette approche stackoverflow.com/questions/5024533/… au lieu d'essayer d'utiliser une requête native.
FGreg
2
@EdwinDalorzo: c'est parfait pour jpa 1.0. dans jpa 2.1, ils ont ajouté en ConstructorResulttant que paramètre à SqlResultSetMappingcela permet d'utiliser un pojo avec tous les champs définis dans le constructeur. Je mettrai à jour la réponse.
Denis Tulskiy
4
Je vois une autre vérité amère: ConstructorResult peut mapper sur un POJO .. MAIS ConstructorResult lui-même doit être dans la classe Entity donc Entity que vous ne pouvez pas éviter ... et par conséquent le plus gros fait: vous avez besoin d'un résultat sans vous soucier à la clé primaire - vous devez toujours avoir @Id dans Entity ... ridicule non?
Arnab Dutta
210

J'ai trouvé quelques solutions à cela.

Utilisation d'entités mappées (JPA 2.0)

En utilisant JPA 2.0, il n'est pas possible de mapper une requête native à un 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<Jedi> items = (List<Jedi>) query.getResultList();

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

Une alternative pour éviter l'avertissement non coché 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<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();

C'est plus sûr, mais nous sommes 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 à faire du mapping contre un constructeur POJO en utilisant un peu de réflexion.

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

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

Ensuite, nous pouvons utiliser des méthodes pratiques telles que:

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

Et nous pouvons simplement utiliser cette technique comme suit:

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> 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 mappage d'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<Jedi> samples = query.getResultList();

Bien sûr, dans ce cas, Jediil n'est pas nécessaire d'être une entité mappée. Cela peut être un POJO ordinaire.

Utilisation du mappage XML

Je fais partie de ceux qui trouvent que l'ajout de tous ces éléments est @SqlResultSetMappingassez invasif 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 META-INF/orm.xmlfichier:

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

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

Edwin Dalorzo
la source
1
Note de bas de page: Je viens d'utiliser l'approche JPA 2.0 avec la dépendance JPA2.1, et cela a échoué. Donc probablement ce n'est pas compatible vers le bas ...
Membersound
1
qu'entendez-vous par «quelque part dans une entité»? Mon Pojo n'est pas une entité JPA, puis-je déclarer le @SqlResultSetMapping dans mon POJO? Je suis intéressé par les solutions JPA 2.1. Veuillez être un peu plus précis.
Alboz
3
@Alboz Le @SqlResultSetMappingdoit être placé dans une entité car c'est à partir de là que JPA va lire les métadonnées. Vous ne pouvez pas vous attendre à ce que JPA inspecte vos POJO. L'entité dans laquelle vous placez le mappage n'est pas pertinente, peut-être celle qui est le plus liée à vos résultats POJO. Alternativement, le mappage pourrait être exprimé en XML pour éviter le couplage avec une entité totalement indépendante.
Edwin Dalorzo
1
Est-il possible pour le résultat du constructeur d'utiliser une classe qui a une classe imbriquée?
chrismarx
5
Si vous utilisez JPA 2.1 avec, @SqlResultSetMappingil peut être intéressant de noter que la Jediclasse nécessitera un constructeur all-arg et que l' @ColumnResultannotation peut nécessiter l' typeajout de l' attribut aux conversions qui pourraient ne pas être implicites (j'avais besoin d'ajouter type = ZonedDateTime.classpour certaines colonnes).
Glenn
11

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

Déclarez d'abord votre requête native, puis votre mappage d'ensemble de résultats (qui définit le mappage des données renvoyées par la base de données vers vos POJO). Écrivez votre classe POJO à laquelle vous référer (non incluse ici par souci de concision). Dernier point mais non le moindre: créez 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 ajouter la déclaration de mappage du jeu 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 = String.class)
          }
      )
   } 
)

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

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

C'est tout.

GiulioDS
la source
Bonne réponse, mais je pense que vous avez manqué une parenthèse après la première annotation @ColumnResult.
mwatzer le
Il y a des erreurs dans le code, mais faciles à corriger. Par exemple: "resulSetMapping =" devrait être "resultSetMapping ="
Zbyszek
3
Je vois une autre vérité amère: NamedNativeQuery & SqlResultSetMapping doit être dans une classe @Entity
Arnab Dutta
10

Si vous utilisez Spring-jpa, ceci est un complément aux réponses et à cette question. Veuillez corriger cela en cas de défaut. J'ai principalement utilisé trois méthodes pour obtenir un "résultat de mappage Object[]vers un pojo" en fonction du besoin pratique que je rencontre:

  1. La méthode intégrée JPA est suffisante.
  2. La méthode intégrée JPA ne suffit pas, mais une personnalisation sqlavec son Entitysuffit.
  3. L'ancien 2 a échoué, et je dois utiliser un fichier nativeQuery. Voici les exemples. Le pojo attendait:

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

Méthode 1 : changez le pojo en une interface:

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

Et référentiel:

interface AntiStealingRepository extends CrudRepository<Antistealing, Long> {
    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 paramètres du constructeur POJO doit être identique à la fois dans la définition POJO et dans SQL.

Méthode 3 : Utilisez @SqlResultSetMappinget @NamedNativeQueryin Entitycomme exemple dans la réponse d'Edwin Dalorzo.

Les deux premières méthodes appelleraient de nombreux gestionnaires intermédiaires, comme des convertisseurs personnalisés. Par exemple, AntiStealingdéfinit un secretKey, avant qu'il ne soit persistant, un convertisseur est inséré pour le chiffrer. Cela entraînerait les 2 premières méthodes renvoyant un retour converti secretKeyqui n'est pas ce que je veux. Alors que la méthode 3 surmonterait le convertisseur, et renvoyée secretKeyserait la même qu'elle est stockée (cryptée).

Tiina
la source
La méthode 1 ne nécessite pas réellement Spring et fonctionne avec Hibernate pur.
Martin Vysny
@MartinVysny oui, M1 est JPQL. tout projet implémente JPQL doit le supporter. De cette façon, peut-être que M2 est également largement pris en charge?
Tiina
8

La procédure de déballage peut être effectuée pour attribuer des résultats à une non-entité (qui est Beans / POJO). La procédure est la suivante.

List<JobDTO> 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.

zawhtut
la source
notez que JobDTOdevrait avoir le constructeur par défaut. Ou vous pouvez implémenter votre propre transformateur en fonction de l' AliasToBeanResultTransformerimplémentation.
Lu55
4

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

Puis annotez votre POJO comme suit:

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

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

Ensuite, écrivez le processeur d'annotations:

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

Utilisez le cadre ci-dessus comme suit:

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
la source
Quel est le paquet BeanUtils?
Harish
4

Le moyen le plus simple est d'utiliser ainsi des projections . Il peut mapper les résultats de la requête directement aux interfaces et est plus facile à implémenter que l'utilisation de SqlResultSetMapping.

Un exemple est présenté ci-dessous:

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

}

Les champs de l'interface projetée doivent correspondre aux champs de cette entité. Sinon, le mappage de champ pourrait être interrompu.

De plus, si vous utilisez la SELECT table.columnnotation, définissez toujours des alias correspondant aux noms d'entité comme indiqué dans l'exemple.

Thanthu
la source
1
la requête native et les projections ne vont pas bien ensemble.
Kevin Rave
1
Je n'ai pas pu faire fonctionner correctement le mappage des champs - la plupart des valeurs revenaient toujours comme nulles
ayang
4

En veille prolongée, vous pouvez utiliser ce code pour mapper facilement votre requête native.

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
la source
2

Utilisation de Hibernate:

@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
la source
2

Ancien style avec ResultSet

@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
la source
1

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

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

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

Aussi FYI, résultats de requêtes natives JPA 2.0 sous forme de carte

Darshan Patel
la source
1

Je ne sais pas si cela correspond ici, mais j'avais une question similaire et j'ai trouvé la solution / exemple simple suivante pour moi:

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

Dans mon cas, j'ai dû utiliser des parties SQL définies dans Strings ailleurs, donc je ne pouvais pas simplement utiliser NamedNativeQuery.

Andreas L.
la source
dès que nous retournons l'entité. Rien d'extraordinaire. Le problème est lorsque vous essayez de mapper le résultat à un POJO non géré.
Olgun Kaya
1

Ancien style à l'aide du jeu de résultats

@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
la source
1

Nous avons résolu le problème de la manière suivante:

   //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
la source
0

Voir l'exemple ci-dessous pour utiliser un POJO comme pseudo-entité pour récupérer le résultat d'une requête native sans utiliser SqlResultSetMapping complexe. Juste besoin de deux annotations, un @Enity nu et un @Id factice 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 maven complet ici

La requête native est basée sur les exemples d'employés 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
la source
1
Puisque votre listest, prétendument, une liste de Employee, pourquoi votre boucle for-each itère sur un type Object? Si vous écrivez votre boucle for-each comme cela, for(Employee emp : list)vous découvrirez que votre réponse est fausse et que le contenu de votre liste n'est pas des employés et que cet avertissement que vous avez supprimé avait pour but de vous alerter de cette erreur potentielle.
Edwin Dalorzo
@SuppressWarnings ("non coché") est utilisé pour supprimer l'avertissement pour List<Employee> list = (List<Employee>) query.getResultList();Change for (Object emp : list)to for (Employee emp : list)is better, mais aucune erreur si elle est conservée Object empcar la liste est une instance de List<Employee>. J'ai changé le code dans le projet git mais pas ici pour garder votre commentaire pertinent par rapport à l'article original
Jonathan L
le problème est que votre requête ne renvoie pas une liste d'employés, mais un tableau d'objets. Votre avertissement supprimé cache cela. Au moment où vous essayez de convertir l'un de ceux-ci en employé, vous obtiendrez une erreur, une exception de conversion.
Edwin Dalorzo
Regardez Query query = em.createNativeQuery("select * ...", Employee.class);et persistence.xml, la requête native renvoie une liste d'employés. Je viens de vérifier et d'exécuter le projet sans problème. Si vous configurez la base de données des exemples d'employés mysql localement, vous devriez également pouvoir exécuter le projet
Jonathan L
Oh je vois ce que tu veux dire maintenant. Mais dans ce cas, votre réponse ne répond pas à la question, car il s'agissait d'utiliser un POJO normal comme objet cible, et votre réponse utilise Employeece que je suppose être une entité. N'est-ce pas?
Edwin Dalorzo
0

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

Voici un exemple:

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
la source
0

Utilisation de Hibernate:

@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
la source
-1

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

Query query = getCurrentSession().createSQLQuery(sqlQuery).addEntity(Actors.class);
List<Actors> list = (List<Actors>) query.list();
return list;
Parth Solanki
la source
-1

Tout ce dont vous avez besoin est un DTO avec un constructeur:

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

et appelez-le:

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
la source
Ajoutez une nouvelle colonne et le code se cassera.
Plat du
-2

Utilisez DTO Design Pattern. Il a été utilisé dans EJB 2.0. L'entité était gérée par conteneur. DTO Design Patternest utilisé pour résoudre ce problème. Mais, il pourrait être utilisé maintenant, lorsque l'application est développée Server Sideet Client Sideséparément. DTOest utilisé lorsque Server sidene veut pas passer / retourner Entityavec une annotationClient Side .

Exemple 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 <- il mide be need

public class EntityBuilder() {
    public static PersonEntity buildPersonEntity(PersonDTO person) {
        return new PersonEntity(person.getId(). person.getAddress());
    }
}
Zaw Than oo
la source
4
Merci pour la réponse. Ici, je n'ai pas besoin de modèle DTO. Mon exigence n'est pas de cacher les détails de l'annotation au client. Je n'ai donc pas besoin de créer un autre POJO dans mon application. Mon exigence est de convertir l'ensemble de résultats en qa pojo qui n'est pas une entité JAVA mais une simple classe POJO qui a les mêmes champs que les colonnes de l'ensemble de résultats.
Gunjan Shah