Was ist der „richtige“ Weg, um Hibernate Query.list () in List <Type> umzuwandeln?

84

Ich bin ein Neuling im Ruhezustand und schreibe eine einfache Methode, um eine Liste von Objekten zurückzugeben, die einem bestimmten Filter entsprechen. List<Foo>schien ein natürlicher Rückgabetyp zu sein.

Was auch immer ich tue, ich kann den Compiler nicht glücklich machen, es sei denn, ich beschäftige einen hässlichen @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();
    }
}

Das würde ich gerne loswerdenSuppressWarnings . Aber wenn ich das tue, bekomme ich die Warnung

Warning: Unchecked cast from List to List<Foo>

(Ich kann es ignorieren, aber ich möchte es gar nicht erst bekommen), und wenn ich das Generikum entferne, um es dem .list()Rückgabetyp anzupassen , erhalte ich die Warnung

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

Ich bemerkte , dass org.hibernate.mapping tut ein erklären List; aber es ist insgesamt ein anderer Typ - Querygibt a java.util.Listals Rohtyp zurück. Ich finde es seltsam, dass ein neuerer Ruhezustand (4.0.x) keine parametrisierten Typen implementiert, daher vermute ich, dass ich stattdessen etwas falsch mache.

Es sieht dem Ergebnis von Cast Hibernate für eine Liste von Objekten sehr ähnlich , aber hier habe ich keine "harten" Fehler (das System kennt den Typ Foo und ich verwende keine SQLQuery, sondern eine direkte Abfrage). Also keine Freude.

Ich habe auch betrachtet Hibernate Class Cast Exception , da es viel versprechend aussah, aber dann merkte ich , dass ich nicht wirklich bekommen jede Exception... mein Problem ist nur , dass eine Warnung - eine Codierung Stil, wenn man so will.

Die Dokumentation auf jboss.org, Hibernate-Handbücher und einige Tutorials scheinen das Thema nicht so detailliert zu behandeln (oder ich habe nicht an den richtigen Stellen gesucht?). Wenn sie ins Detail gehen, verwenden sie On-the-Fly-Casting - und dies in Tutorials, die nicht auf der offiziellen jboss.org-Website waren, also bin ich etwas vorsichtig.

Der einmal kompilierte Code läuft ohne offensichtliche Probleme ... von denen ich weiß ... bis jetzt; und die Ergebnisse sind die erwarteten.

Also: mache ich das richtig? Vermisse ich etwas Offensichtliches? Gibt es einen "offiziellen" oder "empfohlenen" Weg, dies zu tun ?

LSerni
quelle

Antworten:

100

Eine kurze Antwort @SuppressWarningsist der richtige Weg.

Lange Antwort, Hibernate gibt ein Raw Listvon der Query.listMethode zurück, siehe hier . Dies ist kein Fehler im Ruhezustand oder etwas, das behoben werden kann. Der von der Abfrage zurückgegebene Typ ist zum Zeitpunkt der Kompilierung nicht bekannt .

Deshalb beim Schreiben

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

Sie machen eine unsichere Besetzung von Listbis List<MyObject>- dies kann nicht vermieden werden.

Es gibt keine Möglichkeit, die Besetzung sicher durchzuführen, da sie alles enthalten List könnte .

Der einzige Weg, um den Fehler zu beseitigen, ist der noch hässlichere

