Qual é a maneira “adequada” de lançar Hibernate Query.list () para List <Type>?

84

Sou um novato no Hibernate e estou escrevendo um método simples para retornar uma lista de objetos que correspondem a um filtro específico. List<Foo>parecia um tipo de retorno natural.

Faça o que fizer, não consigo deixar o compilador feliz, a menos que eu use um feio @SuppressWarnings.

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;

public class Foo {

    public Session acquireSession() {
        // All DB opening, connection etc. removed,
        // since the problem is in compilation, not at runtime.
        return null;
    }

    @SuppressWarnings("unchecked") /* <----- */

    public List<Foo> activeObjects() {
        Session s = acquireSession();
        Query   q = s.createQuery("from foo where active");
        return (List<Foo>) q.list();
    }
}

Eu gostaria de me livrar dissoSuppressWarnings . Mas se eu fizer isso, recebo o aviso

Warning: Unchecked cast from List to List<Foo>

(Posso ignorá-lo, mas gostaria de não obtê-lo em primeiro lugar), e se eu remover o genérico para estar em conformidade com o .list()tipo de retorno, recebo o aviso

Warning: List is a raw type. References to generic type List<E>
should be parameterized.

Notei que org.hibernate.mapping faz declarar um List; mas é um tipo totalmente diferente - Queryretorna a java.util.List, como um tipo bruto. Acho estranho que um Hibernate (4.0.x) recente não implementasse tipos parametrizados, então suspeito que sou eu fazendo algo errado.

Parece muito com o resultado do Cast Hibernate para uma lista de objetos , mas aqui não tenho nenhum erro "difícil" (o sistema conhece o tipo Foo, e não estou usando um SQLQuery, mas uma consulta direta). Portanto, nenhuma alegria.

Também examinei Hibernate Class Cast Exception, pois parecia promissor, mas então percebi que não recebo nenhum Exception... meu problema é apenas um aviso - um estilo de codificação, se preferir.

A documentação no jboss.org, os manuais do Hibernate e vários tutoriais parecem não cobrir o tópico com tantos detalhes (ou não pesquisei nos lugares certos?). Quando eles entram em detalhes, eles usam fundição instantânea - e isso em tutoriais que não estavam no site oficial jboss.org, então estou um pouco desconfiado.

O código, uma vez compilado, é executado sem nenhum problema aparente ... que eu saiba ... ainda; e os resultados são os esperados.

Então: estou fazendo certo? Estou perdendo algo óbvio? Existe uma maneira "oficial" ou "recomendada" de fazer isso ?

LSerni
fonte

Respostas:

101

Uma resposta curta @SuppressWarningsé o caminho certo a seguir.

Resposta longa, o Hibernate retorna um raw Listdo Query.listmétodo, veja aqui . Este não é um bug do Hibernate ou algo que pode ser resolvido, o tipo retornado pela consulta não é conhecido em tempo de compilação.

Portanto, quando você escreve

final List<MyObject> list = query.list();

Você está fazendo um elenco inseguro de Listpara List<MyObject>- isso não pode ser evitado.

Não há como realizar o gesso com segurança, pois ele List pode conter qualquer coisa.

A única maneira de fazer com que o erro desapareça é ainda mais feio

final List<MyObject> list = new LinkedList<>();
for(final Object o : query.list()) {
    list.add((MyObject)o);
}
Boris o Aranha
fonte
4
Eu ia apenas votar a favor da sua resposta, esperando que uma melhor aparecesse. Em vez disso, descobri esse problema conhecido como "elenco feio" por Bruce Eckel (Pensando em Java) e Robert Sedgewick - o Sedgewick. Eu também encontrei stackoverflow.com/questions/509076/… . Suspiro.
LSerni
6
Gosto do seu estilo de usofinal
Pavel
9
Se os caras do hibernate adicionarem um argumento com type Class<?>in list(), o problema pode ser resolvido. É uma pena usar uma API tão feia.
Bin Wang
@BinWang então o elenco inseguro aconteceria em outro lugar, não resolve o problema - apenas o move. Nem é preciso dizer que a API HQL está efetivamente obsoleta há anos . A JPA possui uma API de consulta de tipo seguro chamada Criteria Query API .
Boris the Spider
2
@PeteyPabPro Embora eu concorde que rawtypes devem ser evitados, discordo que os resultados de uma consulta devem ser tratados como um List<Object>. Os resultados devem ser convertidos para o tipo esperado e testes de unidade devem ser adicionados para garantir que a consulta retorne os resultados corretos. É inaceitável que erros com consultas apareçam " mais tarde no código ". Seu exemplo é um argumento contra as práticas de codificação que deveriam ser um anátema no século 21. É, eu sugeriria, nunca aceitável ter um List<Object>.
Boris the Spider
26

