JPA: Como converter um conjunto de resultados de consulta nativo na coleção de classes POJO


174

Estou usando o JPA no meu projeto.

Cheguei a uma consulta na qual preciso fazer a operação de junção em cinco tabelas. Então eu criei uma consulta nativa que retorna cinco campos.

Agora eu quero converter o objeto de resultado em java POJO class que contém as mesmas cinco Strings.

Existe alguma maneira no JPA de converter diretamente esse resultado na lista de objetos POJO?

Eu vim para a seguinte solução ..

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

Agora, aqui em resultClass, precisamos fornecer uma classe que seja a entidade JPA real? OU Podemos convertê-lo em qualquer classe JAVA POJO que contenha os mesmos nomes de coluna?


Verifique esta resposta. Tem resposta completa: stackoverflow.com/a/50365522/3073945
Md. Sajedul Karim

ele está usando jpa, não spring
ele

Respostas:


103

O JPA fornece um SqlResultSetMappingque permite mapear os retornos da sua consulta nativa em uma Entidadeou uma classe personalizada.

EDIT JPA 1.0 não permite mapeamento para classes não-entidade. Somente no JPA 2.1, um ConstructorResult foi adicionado para mapear os valores de retorno de uma classe java.

Além disso, para o problema do OP em obter a contagem, deve ser suficiente definir um mapeamento de conjunto de resultados com um único ColumnResult


1
Obrigado pela resposta. Aqui, estamos mapeando nosso resultado com a entidade com a classe de entidade java com as anotações "@EntityResult" e "@FieldResult". Isso é bom. Mas aqui eu preciso de mais clareza. É necessário que a classe com a qual estamos mapeando o resultado seja uma classe de entidade JPA? OU podemos usar uma classe POJO simples que não é uma compra de entidade que possui toda a variável necessária como as colunas no conjunto de resultados.
Gunjan Shah

1
@GunjanShah: a melhor maneira de saber é experimentá-lo :) também, uma entidade é exatamente o mesmo pojo, apenas com algumas anotações. contanto que você não esteja tentando persistir, ele permanecerá um pojo.
Denis Tulskiy

2
Quando tentei isso, recebi um erro de que a classe não era uma entidade conhecida. Acabei usando essa abordagem stackoverflow.com/questions/5024533/… em vez de tentar usar uma consulta nativa.
precisa saber é

2
@ EdwinDalorzo: isso é certo para o jpa 1.0. no jpa 2.1, eles adicionaram ConstructorResultcomo um dos parâmetros SqlResultSetMappingque permite usar um pojo com todos os campos definidos no construtor. Vou atualizar a resposta.
Denis Tulskiy

4
Vejo outra verdade amarga: o ConstructorResult pode mapear para um POJO. Mas o próprio ConstructorResult deve estar na classe Entity, para que você não possa evitar a Entity ... e, portanto, o maior fato difícil: você precisa de algum resultado sem se importar para chave primária - ainda é preciso ter o @Id na Entidade ... ridículo, certo?
Arnab Dutta 22/03

210

Eu encontrei algumas soluções para isso.

Usando entidades mapeadas (JPA 2.0)

Usando o JPA 2.0, não é possível mapear uma consulta nativa para um POJO, isso só pode ser feito com uma entidade.

Por exemplo:

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

Mas, neste caso, Jedideve ser uma classe de entidade mapeada.

Uma alternativa para evitar o aviso desmarcado aqui seria usar uma consulta nativa nomeada. Portanto, se declararmos a consulta nativa em uma entidade

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

Então, podemos simplesmente fazer:

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

Isso é mais seguro, mas ainda estamos restritos a usar uma entidade mapeada.

Mapeamento Manual

Uma solução que experimentei um pouco (antes da chegada do JPA 2.1) estava fazendo o mapeamento contra um construtor POJO usando um pouco de reflexão.

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

Esse método basicamente pega uma matriz de tupla (retornada por consultas nativas) e a mapeia em uma classe POJO fornecida, procurando por um construtor que tenha o mesmo número de campos e o mesmo tipo.

Em seguida, 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);
}

E podemos simplesmente usar esta técnica da seguinte maneira:

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

JPA 2.1 com @SqlResultSetMapping

Com a chegada do JPA 2.1, podemos usar a anotação @SqlResultSetMapping para resolver o problema.

Precisamos declarar um mapeamento de conjunto de resultados em algum lugar de uma entidade:

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

E então simplesmente fazemos:

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

Obviamente, nesse caso Jedi, não precisa ser uma entidade mapeada. Pode ser um POJO comum.

Usando mapeamento XML

Eu sou um daqueles que consideram a adição de todas essas informações @SqlResultSetMappingbastante invasivas em minhas entidades, e particularmente não gosto da definição de consultas nomeadas dentro de entidades, então, alternativamente, faço tudo isso no META-INF/orm.xmlarquivo:

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

E essas são todas as soluções que eu conheço. Os dois últimos são a maneira ideal se podemos usar o JPA 2.1.


1
Nota: Acabei de usar a abordagem JPA 2.0 com dependência JPA2.1, e falhou. Então, provavelmente, isso não é compatível com versões anteriores ... #
11781

