JPA: cómo convertir un conjunto de resultados de consultas nativas en una colección de clases POJO

174

Estoy usando JPA en mi proyecto.

Llegué a una consulta en la que necesito hacer una operación de unión en cinco tablas. Entonces creé una consulta nativa que devuelve cinco campos.

Ahora quiero convertir el objeto de resultado a la clase de Java POJO que contiene las mismas cinco cadenas.

¿Hay alguna forma en JPA para transmitir directamente ese resultado a la lista de objetos POJO?

Llegué a la siguiente solución ...

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

Ahora aquí en resultClass, ¿necesitamos proporcionar una clase que sea una entidad JPA real? O ¿Podemos convertirlo a cualquier clase de JAVA POJO que contenga los mismos nombres de columna?

Gunjan Shah
fuente
Mira esta respuesta. Tiene respuesta completa: stackoverflow.com/a/50365522/3073945
Md. Sajedul Karim
él está usando jpa, no la primavera
él

Respuestas:

103

JPA proporciona un SqlResultSetMappingque le permite asignar lo que devuelve su consulta nativa a una entidado una clase personalizada.

EDITAR JPA 1.0 no permite la asignación a clases que no sean de entidad. Solo en JPA 2.1 se ha agregado un ConstructorResult para asignar valores de retorno a una clase java.

Además, para el problema de OP con el recuento, debería ser suficiente para definir una asignación de conjunto de resultados con un solo ColumnResult

Denis Tulskiy
fuente
1
Gracias por la respuesta. Aquí estamos mapeando nuestro resultado con la entidad con la clase de entidad java con anotaciones "@EntityResult" y "@FieldResult". Esta bien. Pero aquí necesito más claridad. ¿Se requiere que la clase que estamos mapeando con el resultado sea una clase de entidad JPA? O podemos usar una clase POJO simple que no sea una compra de entidad que tenga todas las variables requeridas como columnas en el conjunto de resultados.
Gunjan Shah
1
@GunjanShah: la mejor manera de saberlo es intentarlo :) también, una entidad es el mismo pojo, solo que con algunas anotaciones. mientras no intentes persistir, seguirá siendo un pojo.
Denis Tulskiy
2
Cuando intenté esto, recibí un error de que la clase no era una Entidad conocida. Terminé usando este enfoque stackoverflow.com/questions/5024533/… en lugar de intentar usar una consulta nativa.
FGreg
2
@EdwinDalorzo: eso es correcto para jpa 1.0. en jpa 2.1 se agregaron ConstructorResultcomo uno de los parámetros SqlResultSetMappingque permiten usar un pojo con todos los campos establecidos en el constructor. Actualizaré la respuesta.
Denis Tulskiy
44
Veo otra verdad amarga: ConstructorResult puede correlacionarse con un POJO ... PERO ConstructorResult en sí mismo debe estar en la clase Entity para que Entity no pueda evitar ... y, por lo tanto, el hecho más difícil: necesita algún resultado sin importarle a la clave principal: aún así debe tener @Id en la entidad ... ¿ridículo, verdad?
Arnab Dutta
210

He encontrado un par de soluciones para esto.

Uso de entidades mapeadas (JPA 2.0)

Con JPA 2.0 no es posible asignar una consulta nativa a un POJO, solo se puede hacer con una entidad.

Por ejemplo:

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

Pero en este caso, Jedidebe ser una clase de entidad asignada.

Una alternativa para evitar la advertencia no verificada aquí sería utilizar una consulta nativa con nombre. Entonces, si declaramos la consulta nativa en una entidad

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

Entonces, simplemente podemos hacer:

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

Esto es más seguro, pero todavía estamos restringidos a usar una entidad asignada.

Mapeo manual

Una solución que experimenté un poco (antes de la llegada de JPA 2.1) fue hacer un mapeo contra un constructor POJO usando un poco de reflexión.

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

