Como evitar avisos de segurança de tipo com resultados de Hibernate HQL?

105

Por exemplo, eu tenho essa consulta:

Query q = sess.createQuery("from Cat cat");
List cats = q.list();

Se eu tentar fazer algo assim, mostrará o seguinte aviso

Type safety: The expression of type List needs unchecked conversion to conform to List<Cat>


List<Cat> cats = q.list();

Existe uma maneira de evitá-lo?

serg
fonte
11
Vale a pena mencionar que com JPA você pode ter consultas de tipo seguro, adicionando o tipo ao createQuery.
Elazar Leibovich
5
Um pouco tarde, mas sess.createQuery("from Cat cat", Cat.class);como Elazar mencionou.
Dominik Mohr

Respostas:

99

Usar em @SuppressWarningsqualquer lugar, conforme sugerido, é uma boa maneira de fazer isso, embora envolva digitar um pouco o dedo cada vez que você ligar q.list().

Existem duas outras técnicas que eu sugiro:

Escreva um elenco auxiliar

Simplesmente refatore tudo @SuppressWarningsem um só lugar:

List<Cat> cats = MyHibernateUtils.listAndCast(q);

...

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

Impedir que o Eclipse gere avisos para problemas inevitáveis

No Eclipse, vá para Janela> Preferências> Java> Compilador> Erros / Avisos e em Tipo genérico, selecione a caixa de seleção Ignore unavoidable generic type problems due to raw APIs

Isso desativará avisos desnecessários para problemas semelhantes, como o descrito acima, que são inevitáveis.

Alguns comentários:

  • Eu escolhi passar no em Queryvez do resultado de q.list()porque dessa forma este método de "trapaça" só pode ser usado para trapacear com o Hibernate, e não para trapacear Listem geral.
  • Você pode adicionar métodos semelhantes para .iterate()etc.
Matt Quail
fonte
20
À primeira vista, o método Collections.checkedList (Collection <E>, Class <E>) parece a solução perfeita. No entanto, o javadoc diz que ele apenas evita que elementos digitados incorretamente sejam adicionados por meio da visão de tipo seguro que o método gera. Nenhuma verificação é feita na lista fornecida.
phatblat
11
"List <Cat> list = Collections.checkedList (q.list (), Cat.class);" ainda requer um "@SuppressWarnings" no Eclipse. Sobre a outra dica: digitar "listAndCast" não é realmente menor do que "@SuppressWarnings", que é adicionado automaticamente via Eclipse.
Tristan
2
BTW, o Collections.checkedList()método não suprimirá o aviso de atribuição não verificada.
Diablo,
39

Já se passou muito tempo desde que a pergunta foi feita, mas espero que minha resposta possa ser útil para alguém como eu.

Se você der uma olhada nos documentos da API javax.persistence , verá que alguns novos métodos foram adicionados lá desde então Java Persistence 2.0. Um deles é o createQuery(String, Class<T>)que retorna TypedQuery<T>. Você pode usar TypedQueryda mesma forma, Querycom aquela pequena diferença de que todas as operações agora são seguras para tipos.

Então, apenas altere seu código para smth assim:

Query q = sess.createQuery("from Cat cat", Cat.class);
List<Cat> cats = q.list();

E você está pronto.

antonpp
fonte
1
A pergunta não é sobre JPA
Mathijs Segers
2
Versões recentes do Hibernate implementam JPA 2.x, então esta resposta é relevante.
caspinos
TypedQuery <T> é o melhor cenário.
Muneeb Mirza de
21

Usamos @SuppressWarnings("unchecked")também, mas na maioria das vezes tentamos usá-lo apenas na declaração da variável, não no método como um todo:

public List<Cat> findAll() {
    Query q = sess.createQuery("from Cat cat");
    @SuppressWarnings("unchecked")
    List<Cat> cats = q.list();
    return cats;
}
Cretzel
fonte
15

Tente usar em TypedQueryvez de Query. Por exemplo, em vez deste: -

Query q = sess.createQuery("from Cat cat", Cat.class);
List<Cat> cats = q.list();

Usa isto:-