1
o que você quer dizer com "em algum lugar de uma entidade"? Meu Pojo não é uma entidade JPA. Não posso declarar o @SqlResultSetMapping no meu POJO? Estou interessado nas soluções JPA 2.1. Por favor, seja um pouco mais preciso.
alboz

3
@Alboz O @SqlResultSetMappingdeve ser colocado em uma entidade, porque é disso que o JPA vai ler os metadados. Você não pode esperar que a JPA inspecione seus POJOs. A entidade na qual você coloca o mapeamento é irrelevante, talvez a que esteja mais relacionada aos seus resultados no POJO. Como alternativa, o mapeamento pode ser expresso em XML para evitar o acoplamento com uma entidade totalmente não relacionada.
Edwin Dalorzo

1
É possível que o resultado da construção use uma classe que possui uma classe aninhada?
Chrismarx #

5
Se você usar o JPA 2.1 @SqlResultSetMapping, pode ser interessante notar que a Jediclasse exigirá um construtor all-arg e a @ColumnResultanotação poderá precisar do typeatributo adicionado às conversões que podem não estar implícitas (eu precisava adicionar type = ZonedDateTime.classalgumas colunas).
Glenn

11

Sim, com o JPA 2.1 é fácil. Você tem anotações muito úteis. Eles simplificam sua vida.

Primeiro, declare sua consulta nativa e, em seguida, o mapeamento do conjunto de resultados (que define o mapeamento dos dados retornados pelo banco de dados aos seus POJOs). Escreva para a sua turma POJO (não incluída aqui por questões de brevidade). Por último, mas não menos importante: crie um método em um DAO (por exemplo) para chamar a consulta. Isso funcionou para mim em um aplicativo dropwizard (1.0.0).

Primeiro declare uma consulta nativa em uma classe de entidade:

@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

Abaixo, você pode adicionar a declaração de mapeamento do 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)
          }
      )
   } 
)

Posteriormente em um DAO, você pode consultar a consulta como

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

É isso aí.


Boa resposta, mas acho que você perdeu um parêntese após a primeira anotação @ColumnResult.
Mkatzer 17/09

Existem erros no código, mas fáceis de corrigir. Por exemplo: "resulSetMapping =" deveria ser "resultSetMapping ="
Zbyszek em

3
Eu vejo outra verdade amarga: NamedNativeQuery & SqlResultSetMapping tem que estar em uma classe @Entity
Arnab Dutta

10

Se você usar Spring-jpa, este é um complemento para as respostas e esta pergunta. Corrija isso se houver alguma falha. Eu usei principalmente três métodos para obter o "resultado de mapeamento Object[]para um pojo" com base nas necessidades práticas que eu atendo:

  1. O método incorporado JPA é suficiente.
  2. JPA construído em método não é o suficiente, mas um personalizado sqlcom o seu Entitysão suficientes.
  3. O antigo 2 falhou e eu tenho que usar um nativeQuery. Aqui estão os exemplos. O pojo esperava:

    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 : Mude o pojo para uma interface:

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

E repositório:

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

Método 2 : Repositório:

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

Nota: a sequência de parâmetros do construtor POJO deve ser idêntica na definição de POJO e no sql.

Método 3 : Use @SqlResultSetMappinge @NamedNativeQueryem Entitycomo o exemplo de resposta de Edwin Dalorzo.

Os dois primeiros métodos chamariam muitos manipuladores intermediários, como conversores personalizados. Por exemplo, AntiStealingdefine a secretKey, antes de persistir, um conversor é inserido para criptografá-lo. Isso resultaria nos 2 primeiros métodos retornando um retorno convertido secretKeyque não é o que eu quero. Enquanto o método 3 superaria o conversor, e retornado secretKeyseria o mesmo que ele é armazenado (um criptografado).


O Método 1, na verdade, não requer Spring e funciona com o Hibernate puro.
Martin Vysny

@MartinVysny sim, M1 é JPQL. qualquer projeto que implemente o JPQL deve suportá-lo. Dessa forma, talvez o M2 também seja amplamente suportado?
Tiina

8

O procedimento de desempacotamento pode ser executado para atribuir resultados a não-entidade (que é Beans / POJO). O procedimento é o seguinte.

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

O uso é para implementação do JPA-Hibernate.


observe que JobDTOdeve ter o construtor padrão. Ou você pode implementar seu próprio transformador com base na AliasToBeanResultTransformerimplementação.
Lu55

4

Primeiro declare as seguintes anotações:

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

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

Anote seu POJO da seguinte maneira:

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

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

Em seguida, escreva o processador de anotação:

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 a estrutura acima da seguinte maneira:

String sql = "select a,b from x order by a";
Query q = entityManager.createNativeQuery(sql);

List<ClassX> results = NativeQueryResultsMapper.map(q.getResultList(), ClassX.class);

Em que pacote está BeanUtils?
Harish

4

A maneira mais fácil é usar essas projeções . Ele pode mapear os resultados da consulta diretamente para interfaces e é mais fácil de implementar do que usar SqlResultSetMapping.