Básicamente, este método toma una matriz de tuplas (como la devuelven las consultas nativas) y la asigna a una clase POJO proporcionada buscando un constructor que tenga el mismo número de campos y del mismo tipo.

Entonces podemos usar métodos convenientes como:

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

Y simplemente podemos usar esta técnica de la siguiente manera:

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

JPA 2.1 con @SqlResultSetMapping

Con la llegada de JPA 2.1, podemos usar la anotación @SqlResultSetMapping para resolver el problema.

Necesitamos declarar una asignación de conjunto de resultados en algún lugar de una entidad:

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

Y luego simplemente hacemos:

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

Por supuesto, en este caso Jedino necesita ser una entidad mapeada. Puede ser un POJO regular.

Usar mapeo XML

Soy uno de los que encuentran que agregar todo esto es @SqlResultSetMappingbastante invasivo en mis entidades, y particularmente no me gusta la definición de consultas con nombre dentro de las entidades, por lo que alternativamente hago todo esto en el META-INF/orm.xmlarchivo:

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

Y esas son todas las soluciones que conozco. Los dos últimos son la forma ideal si podemos usar JPA 2.1.

Edwin Dalorzo
fuente
1
Nota al margen: acabo de utilizar el enfoque JPA 2.0 con dependencia JPA2.1, y falló. Así que probablemente esto no sea compatible con
versiones
1
¿Qué quieres decir con "en algún lugar de una entidad"? Mi Pojo no es una entidad JPA, ¿no puedo declarar @SqlResultSetMapping en mi POJO? Estoy interesado en las soluciones JPA 2.1. Por favor sea un poco más preciso.
Alboz
3
@Alboz El @SqlResultSetMappingdebe colocarse en una entidad porque de eso JPA leerá los metadatos. No puede esperar que JPA inspeccione sus POJO. La entidad en la que coloca el mapeo es irrelevante, tal vez la que está más relacionada con sus resultados POJO. Alternativamente, la asignación podría expresarse en XML para evitar el acoplamiento con una entidad totalmente no relacionada.
Edwin Dalorzo
1
¿Es posible que el resultado del constructor use una clase que tenga una clase anidada?
Chrismarx
55
Si se usa JPA 2.1 con @SqlResultSetMappingél, vale la pena señalar que la Jediclase requerirá un constructor todo arg y la @ColumnResultanotación puede necesitar que se typeagregue el atributo a las conversiones que podrían no ser implícitas (necesitaba agregar type = ZonedDateTime.classalgunas columnas).
Glenn
11

Sí, con JPA 2.1 es fácil. Tienes anotaciones muy útiles. Simplifican tu vida.

Primero declare su consulta nativa, luego su asignación de conjunto de resultados (que define la asignación de los datos devueltos por la base de datos a sus POJO). Escriba su clase de POJO para referirse (no se incluye aquí por brevedad). Por último, pero no menos importante: cree un método en un DAO (por ejemplo) para llamar a la consulta. Esto funcionó para mí en una aplicación dropwizard (1.0.0).

Primero declare una consulta nativa en una clase de entidad:

@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

Debajo puede agregar la declaración de mapeo del conjunto de resultados:

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

Más adelante en un DAO puede referirse a la consulta como

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

Eso es.

GiulioDS
fuente
Buena respuesta, pero creo que te has perdido un paréntesis después de la primera anotación @ColumnResult.
mwatzer
Hay errores en el código, pero fáciles de corregir. Por ejemplo: "resulSetMapping =" debería ser "resultSetMapping ="
Zbyszek
3
Veo otra amarga verdad: NamedNativeQuery & SqlResultSetMapping tiene que estar en una clase @Entity
Arnab Dutta
10