TypedQuery<Cat> q1 = sess.createQuery("from Cat cat", Cat.class);
List<Cat> cats = q1.list();
Shivam oberoi
fonte
1
Existe uma maneira de fazer isso Criteria?
Stealth Rabino
5

Em nosso código, anotamos os métodos de chamada com:

@SuppressWarnings ("desmarcado")

Eu sei que parece um hack, mas um co-desenvolvedor verificou recentemente e descobriu que era tudo o que podíamos fazer.

Tyshock
fonte
5

Aparentemente, o método Query.list () na API do Hibernate não é seguro para o tipo "por design" e não há planos para alterá-lo .

Eu acredito que a solução mais simples para evitar avisos do compilador é adicionar @SuppressWarnings ("desmarcado"). Essa anotação pode ser colocada no nível do método ou, se estiver dentro de um método, logo antes de uma declaração de variável.

Caso você tenha um método que encapsula Query.list () e retorna List (ou Collection), você também receberá um aviso. Mas este é suprimido usando @SuppressWarnings ("rawtypes").

O método listAndCast (Query) proposto por Matt Quail é menos flexível que Query.list (). Enquanto eu posso fazer:

Query q = sess.createQuery("from Cat cat");
ArrayList cats = q.list();

Se eu tentar o código abaixo:

Query q = sess.createQuery("from Cat cat");
ArrayList<Cat> cats = MyHibernateUtils.listAndCast(q);

Vou obter um erro de compilação: incompatibilidade de tipo: não é possível converter de List para ArrayList

Paulo Merson
fonte
1
"não há planos para mudá-lo." - é um post de 2005. Eu ficaria surpreso se as coisas não tivessem mudado desde então.
Rup
4

Não é um descuido ou um erro. O aviso reflete um problema real subjacente - não há como o compilador java realmente ter certeza de que a classe de hibernação fará seu trabalho corretamente e que a lista que ela retorna conterá apenas Cats. Qualquer uma das sugestões aqui está bem.

Paulmurray
fonte
2

Não, mas você pode isolá-lo em métodos de consulta específicos e suprimir os avisos com uma @SuppressWarnings("unchecked")anotação.

Dave L.
fonte
Errado ... Joe Dean está certo, você pode usar o? como o tipo genérico para evitar os avisos ...
Mike Stone,
1
Isso não é verdade. Se você usar um List <?>, Não poderá usar os elementos da lista como Cat sem a etapa desnecessária de criar uma lista duplicada e lançar cada item.
Dave L.
bem, se você usar os resultados diretamente por meio do casting, você não precisa criar a lista e, independentemente, a pergunta era "há uma maneira de evitá-lo", a resposta é definitivamente SIM (mesmo sem suprimir avisos)
Mike Stone,
2

Versões mais novas do Hibernate agora suportam um Query<T>objeto de tipo seguro, então você não precisa mais usar @SuppressWarningsou implementar algum hack para fazer os avisos do compilador desaparecerem. Na API de sessão , Session.createQueryagora retornará um Query<T>objeto de tipo seguro . Você pode usá-lo desta forma:

Query<Cat> query = session.createQuery("FROM Cat", Cat.class);
List<Cat> cats = query.list();

Você também pode usá-lo quando o resultado da consulta não retornar um gato:

public Integer count() {
    Query<Integer> query = sessionFactory.getCurrentSession().createQuery("SELECT COUNT(id) FROM Cat", Integer.class);
    return query.getSingleResult();
}

Ou ao fazer uma seleção parcial:

public List<Object[]> String getName() {
    Query<Object[]> query = sessionFactory.getCurrentSession().createQuery("SELECT id, name FROM Cat", Object[].class);
    return query.list();
}
David DeMar
fonte
1

Tivemos o mesmo problema. Mas não foi um grande problema para nós porque tínhamos que resolver outros problemas mais importantes com o Hibernate Query e Session.

Especificamente:

  1. controlar quando uma transação pode ser confirmada. (queríamos contar quantas vezes um tx foi "iniciado" e só confirmar quando o tx foi "encerrado" o mesmo número de vezes que foi iniciado. Útil para código que não sabe se precisa iniciar uma transação. Agora qualquer código que precise de um tx apenas "inicia" um e termina quando terminar.)
  2. Coleta de métricas de desempenho.
  3. Atrasar o início da transação até que se saiba que algo realmente será feito.
  4. Comportamento mais suave para query.uniqueResult ()

