Qual è il modo "corretto" per eseguire il cast di Hibernate Query.list () in List <Type>?


84

Sono un principiante con Hibernate e sto scrivendo un metodo semplice per restituire un elenco di oggetti che corrispondono a un filtro specifico. List<Foo>sembrava un tipo di ritorno naturale.

Qualunque cosa faccia, non riesco a rendere felice il compilatore, a meno che non ne utilizzi un brutto @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();
    }
}

Vorrei sbarazzarmeneSuppressWarnings . Ma se lo faccio, ricevo l'avvertimento

Warning: Unchecked cast from List to List<Foo>

(Posso ignorarlo, ma vorrei non ottenerlo in primo luogo) e se rimuovo il generico per conformarmi al .list()tipo restituito, ottengo l'avvertenza

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

Ho notato che org.hibernate.mapping fa dichiarare unList ; ma è un tipo completamente diverso - Queryrestituisce a java.util.List, come tipo grezzo. Trovo strano che un Hibernate recente (4.0.x) non implementa i tipi parametrizzati, quindi sospetto che sia io invece a fare qualcosa di sbagliato.

Sembra molto simile risultato di Cast Hibernate a un elenco di oggetti , ma qui non ho errori "difficili" (il sistema conosce il tipo Foo e non sto usando una SQLQuery ma una semplice Query). Quindi nessuna gioia.

Ho anche esaminato l' eccezione di Hibernate Class Cast poiché sembrava promettente, ma poi mi sono reso conto di no realtà ricevo Exception... il mio problema è solo quello di un avvertimento - uno stile di codifica, se vuoi.

La documentazione su jboss.org, i manuali di Hibernate e diversi tutorial non sembrano trattare l'argomento in modo così dettagliato (o non ho cercato nei posti giusti?). Quando entrano nei dettagli, usano il casting al volo - e questo su tutorial che non erano sul sito ufficiale jboss.org, quindi sono un po 'diffidente.

Il codice, una volta compilato, funziona senza problemi apparenti ... che io sappia ... ancora; ei risultati sono quelli attesi.

Quindi: lo sto facendo bene? Mi manca qualcosa di ovvio? Esiste un modo "ufficiale" o "consigliato" per farlo ?

Risposte:


101

La risposta breve @SuppressWarningsè la strada giusta da percorrere.

Risposta lunga, Hibernate restituisce un raw Listdal Query.listmetodo, vedi qui . Questo non è un bug con Hibernate o qualcosa che può essere risolto, il tipo restituito dalla query non è noto in fase di compilazione.

Quindi quando scrivi

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

Stai eseguendo un cast non sicuro da Lista List<MyObject>- questo non può essere evitato.

Non c'è modo di eseguire il cast in sicurezza poiché List potrebbe contenere qualsiasi cosa.

L'unico modo per far sparire l'errore è ancora più brutto

final List<MyObject> list = new LinkedList<>();
for(final Object o : query.list()) {
    list.add((MyObject)o);
}

4
Volevo solo votare la tua risposta, sperando che ne arrivasse una migliore. Invece ho scoperto questo problema denominato "brutto cast" sia da Bruce Eckel (Thinking in Java) che da Robert Sedgewick - il Sedgewick. Ho anche trovato stackoverflow.com/questions/509076/… . Sospiro.
LSerni

6
Mi piace il tuo stile di utilizzofinal
Pavel

9
Se i ragazzi in ibernazione aggiungono un argomento con il tipo Class<?>in list(), il problema potrebbe essere risolto. È un peccato usare un'API così brutta.
Bin Wang

@BinWang allora il cast non sicuro avverrebbe da qualche altra parte, non risolve il problema, lo sposta e basta. Inutile dire che l'API HQL è stata effettivamente deprecata da anni ormai. JPA dispone di un'API di query indipendente dai tipi chiamata API di query dei criteri .
Boris the Spider

2
@PeteyPabPro Pur concordando sul fatto che i rawtypes dovrebbero essere evitati, non sono d'accordo sul fatto che i risultati di una query debbano essere trattati come un file List<Object>. È necessario eseguire il cast dei risultati nel tipo previsto e aggiungere gli unit test per garantire che la query restituisca i risultati corretti. È inaccettabile che gli errori con le query vengano visualizzati " più avanti nel codice ". Il tuo esempio è un argomento contro le pratiche di codifica che dovrebbero essere un anatema nel 21 ° secolo. Direi che non è mai accettabile avere un file List<Object>.
Boris the Spider

26

La risoluzione consiste nell'usare invece TypedQuery. Quando crei una query da EntityManager chiamala in questo modo:

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

Funziona allo stesso modo per query con nome, query con nome native, ecc. I metodi corrispondenti hanno gli stessi nomi di quelli che restituirebbero la query vanilla. Usa questo invece di una query ogni volta che conosci il tipo di ritorno.


