¿Cuál es la forma "correcta" de convertir Hibernate Query.list () en List <Type>?

84

Soy un novato con Hibernate y estoy escribiendo un método simple para devolver una lista de objetos que coinciden con un filtro específico. List<Foo>parecía un tipo de retorno natural.

Haga lo que haga, parece que no puedo hacer feliz al compilador, a menos que emplee un archivo feo @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();
    }
}

Me gustaría deshacerme de esoSuppressWarnings . Pero si lo hago, recibo la advertencia

Warning: Unchecked cast from List to List<Foo>

(Puedo ignorarlo, pero me gustaría no obtenerlo en primer lugar), y si elimino el genérico para cumplir con el .list()tipo de retorno, aparece la advertencia

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

Noté que org.hibernate.mapping hace declarar List; pero es un tipo completamente diferente: Querydevuelve a java.util.List, como tipo sin formato. Me parece extraño que un Hibernate reciente (4.0.x) no implemente tipos parametrizados, así que sospecho que soy yo, en cambio, haciendo algo mal.

Se parece mucho al resultado de Cast Hibernate a una lista de objetos , pero aquí no tengo errores "duros" (el sistema conoce el tipo Foo, y no estoy usando una SQLQuery sino una consulta directa). Así que no hay alegría.

También he analizado la excepción de conversión de clase de Hibernate, ya que parecía prometedor, pero luego me di cuenta de que en realidad no obtengo ninguna Exception... mi problema es solo una advertencia, un estilo de codificación, por así decirlo.

La documentación en jboss.org, los manuales de Hibernate y varios tutoriales no parecen cubrir el tema con tanto detalle (¿o no busqué en los lugares correctos?). Cuando entran en detalles, usan la transmisión sobre la marcha, y esto en tutoriales que no estaban en el sitio oficial jboss.org, así que soy un poco cauteloso.

El código, una vez compilado, se ejecuta sin problema aparente ... que yo sepa ... todavía; y los resultados son los esperados.

Entonces: ¿estoy haciendo esto bien? ¿Me estoy perdiendo algo obvio? ¿Existe una forma "oficial" o "recomendada" de hacerlo ?

LSerni
fuente

Respuestas:

101

La respuesta corta @SuppressWarningses el camino correcto a seguir.

Respuesta larga, Hibernate devuelve un crudo Listdel Query.listmétodo, ver aquí . Esto no es un error con Hibernate o algo que se pueda resolver, el tipo devuelto por la consulta no se conoce en el momento de la compilación.

Por eso cuando escribes

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

Está realizando un lanzamiento no seguro de Lista List<MyObject>; esto no se puede evitar.

No hay forma de que pueda realizar el yeso de manera segura, ya que List podría contener cualquier cosa.

La única forma de hacer desaparecer el error es aún más feo

final List<MyObject> list = new LinkedList<>();
for(final Object o : query.list()) {
    list.add((MyObject)o);
}
Boris la Araña
fuente
4
Solo iba a votar a favor tu respuesta, esperando que apareciera una mejor. En su lugar, descubrí este problema al que se refieren como "feo reparto" tanto por Bruce Eckel (Pensando en Java) como por Robert Sedgewick, el Sedgewick. También encontré stackoverflow.com/questions/509076/… . Suspiro.
LSerni
6
Me gusta tu estilo de usofinal
Pavel
9
Si los chicos de Hibernate agregan un argumento con el tipo Class<?>de entrada list(), el problema podría resolverse. Es una pena usar una API tan fea.
Bin Wang
@BinWang, entonces el elenco inseguro ocurriría en otro lugar, no resuelve el problema, solo lo mueve. No hace falta decir que la API de HQL ha estado obsoleta de manera efectiva durante años . JPA tiene una API de consulta segura de tipos llamada API de consulta de criterios .
Boris the Spider
2
@PeteyPabPro Si bien estoy de acuerdo en que se deben evitar los tipos sin formato, no estoy de acuerdo en que los resultados de una consulta se deben tratar como List<Object>. Los resultados se deben convertir al tipo esperado y se deben agregar pruebas unitarias para garantizar que la consulta devuelva los resultados correctos. Es inaceptable que aparezcan errores con consultas " más adelante en el código ". Su ejemplo es un argumento en contra de las prácticas de codificación que deberían ser un anatema en el siglo XXI. Sugeriría que nunca es aceptable tener un List<Object>.
Boris the Spider
26

La resolución es utilizar TypedQuery en su lugar. Al crear una consulta desde el EntityManager, llámelo así:

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

Esto también funciona de la misma forma para consultas con nombre, consultas con nombre nativo, etc. Los métodos correspondientes tienen los mismos nombres que los que devolverían la consulta básica. Solo use esto en lugar de una Consulta siempre que sepa el tipo de retorno.