Então, para nós, temos:

  1. Crie uma interface (AmplafiQuery) que estende Query
  2. Crie uma classe (AmplafiQueryImpl) que estende AmplafiQuery e envolve um org.hibernate.Query
  3. Crie um Txmanager que retorna um Tx.
  4. Tx tem os vários métodos createQuery e retorna AmplafiQueryImpl

E por fim,

AmplafiQuery tem um "asList ()" que é uma versão genérica habilitada de Query.list () AmplafiQuery tem um "unique ()" que é uma versão genérica habilitada de Query.uniqueResult () (e apenas registra um problema em vez de lançar um exceção)

Isso é muito trabalho apenas para evitar @SuppressWarnings. No entanto, como eu disse (e listei), existem muitos outros melhores! razões para fazer o trabalho de embrulho.

Pat
fonte
0

Eu sei que isso é mais antigo, mas 2 pontos a serem observados a partir de hoje em Matt Quails Answer.

Ponto 1

este

List<Cat> cats = Collections.checkedList(Cat.class, q.list());

Deveria ser isso

List<Cat> cats = Collections.checkedList(q.list(), Cat.class);

Ponto 2

Deste

List list = q.list();

para isso

List<T> list = q.list();

reduziria outros avisos obviamente em marcadores de tag de resposta originais foram removidos pelo navegador.

Tony Shih
fonte
Tente fazer com que as respostas sejam uma resposta à pergunta, não uma resposta a outra resposta. É bom incluir um comentário sobre a resposta de Matt Quail para dizer que ele está desatualizado, mas apenas escreva sua resposta de forma pura e correta.
Cory Kendall
-1

Experimente isto:

Query q = sess.createQuery("from Cat cat");
List<?> results = q.list();
for (Object obj : results) {
    Cat cat = (Cat) obj;
}
Brian Ngure
fonte
4
Esta é uma cópia incorreta da resposta de Joe Dean , porque você ainda precisa fazer algo com a catinstância.
Artjom B.
-1

Uma boa solução para evitar avisos de segurança de tipo com consulta de hibernação é usar uma ferramenta como TorpedoQuery para ajudá-lo a construir hql de tipo seguro.

Cat cat = from(Cat.class);
org.torpedoquery.jpa.Query<Entity> select = select(cat);
List<Cat> cats = select.list(entityManager);
xjodoin
fonte
-1
TypedQuery<EntityName> createQuery = entityManager.createQuery("from EntityName", EntityName.class);
List<EntityName> resultList = createQuery.getResultList();
Rakesh Singh Balhara
fonte
3
Tente fornecer uma boa descrição sobre como sua solução funciona. Veja: Como escrevo uma boa resposta? . Obrigado.
Shree
1
Você pode adicionar alguma explicação ao seu código de forma que outras pessoas possam aprender com ele?
Nico Haase
-6

Se você não quiser usar @SuppressWarnings ("unchecked"), você pode fazer o seguinte.

   Query q = sess.createQuery("from Cat cat");
   List<?> results =(List<?>) q.list();
   List<Cat> cats = new ArrayList<Cat>();
   for(Object result:results) {
       Cat cat = (Cat) result;
       cats.add(cat);
    }

Para sua informação - criei um método utilitário que faz isso para mim, de forma que não bagunce meu código e não precise usar @SupressWarning.

Joe Dean
fonte
2
Isso é simplesmente estúpido. Você está adicionando sobrecarga de tempo de execução para superar um problema totalmente relacionado ao compilador. Lembre-se de que os argumentos de tipo não são reificados, portanto, não há verificação de tempo de execução do tipo.
John Nilsson,
Concordo, se você ainda quiser fazer algo assim, poderá adicionar verificação de tempo de execução do tipo com: List <Cat> cats = Collections.checkedList (new ArrayList <Cat> (), Cat.class); cats.addAll (q.list ()); Isso deve funcionar.
ddcruver