1
Come nota a margine, questo non funziona per le query puramente native che stai creando in linea. La query restituita è sempre e solo una query, non una query tipizzata :(
Taugenichts

Ora, il messaggio di errore è sparito e l'elenco risultante utilizza gli oggetti effettivi invece dell'oggetto Object .. ottima risposta!
Johannes

Questo sembra essere stato rilasciato in Hibernate 5.0. Non lo vedo in 4.3.11 ma il seguente articolo si riferisce a 5.3. wiki.openbravo.com/wiki/… Vedo anche un post su stackoverflow del gennaio 2014 che fa riferimento ad esso: stackoverflow.com/a/21354639/854342 .
Curtis Yallop

Fa parte di hibernate-jpa-2.0-api. Puoi usarlo con Hibernate 4.3, poiché attualmente lo sto usando su Hibernate 4.3.
Taugenichts

6

Puoi evitare gli avvisi del compilatore con soluzioni alternative come questa:

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

Ma ci sono alcuni problemi con questo codice:

  • creato ArrayList superfluo
  • ciclo non necessario su tutti gli elementi restituiti dalla query
  • codice più lungo.

E la differenza è solo estetica, quindi utilizzare soluzioni alternative è, a mio parere, inutile.

Devi convivere con questi avvertimenti o sopprimerli.


1
Sono d'accordo con l'inutilità. Sopprimerò, anche se va contro la mia tendenza. Grazie lo stesso e +1.
LSerni

2
> Devi convivere con questi avvertimenti o sopprimerli. È sempre meglio sopprimere gli avvisi sbagliati, oppure puoi perdere gli avvisi giusti in uno spam di avvisi errati non soppressi
SpongeBobFan,

6

Per rispondere alla tua domanda, non esiste un "modo corretto" per farlo. Ora, se è solo l'avvertimento che ti dà fastidio, il modo migliore per evitarne la proliferazione è avvolgere il Query.list()metodo in un DAO:

public class MyDAO {

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

In questo modo puoi usare l' @SuppressWarnings("unchecked")unica volta.


Benvenuto in Stack Overflow ! Comunque, non dimenticare di fare il tour
Sнаношƒаӽ

3

L'unico modo in cui ha funzionato per me era con un Iterator.

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

Con altri metodi che ho trovato, ho avuto problemi di lancio


Quali "problemi di cast"? Ho sempre inserito direttamente l'elenco, in che modo quanto sopra è più conciso o più sicuro?
Giovanni Botta

Non riesco a trasmettere direttamente. Ci sono stati problemi di lancio perché non è stato possibile effettuare il cast da Object to Destination
Popa Andrei

Sai che Hibernate può costruirne uno Destinstionper te, giusto? Utilizzando la select newsintassi. Questo non è certamente l'approccio giusto.
Boris the Spider

Anch'io ho avuto la stessa esperienza. Poiché la mia query restituisce campi diversi da più tabelle che non sono collegate tra loro. Quindi l'unico modo che ha funzionato per me è stato questo. Grazie :)
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);
}

Sì, questa è fondamentalmente la stessa "bruttezza" suggerita da Boris, con un cast all'interno del ciclo.
LSerni

2

Usi un ResultTransformer come quello:

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

1
Non posso provarlo ora, ma ... cosa cambia? qè ancora Querye quindi q.list()è ancora un java.util.Listtipo grezzo . Il cast è quindi ancora non controllato; avendo cambiato il tipo di oggetto internamente non dovrebbe valere nulla ...
LSerni

Sì, il cast è ancora deselezionato, ma con una corretta denominazione dei campi, l'impostazione di un resultTransformer fa il lavoro di lanciare gli oggetti come il POJO desiderato. Vedi questo post su
stackoverflow

from foo where activenon è una query nativa. Quindi non è necessario un trasformatore di risultati, poiché la mappatura predefinita sarà sufficiente. La domanda non riguarda il casting dei campi POJO, ma il casting dell'oggetto risultato. Un trasformatore di risultato non sarebbe d'aiuto qui.
Tobias Liefke

0

Il modo corretto è usare i trasformatori 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);

L'iterazione attraverso Object [] è ridondante e avrebbe qualche penalità nelle prestazioni. Informazioni dettagliate sull'utilizzo dei transofrmers sono disponibili qui: Transformers for HQL e SQL

Se stai cercando una soluzione ancora più semplice, puoi utilizzare un trasformatore di mappa fuori dagli schemi:

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

La domanda non riguardava la trasformazione dei risultati. Si trattava di trasmettere i Queryrisultati, che è ancora necessario nel tuo esempio. E il tuo esempio non ha nulla a che fare con l'originale from foo where active.
Tobias Liefke

0

Solo usando Transformers Non ha funzionato per me stavo ricevendo un'eccezione di tipo cast.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) non ha funzionato perché stavo ottenendo Array of Object nell'elemento della lista di restituzione non nel tipo fisso di elemento della lista MYEngityName.

Ha funzionato per me quando apporto le seguenti modifiche Quando ho aggiunto sqlQuery.addScalar(-)ogni colonna selezionata e il suo tipo e per una colonna di tipo String specifica, non è necessario mappare il suo tipo. piaceaddScalar("langCode");

E mi sono unito a MYEngityName con NextEnity, non possiamo solo select *nella query che fornirà un array di oggetti nell'elenco di restituzione.

Di seguito il codice di esempio:

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

Potrebbe aiutare qualcuno. in questo modo lavora per me.


-1

Ho trovato la soluzione migliore qui , la chiave di questo problema è il metodo 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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.