Si lo usa Spring-jpa, este es un complemento de las respuestas y esta pregunta. Corrija esto si hay fallas. Principalmente he usado tres métodos para lograr el "resultado de mapeo Object[]a un pojo" basado en la necesidad práctica que cumplo:

  1. El método integrado JPA es suficiente.
  2. El método incorporado de JPA no es suficiente, pero un personalizado sqlcon su Entityes suficiente
  3. Los primeros 2 fallaron, y tengo que usar a nativeQuery. Aquí están los ejemplos. El pojo esperaba:

    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étodo 1 : cambie el pojo a una interfaz:

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

Y repositorio:

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

Método 2 : repositorio:

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

Nota: la secuencia de parámetros del constructor POJO debe ser idéntica tanto en la definición POJO como en sql.

Método 3 : Uso @SqlResultSetMappingy @NamedNativeQueryen la Entityque el ejemplo de la respuesta de Edwin Dalorzo.

Los primeros dos métodos llamarían a muchos manejadores intermedios, como convertidores personalizados. Por ejemplo, AntiStealingdefine a secretKey, antes de que persista, se inserta un convertidor para encriptarlo. Esto daría como resultado que los primeros 2 métodos devuelvan una conversión convertida secretKeyque no es lo que quiero. Si bien el método 3 superaría el convertidor, y se devolvería secretKeysería el mismo que se almacena (uno cifrado).

Tiina
fuente
El Método 1 en realidad no requiere Spring y funciona con Hibernate puro.
Martin Vysny
@ MartinVysny sí, M1 es JPQL. cualquier proyecto que implemente JPQL debería soportarlo. De esta manera, ¿quizás M2 también sea ampliamente compatible?
Tiina
8

El procedimiento de desenvoltura se puede realizar para asignar resultados a entidades que no sean entidades (que es Beans / POJO). El procedimiento es el siguiente.

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

El uso es para la implementación JPA-Hibernate.

zawhtut
fuente
tenga en cuenta que JobDTOdebe tener un constructor predeterminado. O puede implementar su propio transformador basado en la AliasToBeanResultTransformerimplementación.
Lu55
4

Primero declare las siguientes anotaciones:

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

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

Luego anote su POJO de la siguiente manera:

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

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

Luego escriba el procesador de anotaciones:

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

Use el marco anterior de la siguiente manera:

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
fuente
¿En qué paquete está BeanUtils?
Harish
4

La forma más fácil es usar las proyecciones . Puede asignar resultados de consultas directamente a las interfaces y es más fácil de implementar que usar SqlResultSetMapping.

A continuación se muestra un ejemplo:

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

}

Los campos de la interfaz proyectada deben coincidir con los campos de esta entidad. De lo contrario, la asignación de campo podría romperse.

Además, si usa la SELECT table.columnnotación, siempre defina alias que coincidan con los nombres de la entidad, como se muestra en el ejemplo.

Thanthu
fuente
1
La consulta y las proyecciones nativas no van bien juntas.
Kevin Rave
1
No pude conseguir la asignación de campos de trabajo del todo bien - la mayoría de los valores mantenidos atrás como nula viene
ayang
4

En hibernación, puede usar este código para asignar fácilmente su consulta nativa.

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

Usando 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
fuente
2

Estilo antiguo con 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
fuente
1

Como otros ya han mencionado todas las soluciones posibles, estoy compartiendo mi solución alternativa.

En mi situación con Postgres 9.4, mientras trabajo con 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>>() {});

Estoy seguro de que puede encontrar lo mismo para otras bases de datos.

También para su información, resultados de consultas nativas JPA 2.0 como mapa

Darshan Patel
fuente
1

No estoy seguro de si esto encaja aquí, pero tuve una pregunta similar y encontré la siguiente solución / ejemplo simple para mí:

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

En mi caso, tuve que usar las partes SQL definidas en cadenas en otro lugar, por lo que no pude usar NamedNativeQuery.

Andreas L.
fuente
Tan pronto como regresamos entidad. nada sofisticado. El problema es cuando intenta asignar el resultado a un POJO no administrado.
Olgun Kaya
1