final List<MyObject> list = new LinkedList<>();
for(final Object o : query.list()) {
    list.add((MyObject)o);
}
Boris die Spinne
quelle
4
Ich wollte nur Ihre Antwort positiv bewerten, in der Hoffnung, dass eine bessere vorbeikommt. Stattdessen fand ich dieses Problem heraus, das sowohl von Bruce Eckel (Thinking in Java) als auch von Robert Sedgewick - dem Sedgewick - als "hässliche Besetzung" bezeichnet wurde . Ich habe auch stackoverflow.com/questions/509076/… gefunden . Seufzer.
LSerni
6
Ich mag Ihren Stilfinal
Pavel
8
Wenn Hibernate Jungs ein Argument mit Typ hinzufügen , Class<?>in list(), kann das Problem gelöst werden. Es ist eine Schande, solch eine hässliche API zu verwenden.
Bin Wang
@BinWang dann würde die unsichere Besetzung woanders passieren, es löst das Problem nicht - es bewegt es nur. Es erübrigt sich die HQL sagen API für effektiv veraltet ist Jahre jetzt. JPA verfügt über eine typsichere Abfrage-API, die als Kriterienabfrage-API bezeichnet wird .
Boris die Spinne
1
@PeteyPabPro Obwohl ich damit einverstanden bin, dass Rohtypen vermieden werden sollten, bin ich nicht der Meinung, dass die Ergebnisse einer Abfrage als behandelt werden sollten List<Object>. Die Ergebnisse sollten in den erwarteten Typ umgewandelt und Komponententests hinzugefügt werden, um sicherzustellen, dass die Abfrage die richtigen Ergebnisse zurückgibt. Es ist nicht akzeptabel, dass Fehler bei Abfragen " später im Code " auftreten. Ihr Beispiel ist ein Argument gegen Kodierungspraktiken, die im 21. Jahrhundert ein Gräuel sein sollten. Es ist, würde ich vorschlagen, niemals akzeptabel, eine zu haben List<Object>.
Boris die Spinne
26

Die Lösung besteht darin, stattdessen TypedQuery zu verwenden. Wenn Sie eine Abfrage aus dem EntityManager erstellen, rufen Sie sie stattdessen folgendermaßen auf:

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

Dies funktioniert auch für benannte Abfragen, native benannte Abfragen usw. Die entsprechenden Methoden haben dieselben Namen wie diejenigen, die die Vanille-Abfrage zurückgeben würden. Verwenden Sie dies einfach anstelle einer Abfrage, wenn Sie den Rückgabetyp kennen.

Taugenichts
quelle
1
Nebenbei bemerkt funktioniert dies nicht für rein native Abfragen, die Sie inline erstellen. Die zurückgegebene Abfrage ist immer nur eine Abfrage, keine typisierte Abfrage :(
Taugenichts
Jetzt ist die Fehlermeldung verschwunden und die resultierende Liste verwendet die tatsächlichen Objekte anstelle des Objektobjekts. Tolle Antwort!
Johannes
Dies scheint im Ruhezustand 5.0 freigegeben zu sein. Ich sehe es nicht in 4.3.11, aber der folgende Artikel bezieht sich auf 5.3. wiki.openbravo.com/wiki/… Ich sehe auch einen Stackoverflow-Beitrag vom Januar 2014, der darauf verweist: stackoverflow.com/a/21354639/854342 .
Curtis Yallop
Es ist Teil von hibernate-jpa-2.0-api. Sie können dies mit Ruhezustand 4.3 verwenden, da ich es derzeit im Ruhezustand 4.3 verwende.
Taugenichts
6

Sie können Compiler-Warnungen mit folgenden Problemumgehungen vermeiden:

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

Es gibt jedoch einige Probleme mit diesem Code:

  • überflüssige ArrayList erstellt
  • unnötige Schleife über alle von der Abfrage zurückgegebenen Elemente
  • längerer Code.

Und der Unterschied ist nur kosmetischer Natur, daher ist die Verwendung solcher Problemumgehungen meiner Meinung nach sinnlos.

Sie müssen mit diesen Warnungen leben oder sie unterdrücken.

Grzegorz Olszewski
quelle
1
Ich stimme der Sinnlosigkeit zu. Ich werde unterdrücken, auch wenn es gegen meinen Strich geht. Trotzdem danke und +1.
LSerni
2
> Sie müssen mit diesen Warnungen leben oder sie unterdrücken. Es ist immer besser, Warnungen zu unterdrücken, die falsch sind, oder Sie können die richtige Warnung in einem Spam von nicht unterdrückten falschen Warnungen verpassen
SpongeBobFan
5

Um Ihre Frage zu beantworten, gibt es keinen "richtigen Weg", dies zu tun. Wenn Sie nur von der Warnung gestört werden, können Sie die Verbreitung am besten vermeiden, indem Sie die Query.list()Methode in ein DAO einbinden:

public class MyDAO {

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

Auf diese Weise können Sie die @SuppressWarnings("unchecked")nur einmal verwenden.

Pdv
quelle
Willkommen bei Stack Overflow ! Wie auch immer, vergessen Sie nicht, die Tour zu machen
Sнаđошƒаӽ
3

Die einzige Möglichkeit für mich war die Arbeit mit einem 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);
    }