A resolução é usar TypedQuery em vez disso. Ao criar uma consulta no EntityManager, chame-a assim:

TypedQuery<[YourClass]> query = entityManager.createQuery("[your sql]", [YourClass].class);
List<[YourClass]> list = query.getResultList(); //no type warning

Isso também funciona da mesma forma para consultas nomeadas, consultas nomeadas nativas, etc. Os métodos correspondentes têm os mesmos nomes que aqueles que retornariam a consulta básica. Basta usar isso em vez de uma consulta sempre que você souber o tipo de retorno.

Taugenichts
fonte
1
Como uma observação lateral, isso não funciona para consultas puramente nativas que você está criando inline. A consulta retornada é apenas uma consulta, não uma consulta digitada :(
Taugenichts
Agora, a mensagem de erro se foi e a lista resultante usa os objetos reais em vez do objeto Object .. ótima resposta!
Johannes
Parece ter sido lançado no hibernate 5.0. Não vejo isso em 4.3.11, mas o artigo a seguir se refere a 5.3. wiki.openbravo.com/wiki/… Também vejo uma postagem stackoverflow de janeiro de 2014 referindo-se a ele: stackoverflow.com/a/21354639/854342 .
Curtis Yallop
Faz parte do hibernate-jpa-2.0-api. Você pode usar isso com o hibernate 4.3, como estou usando no momento no hibernate 4.3.
Taugenichts
6

Você pode evitar o aviso do compilador com soluções alternativas como esta:

List<?> resultRaw = query.list();
List<MyObj> result = new ArrayList<MyObj>(resultRaw.size());
for (Object o : resultRaw) {
    result.add((MyObj) o);
}

Mas existem alguns problemas com este código:

  • criou ArrayList supérfluo
  • loop desnecessário em todos os elementos retornados da consulta
  • código mais longo.

E a diferença é apenas estética, portanto, usar essas soluções alternativas é - na minha opinião - inútil.

Você tem que conviver com esses avisos ou suprimi-los.

Grzegorz Olszewski
fonte
1
Eu concordo com a inutilidade. Vou suprimir, mesmo que vá contra a minha natureza. Obrigado mesmo assim, e +1.
LSerni
2
> Você tem que conviver com esses avisos ou suprimi-los. É sempre melhor suprimir os avisos que estão errados, ou você pode perder o aviso certo em um spam de avisos errados não suprimidos
SpongeBobFan
6

Para responder à sua pergunta, não existe uma "maneira adequada" de fazer isso. Agora, se é apenas o aviso que o incomoda, a melhor maneira de evitar sua proliferação é envolver o Query.list()método em um DAO:

public class MyDAO {

    @SuppressWarnings("unchecked")
    public static <T> List<T> list(Query q){
        return q.list();
    }
}

Dessa forma, você pode usar @SuppressWarnings("unchecked")apenas uma vez.

PDV
fonte
Bem-vindo ao Stack Overflow ! De qualquer forma, não se esqueça de fazer o tour
Sнаđошƒаӽ
3

A única maneira que funcionou para mim foi com um Iterador.

Iterator iterator= query.list().iterator();
Destination dest;
ArrayList<Destination> destinations= new ArrayList<>();
Iterator iterator= query.list().iterator();
    while(iterator.hasNext()){
        Object[] tuple= (Object[]) iterator.next();
        dest= new Destination();
        dest.setId((String)tuple[0]);
        dest.setName((String)tuple[1]);
        dest.setLat((String)tuple[2]);
        dest.setLng((String)tuple[3]);
        destinations.add(dest);
    }

Com outros métodos que encontrei, tive problemas de elenco

Popa Andrei
fonte
Quais são os "problemas de elenco"? Sempre elenco a lista diretamente, como o acima é mais conciso ou mais seguro?
Giovanni Botta,
Não consigo lançar diretamente. Houve problemas de elenco porque não foi possível fazer elenco de Object to Destination
Popa Andrei
Você sabe que o Hibernate pode construir um Destinstionpara você, certo? Usando a select newsintaxe. Essa certamente não é a abordagem certa.
Boris the Spider
Eu também tive a mesma experiência. Como minha consulta retorna campos diferentes de várias tabelas que não estão conectadas entre si. Então, a única maneira que funcionou para mim foi esta. Obrigado :)
Chintan Patel
3
List<Person> list = new ArrayList<Person>();
Criteria criteria = this.getSessionFactory().getCurrentSession().createCriteria(Person.class);
for (final Object o : criteria.list()) {
    list.add((Person) o);
}
user3184564
fonte
Sim, essa é basicamente a mesma 'feiúra' sugerida por Boris, com um elenco dentro do loop.
LSerni
2