Estilo antiguo con 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
fuente
1

Hemos resuelto el problema de la siguiente manera:

   //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
fuente
0

Vea el siguiente ejemplo para usar un POJO como pseudoentidad para recuperar el resultado de una consulta nativa sin usar SqlResultSetMapping complejo. Solo necesito dos anotaciones, una @Enity desnuda y una @Id ficticia en tu POJO. @Id se puede usar en cualquier campo de su elección, un campo @Id puede tener claves duplicadas pero no valores nulos.

Como @Enity no se asigna a ninguna tabla física, este POJO se denomina pseudoentidad.

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

Puedes descargar el proyecto completo de Maven aquí

La consulta nativa se basa en ejemplos de empleados de 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
fuente
1
Dado que su listes, supuestamente, una lista de Employee, ¿por qué su ciclo for-each itera sobre un tipo Object? Si escribe su ciclo for-each como for(Employee emp : list)entonces, descubriría que su respuesta es incorrecta y que el contenido de su lista no son empleados y que esa advertencia que suprimió tenía el propósito de alertarlo sobre este posible error.
Edwin Dalorzo
@SuppressWarnings ("sin marcar") se usa para suprimir la advertencia de que List<Employee> list = (List<Employee>) query.getResultList();Cambiar for (Object emp : list)a for (Employee emp : list)es mejor, pero no hay errores si se mantiene Object empya que la lista es una instancia de List<Employee>. Cambié el código en el proyecto git pero no aquí para mantener tu comentario relevante a la publicación original
Jonathan L
El problema es que su consulta no devuelve una lista de empleados, sino una matriz de objetos. Tu advertencia suprimida está ocultando eso. En el momento en que intente convertir cualquiera de ellos en un empleado, obtendrá un error, una excepción de lanzamiento.
Edwin Dalorzo
Mire Query query = em.createNativeQuery("select * ...", Employee.class);y persistence.xml, la consulta nativa devuelve una lista de Empleado. Acabo de salir y ejecutar el proyecto sin problema. Si configura los empleados de muestra de MySQL DB localmente, también debería poder ejecutar el proyecto
Jonathan L
Oh, ya veo lo que quieres decir ahora. Pero en ese caso, su respuesta no satisface la pregunta, porque se trataba de usar un POJO regular como el objeto de destino, y su respuesta está usando Employeelo que supongo que es una entidad. ¿No es así?
Edwin Dalorzo
0

si está usando Spring, puede usar org.springframework.jdbc.core.RowMapper

Aquí hay un ejemplo:

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

Usando 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
fuente
-1

Manera simple de convertir la consulta SQL a la colección de clase POJO,

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

Todo lo que necesitas es un DTO con un constructor:

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

y llámalo:

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
fuente
Agregue una nueva columna y el código se romperá.
Plato
-2

Uso DTO Design Pattern. Fue utilizado en EJB 2.0. La entidad fue administrada por contenedor. DTO Design Patternse utiliza para resolver este problema Pero, podría usarse ahora, cuando la aplicación se desarrolle Server Sidey por Client Sideseparado. DTOse usa cuando Server sideno quiere pasar / regresar Entitycon anotación a Client Side.

Ejemplo de 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 <- puede ser necesario

public class EntityBuilder() {
    public static PersonEntity buildPersonEntity(PersonDTO person) {
        return new PersonEntity(person.getId(). person.getAddress());
    }
}
Zaw Than oo
fuente
44
Gracias por la respuesta. Aquí no necesito el patrón DTO. Mi requisito es no ocultar los detalles de la anotación del cliente. Por lo tanto, no necesito crear un POJO más en mi aplicación. Mi requisito es convertir el conjunto de resultados a qa pojo que no sea una entidad JAVA sino una clase POJO simple que tenga los mismos campos que las columnas del conjunto de resultados.
Gunjan Shah