Mit anderen Methoden, die ich fand, hatte ich Casting-Probleme

Popa Andrei
quelle
Welche "Besetzungsprobleme"? Ich habe die Liste immer nur direkt gegossen. Wie ist das oben Genannte prägnanter oder sicherer?
Giovanni Botta
Ich kann nicht direkt besetzen. Wurden Besetzungsprobleme, weil es nicht möglich war, eine Besetzung von Objekt zu Ziel zu machen
Popa Andrei
Sie wissen, dass Hibernate ein Destinstionfür Sie bauen kann, oder? Verwenden der select newSyntax. Dies ist sicherlich nicht der richtige Ansatz.
Boris die Spinne
Ich hatte auch die gleiche Erfahrung. Da meine Abfrage verschiedene Felder aus mehreren Tabellen zurückgibt, die nicht miteinander verbunden sind. Der einzige Weg, der für mich funktioniert hat, war dieser. Danke :)
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
quelle
Ja, dies ist im Grunde die gleiche "Hässlichkeit", die Boris vorgeschlagen hat, mit einer Besetzung innerhalb der Schleife.
LSerni
2

Sie verwenden einen ResultTransformer wie folgt:

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
quelle
1
Ich kann das jetzt nicht testen, aber ... was ändert sich daran? qist immer noch ein Queryund ist daher q.list()immer noch ein roher java.util.ListTyp. Die Besetzung ist dann noch deaktiviert; Eine interne Änderung des Objekttyps sollte nichts nützen ...
LSerni
Ja, die Umwandlung ist immer noch deaktiviert, aber bei korrekter Benennung Ihrer Felder übernimmt das Festlegen eines Ergebnistransformators die Aufgabe, die Objekte als Ihr gewünschtes POJO zu übertragen. Sehen Sie sich diesen Stapelüberlauf-Beitrag an und lesen Sie dieses Referenzdokument zum Ruhezustand über die Verwendung nativer Abfragen
lakreqta
from foo where activeist keine native Abfrage. Es ist also kein Ergebnis-Transformator erforderlich, da die Standardzuordnung ausreicht. Bei der Frage geht es nicht um das Umwandeln der POJO-Felder, sondern um das Umwandeln des Ergebnisobjekts. Ein Ergebnis-Transformator würde hier nicht helfen.
Tobias Liefke
0

Der richtige Weg ist die Verwendung von Transformatoren im Ruhezustand:

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

Das Durchlaufen von Object [] ist redundant und hätte einige Leistungseinbußen zur Folge. Detaillierte Informationen zur Verwendung von Transofrmern finden Sie hier: Transformers für HQL und SQL

Wenn Sie nach einer noch einfacheren Lösung suchen, können Sie einen sofort einsatzbereiten Kartentransformator verwenden:

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
quelle
Die Frage betraf nicht die Ergebnisumwandlung. Es ging um das Casting von QueryErgebnissen - was in Ihrem Beispiel immer noch benötigt wird. Und Ihr Beispiel hat nichts mit dem Original zu tun from foo where active.
Tobias Liefke
0

Nur Transformers verwenden Es hat bei mir nicht funktioniert. Ich habe eine Ausnahme vom Typ Cast erhalten.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) hat nicht funktioniert, weil ich Array of Object im Rückgabelistenelement erhalten habe, nicht den festen MYEngityName-Typ des Listenelements.

Es hat bei mir funktioniert, wenn ich folgende Änderungen vornehme. Wenn ich sqlQuery.addScalar(-)jede ausgewählte Spalte und ihren Typ hinzugefügt habe und für eine bestimmte Spalte vom Typ String den Typ nicht zuordnen muss. mögenaddScalar("langCode");

Und ich habe MYEngityName mit NextEnity verbunden. Wir können nicht nur select *in der Abfrage ein Array von Objekten in der Rückgabeliste angeben .

Unten Codebeispiel:

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

Es könnte jemandem helfen. auf diese Weise für mich arbeiten.

Laxman G.
quelle
-1

Ich habe hier die beste Lösung gefunden . Der Schlüssel zu diesem Problem ist die addEntity- Methode

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
quelle