Você usa um ResultTransformer assim:

public List<Foo> activeObjects() {
    Session s = acquireSession();
    Query   q = s.createQuery("from foo where active");
    q.setResultTransformer(Transformers.aliasToBean(Foo.class));
    return (List<Foo>) q.list();
}
Lakreqta
fonte
1
Não posso testar isso agora, mas ... o que isso muda? qainda é um Querye, portanto, q.list()ainda é um java.util.Listtipo bruto . O elenco ainda está desmarcado; ter o tipo de objeto alterado internamente não deve servir de nada ...
LSerni
Sim, o elenco ainda está desmarcado, mas com a nomenclatura adequada de seus campos, definir um resultTransformer faz o trabalho de converter os objetos como seu POJO desejado. Veja esta postagem stackoverflow e leia este documento de referência do hibernate sobre o uso de consultas nativas
lakreqta
from foo where activenão é uma consulta nativa. Portanto, não há necessidade de um transformador de resultado, pois o mapeamento padrão será suficiente. A questão não é sobre a projeção dos campos POJO, mas sobre a projeção do objeto de resultado. Um transformador de resultado não ajudaria aqui.
Tobias Liefke
0

A maneira correta é usar transformadores Hibernate:

public class StudentDTO {
private String studentName;
private String courseDescription;

public StudentDTO() { }  
...
} 

.

List resultWithAliasedBean = s.createSQLQuery(
"SELECT st.name as studentName, co.description as courseDescription " +
"FROM Enrolment e " +
"INNER JOIN Student st on e.studentId=st.studentId " +
"INNER JOIN Course co on e.courseCode=co.courseCode")
.setResultTransformer( Transformers.aliasToBean(StudentDTO.class))
.list();

StudentDTO dto =(StudentDTO) resultWithAliasedBean.get(0);

A iteração por meio de Object [] é redundante e teria alguma penalidade de desempenho. Informações detalhadas sobre o uso de transformadores você encontrará aqui: Transformers para HQL e SQL

Se você está procurando uma solução ainda mais simples, pode usar o transformador de mapa pronto para usar:

List iter = s.createQuery(
"select e.student.name as studentName," +
"       e.course.description as courseDescription" +
"from   Enrolment as e")
.setResultTransformer( Transformers.ALIAS_TO_ENTITY_MAP )
.iterate();

String name = (Map)(iter.next()).get("studentName");
ANTARA
fonte
A questão não era sobre transformação de resultados. Tratava-se de lançar Queryresultados - o que ainda é necessário em seu exemplo. E seu exemplo não tem nada a ver com o original from foo where active.
Tobias Liefke
0

Apenas usando Transformers Não funcionou para mim, eu estava recebendo uma exceção de elenco de tipo.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) não funcionou porque eu estava obtendo Array of Object no elemento de lista de retorno e não o tipo MYEngityName fixo de elemento de lista.

Funcionou para mim quando fiz as seguintes alterações. Quando adicionei sqlQuery.addScalar(-)cada coluna selecionada e seu tipo, e para a coluna de tipo String específica, não precisamos mapear seu tipo. gostaraddScalar("langCode");

E eu juntei MYEngityName com NextEnity que não podemos apenas select *na Query daremos array de Object na lista de retorno.

Exemplo de código abaixo:

session = ht.getSessionFactory().openSession();
                String sql = new StringBuffer("Select txnId,nft.mId,count,retryReason,langCode FROM  MYEngityName nft INNER JOIN NextEntity m on nft.mId  =  m.id where nft.txnId < ").append(lastTxnId)
                       .append(StringUtils.isNotBlank(regionalCountryOfService)? " And  m.countryOfService in ( "+ regionalCountryOfService +" )" :"")
                       .append(" order by nft.txnId desc").toString();
                SQLQuery sqlQuery = session.createSQLQuery(sql);
                sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class));
                sqlQuery.addScalar("txnId",Hibernate.LONG)
                        .addScalar("merchantId",Hibernate.INTEGER)
                        .addScalar("count",Hibernate.BYTE)
                        .addScalar("retryReason")
                        .addScalar("langCode");
                sqlQuery.setMaxResults(maxLimit);
                return sqlQuery.list();

Pode ajudar alguém. desta forma funciona para mim.

Laxman G
fonte
-1

Eu encontrei a melhor solução aqui , a chave para este problema é o método addEntity

public static void testSimpleSQL() {
    final Session session = sessionFactory.openSession();
    SQLQuery q = session.createSQLQuery("select * from ENTITY");
    q.addEntity(Entity.class);
    List<Entity> entities = q.list();
    for (Entity entity : entities) {
        System.out.println(entity);
    }
}
Federico Traiman
fonte