Um exemplo é mostrado abaixo:

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

}

Os campos da interface projetada devem corresponder aos campos nesta entidade. Caso contrário, o mapeamento do campo poderá ser interrompido.

Além disso, se você usar a SELECT table.columnnotação, defina sempre os aliases que correspondem aos nomes da entidade, conforme mostrado no exemplo.


1
consultas e projeções nativas não combinam bem.
Kevin Rave

1
Eu não poderia obter o mapeamento de campo para o trabalho muito bem - a maioria dos valores mantidos voltar como nula
Ayang

4

No hibernate, você pode usar esse código para mapear facilmente sua 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;}

2

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

2

Estilo antigo usando 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"));
                }
            }
        }
    });
}

1

Como outros já mencionaram todas as soluções possíveis, estou compartilhando minha solução alternativa.

Na minha situação Postgres 9.4, enquanto trabalhava com 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>>() {});

Tenho certeza que você pode encontrar o mesmo para outros bancos de dados.

Também FYI, JPA 2.0 resultados da consulta nativa como mapa


1

Não tenho certeza se isso se encaixa aqui, mas tive uma pergunta semelhante e encontrei a seguinte solução / exemplo simples para mim:

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

No meu caso, eu tive que usar partes SQL definidas em Strings em outro lugar, então não pude usar apenas NamedNativeQuery.


assim que retornarmos entidade. nada chique. O problema é quando você tenta mapear o resultado para um POJO não gerenciado.
precisa

1

Estilo antigo usando o 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"));
                }
            }
        }
    });
}

1

Resolvemos o problema da seguinte maneira:

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

0

Veja o exemplo abaixo para usar um POJO como pseudoentidade para recuperar o resultado da consulta nativa sem usar SqlResultSetMapping complexo. Só precisa de duas anotações, uma @Enity nua e uma @ id dummy no seu POJO. @Id pode ser usado em qualquer campo de sua escolha, um campo @Id pode ter chaves duplicadas, mas não valores nulos.

Como o @Enity não mapeia para nenhuma tabela física, esse POJO é chamado de pseudo-entidade.

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

Você pode baixar o projeto completo do maven aqui

A consulta nativa é baseada nos funcionários de amostra do 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);
    }
}
}

1
Como a sua listé, supostamente, uma lista de Employee, por que o loop for-each está repetindo um tipo Object? Se você escrever seu loop for-each, for(Employee emp : list)descobrirá que sua resposta está errada e o conteúdo da sua lista não é empregado e que esse aviso suprimido teve o objetivo de alertá-lo sobre esse possível erro.
Edwin Dalorzo

@SuppressWarnings ("desmarcado") é usado para suprimir o aviso de List<Employee> list = (List<Employee>) query.getResultList();Alterar for (Object emp : list)para for (Employee emp : list)melhor, mas nenhum erro será mantido, Object emppois a lista é uma instância de List<Employee>. Eu mudei o código no projeto git, mas não aqui para manter seu comentário relevante para a postagem original
Jonathan L

o problema é que sua consulta não retorna uma lista de empregados, mas uma variedade de objetos. Seu aviso suprimido está escondendo isso. No momento em que você tentar converter qualquer um deles para um funcionário, você receberá um erro, uma exceção de conversão.
Edwin Dalorzo 23/01

Veja Query query = em.createNativeQuery("select * ...", Employee.class);persistence.xml, a consulta nativa retorna uma lista de Employee. Acabei de efetuar o check-out e executar o projeto sem problemas. Se você configurar os funcionários de amostra do mysql db localmente, poderá executar o projeto também
Jonathan L

Oh, entendo o que você quer dizer agora. Mas, nesse caso, sua resposta não satisfaz a pergunta, porque se tratava de usar um POJO comum como objeto de destino, e sua resposta é usar o Employeeque eu suponho ser uma entidade. Não é?
Edwin Dalorzo

0

se você estiver usando o Spring, poderá usar org.springframework.jdbc.core.RowMapper

Aqui está um exemplo:

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

0

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

-1

Maneira simples de converter consulta SQL em coleção de classe POJO,

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

-1

Tudo que você precisa é de um DTO com um construtor:

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

e chame:

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

Adicione uma nova coluna e o código será interrompido.
Prato

-2

Use DTO Design Pattern. Foi usado em EJB 2.0. A entidade foi gerenciada por contêiner. DTO Design Patterné usado para resolver esse problema. Mas, pode ser usado agora, quando o aplicativo for desenvolvido Server Sidee Client Sideseparadamente. DTOé usado quandoServer side não deseja passar / retornar Entitycom anotação paraClient Side .

Exemplo 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 <- pode ser necessário

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

4
Obrigado pela resposta. Aqui não preciso do padrão DTO. Meu requisito não é ocultar os detalhes da anotação do cliente. Portanto, não preciso criar mais um POJO no meu aplicativo. Meu requisito é converter o conjunto de resultados para qa pojo que não é uma entidade JAVA, mas simples classe POJO que possui os mesmos campos das colunas do conjunto de resultados.
Gunjan Shah
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.