Taugenichts
fuente
1
Como nota al margen, esto no funciona para consultas puramente nativas que está creando en línea. La consulta devuelta es solo una consulta, no una consulta de tipo :(
Taugenichts
Ahora, el mensaje de error desapareció y la lista resultante usa los objetos reales en lugar del objeto Objeto ... ¡gran respuesta!
Johannes
Esto parece estar disponible en hibernate 5.0. No lo veo en 4.3.11 pero el siguiente artículo se refiere a 5.3. wiki.openbravo.com/wiki/ ... También veo una publicación de stackoverflow de enero de 2014 que se refiere a ella: stackoverflow.com/a/21354639/854342 .
Curtis Yallop
Es parte de hibernate-jpa-2.0-api. Puede usar esto con hibernate 4.3, ya que actualmente lo estoy usando en hibernate 4.3.
Taugenichts
6

Puede evitar la advertencia del compilador con soluciones alternativas como esta:

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

Pero hay algunos problemas con este código:

  • creado superfluo ArrayList
  • bucle innecesario sobre todos los elementos devueltos por la consulta
  • código más largo.

Y la diferencia es solo cosmética, por lo que usar tales soluciones es, en mi opinión, inútil.

Tienes que vivir con estas advertencias o reprimirlas.

Grzegorz Olszewski
fuente
1
Estoy de acuerdo con la inutilidad. Reprimiré, incluso si va en contra de mi corriente. Gracias de todos modos y +1.
LSerni
2
> Tienes que vivir con estas advertencias o reprimirlas. Es siempre mejor a las advertencias Supress que están mal, o se puede perder la advertencia justo en un spam de avisos erróneos unsupressed
SpongeBobFan
6

Para responder a su pregunta, no existe una "forma adecuada" de hacerlo. Ahora bien, si es solo la advertencia lo que le molesta, la mejor manera de evitar su proliferación es envolver el Query.list()método en un DAO:

public class MyDAO {

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

De esta manera puedes usar el @SuppressWarnings("unchecked")único una vez.

Pdv
fuente
¡Bienvenido a Stack Overflow ! De todos modos, no olvides hacer el recorrido
Sна Sошƒаӽ
3

La única forma en que me funcionó fue con un 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);
    }

Con otros métodos que encontré, tuve problemas de transmisión

Popa Andrei
fuente
¿Qué "problemas de reparto"? Siempre he enviado la lista directamente, ¿cómo es lo anterior más conciso o más seguro?
Giovanni Botta
No puedo lanzar directamente. Tuvieron problemas de lanzamiento porque no pudo hacer el lanzamiento de Objeto a Destino
Popa Andrei
¿Sabes que Hibernate puede construir una Destinstionpara ti, verdad? Usando la select newsintaxis. Este ciertamente no es el enfoque correcto.
Boris the Spider
Yo también tuve la misma experiencia. Como mi consulta devuelve diferentes campos de varias tablas que no están conectadas entre sí. Así que la única forma que funcionó para mí fue esta. Gracias :)
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
fuente
Sí, esta es básicamente la misma 'fealdad' sugerida por Boris, con un elenco dentro del bucle.
LSerni
2

Usas un ResultTransformer como ese:

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
fuente
1
No puedo probar esto ahora, pero ... ¿qué cambia esto? qsigue siendo un tipo sin formato Queryy, por q.list()lo tanto, sigue siendo java.util.List. Entonces, el elenco aún no se controla; tener el tipo de objeto cambiado internamente no debería servir de nada ...
LSerni
Sí, el elenco aún no está marcado, pero con el nombre adecuado de sus campos, establecer un resultTransformer hace el trabajo de lanzar los Objetos como su POJO deseado. Vea esta publicación de stackoverflow y lea este documento de referencia de hibernación sobre el uso de consultas nativas
lakreqta
from foo where activeno es una consulta nativa. Por lo tanto, no es necesario un transformador de resultados, ya que el mapeo predeterminado será suficiente. La pregunta no es sobre la conversión de los campos POJO, sino sobre la conversión del objeto de resultado. Un transformador de resultado no ayudaría aquí.
Tobias Liefke
0

La forma correcta es usar Hibernate Transformers:

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

Iterar a través de Object [] es redundante y tendría alguna penalización en el rendimiento. Aquí encontrará información detallada sobre el uso de transofrmers: Transformers para HQL y SQL

Si está buscando una solución aún más simple, puede usar el transformador de mapa listo 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
fuente
La pregunta no se refería a la transformación de resultados. Se trataba de emitir Queryresultados, lo que todavía es necesario en su ejemplo. Y tu ejemplo no tiene nada que ver con el original from foo where active.
Tobias Liefke
0

Solo usando Transformers No funcionó para mí, estaba obteniendo una excepción de conversión de tipo.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) no funcionó porque estaba obteniendo Array of Object en el elemento de la lista de retorno, no en el tipo fijo MYEngityName del elemento de lista.

Me funcionó cuando hago los siguientes cambios. Cuando agregué sqlQuery.addScalar(-)cada columna seleccionada y su tipo y para una columna de tipo de cadena específica, no tenemos que asignar su tipo. me gustaaddScalar("langCode");

Y me he unido a MYEngityName con NextEnity, no podemos solo select *en la Consulta, dará una matriz de Objeto en la lista de retorno.

Ejemplo de código a continuación:

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

Podría ayudar a alguien. de esta manera trabaja para mí.

Laxman G
fuente
-1

Encontré la mejor solución aquí , la clave de este problema es el 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
fuente