Jaki jest „właściwy” sposób rzutowania Hibernate Query.list () na List <Type>?

84

Jestem nowicjuszem w Hibernate i piszę prostą metodę zwracania listy obiektów pasujących do określonego filtra. List<Foo>wydawało się naturalnym typem powrotu.

Cokolwiek robię, nie mogę uszczęśliwić kompilatora, chyba że użyję brzydkiego @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();
    }
}

Chciałbym się tego pozbyćSuppressWarnings . Ale jeśli to zrobię, otrzymam ostrzeżenie

Warning: Unchecked cast from List to List<Foo>

(Mogę to zignorować, ale w pierwszej kolejności nie chciałbym tego dostać), a jeśli usunę rodzaj ogólny, aby dostosować się do .list()zwracanego typu, otrzymuję ostrzeżenie

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

Zauważyłem, że org.hibernate.mapping ma zadeklarować List; ale jest to zupełnie inny typ - Queryzwraca a java.util.List, jako typ surowy. Wydaje mi się dziwne, że niedawny Hibernate (4.0.x) nie zaimplementował sparametryzowanych typów, więc podejrzewam, że to ja zamiast tego robię coś złego.

Wygląda to bardzo podobnie do wyniku Cast Hibernate na liście obiektów , ale tutaj nie mam „twardych” błędów (system zna typ Foo i nie używam zapytania SQLQuery, ale prostego zapytania). Więc nie ma radości.

Przyjrzałem się również Wyjątkowi rzutowania klasy Hibernate, ponieważ wyglądał obiecująco, ale potem zdałem sobie sprawę, że tak naprawdę nie dostaję żadnego Exception... moim problemem jest tylko ostrzeżenie - styl kodowania, jeśli wolisz.

Dokumentacja na jboss.org, podręczniki Hibernate i kilka samouczków nie wydają się opisywać tego tematu w tak szczegółowy sposób (czy nie szukałem w odpowiednich miejscach?). Kiedy wchodzą w szczegóły, używają castingu w locie - i to w samouczkach, których nie było na oficjalnej stronie jboss.org, więc jestem trochę ostrożny.

Po skompilowaniu kod działa bez widocznego problemu… o którym wiem… jeszcze; a rezultaty są oczekiwane.

Więc: czy robię to dobrze? Czy brakuje mi czegoś oczywistego? Czy istnieje „oficjalny” lub „zalecany” sposób na zrobienie tego ?

LSerni
źródło

Odpowiedzi:

101

Krótka odpowiedź @SuppressWarningsto właściwa droga.

Długa odpowiedź, Hibernate zwraca surową wartość Listz Query.listmetody, patrz tutaj . To nie jest błąd Hibernacji ani coś, co można rozwiązać, typ zwracany przez zapytanie nie jest znany w czasie kompilacji.

Dlatego kiedy piszesz

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

Wykonujesz niebezpieczną rzut z Listdo List<MyObject>- nie da się tego uniknąć.

Nie ma sposobu, abyś mógł bezpiecznie wykonać obsadę, ponieważ List może zawierać wszystko.

Jedynym sposobem, aby ten błąd zniknął, jest jeszcze brzydszy

final List<MyObject> list = new LinkedList<>();
for(final Object o : query.list()) {
    list.add((MyObject)o);
}
Pająk Boris
źródło
4
Chciałem tylko zagłosować za twoją odpowiedzią, mając nadzieję, że nadejdzie lepsza. Zamiast tego okazało się, problem ten określa się jako "brzydkie obsadzie" zarówno przez Bruce Eckel (Thinking in Java) i Robert Sedgewick - w Sedgewick. Znalazłem również stackoverflow.com/questions/509076/… . Westchnienie.
LSerni
6
Podoba mi się twój styl używaniafinal
Pavel,
9
Jeśli hibernacji chłopaki dodać argument z rodzaju Class<?>w list(), problem może zostać rozwiązany. Szkoda używać tak brzydkiego API.
Bin Wang,
@BinWang, to niebezpieczne przesyłanie zdarzyło się gdzie indziej, nie rozwiązuje problemu - po prostu go przenosi. Nie trzeba dodawać API HQL został skutecznie przestarzałe dla lat teraz. JPA ma interfejs API zapytań bezpiecznego typu o nazwie Criteria Query API .
Boris the Spider
2
@PeteyPabPro Chociaż zgadzam się, że należy unikać typów surowych, nie zgadzam się, że wyniki zapytania należy traktować jako plik List<Object>. Wyniki powinny być rzutowane na oczekiwany typ i należy dodać testy jednostkowe, aby upewnić się, że zapytanie zwróci prawidłowe wyniki. Niedopuszczalne jest występowanie błędów w zapytaniach pojawiających się „w dalszej części kodu ”. Twój przykład jest argumentem przeciwko praktykom kodowania, które powinny być przekleństwem w XXI wieku. To, chciałbym zaproponować, nie do przyjęcia, aby mieć List<Object>.
Boris the Spider
26

Rozdzielczość polega na użyciu zamiast tego TypedQuery. Podczas tworzenia zapytania z EntityManager zamiast tego wywołaj je w ten sposób:

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

Działa to tak samo w przypadku nazwanych zapytań, natywnych nazwanych zapytań itp. Odpowiednie metody mają takie same nazwy, jak te, które zwracają zapytanie proste. Po prostu użyj tego zamiast zapytania zawsze, gdy znasz typ zwracanej wartości.

Taugenichts
źródło
1
Na marginesie, nie działa to w przypadku czysto natywnych zapytań, które tworzysz w tekście. Zwrócone zapytanie jest zawsze tylko zapytaniem, a nie typowym zapytaniem :(
Taugenichts
Teraz komunikat o błędzie zniknął, a wynikowa lista używa rzeczywistych obiektów zamiast obiektu Object ... świetna odpowiedź!
Johannes,
Wydaje się, że zostało to wydane w hibernacji 5.0. Nie widzę tego w 4.3.11, ale poniższy artykuł odnosi się do 5.3. wiki.openbravo.com/wiki/… Widzę również post stackoverflow ze stycznia 2014 roku odnoszący się do niego: stackoverflow.com/a/21354639/854342 .
Curtis Yallop
Jest częścią hibernate-jpa-2.0-api. Możesz tego użyć z hibernacją 4.3, ponieważ obecnie używam go w hibernacji 4.3.
Taugenichts,
6

Możesz uniknąć ostrzeżenia kompilatora, stosując takie obejścia:

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

Ale są pewne problemy z tym kodem:

  • utworzył zbędną ArrayList
  • niepotrzebna pętla nad wszystkimi elementami zwróconymi z zapytania
  • dłuższy kod.

A różnica jest tylko kosmetyczna, więc stosowanie takich obejść jest - moim zdaniem - bezcelowe.

Musisz żyć z tymi ostrzeżeniami lub je stłumić.

Grzegorz Olszewski
źródło
1
Zgadzam się z bezcelowością. Zwolnię, nawet jeśli będzie to sprzeczne z moim przekonaniem. Mimo wszystko dziękuję i +1.
LSerni
2
> Musisz żyć z tymi ostrzeżeniami lub je zwalczać. Zawsze lepiej jest powstrzymać ostrzeżenia, które są błędne, lub możesz przegapić właściwe ostrzeżenie w spamie zawierającym niewłaściwe ostrzeżenia
SpongeBobFan
6

Odpowiadając na twoje pytanie, nie ma „właściwego sposobu”, aby to zrobić. Jeśli przeszkadza Ci tylko ostrzeżenie, najlepszym sposobem uniknięcia jego rozprzestrzeniania się jest zawinięcie Query.list()metody w DAO:

public class MyDAO {

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

W ten sposób możesz użyć @SuppressWarnings("unchecked")tylko raz.

Pdv
źródło
Witamy w Stack Overflow ! W każdym razie nie zapomnij wziąć udziału w wycieczce
Sнаđошƒаӽ
3

Jedynym sposobem, w jaki pracowałem dla mnie, był 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);
    }

W przypadku innych metod, które znalazłem, miałem problemy z rzutami

Popa Andrei
źródło
Jakie "problemy z rzutowaniem"? Zawsze rzucałem listę bezpośrednio, dlaczego powyższe jest bardziej zwięzłe lub bezpieczniejsze?
Giovanni Botta
Nie mogę przesyłać bezpośrednio. Wystąpiły problemy z obsadą, ponieważ nie udało się wykonać obsady z obiektu do celu
Popa Andrei
Wiesz, że Hibernate może zbudować Destinstiondla Ciebie, prawda? Korzystanie ze select newskładni. To z pewnością nie jest właściwe podejście.
Boris the Spider
Ja też miałem to samo doświadczenie. Ponieważ moje zapytanie zwraca różne pola z wielu tabel, które nie są ze sobą połączone. Więc jedyny sposób zadziałał dla mnie, to ten. Dzięki :)
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
źródło
Tak, jest to w zasadzie ta sama „brzydota”, którą sugerował Boris, z obsadą w pętli.
LSerni,
2

Używasz ResultTransformer w ten sposób:

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
źródło
1
Nie mogę tego teraz przetestować, ale ... co to zmienia? qjest nadal Queryi dlatego q.list()nadal jest surowym java.util.Listtypem. Obsada jest wtedy nadal niesprawdzona; zmiana typu obiektu wewnętrznie nic nie
przyniesie
Tak, rzutowanie jest nadal niezaznaczone, ale przy odpowiednim nazewnictwie twoich pól ustawienie resultTransformer wykonuje rzutowanie obiektów jako pożądanego POJO. Zobacz ten post na stackoverflow i przeczytaj ten dokument o hibernacji na temat używania zapytań natywnych
lakreqta
from foo where activeto nie native zapytania. Nie ma więc potrzeby stosowania transformatora wynikowego, ponieważ wystarczy domyślne mapowanie. Nie chodzi o rzutowanie pól POJO, ale o rzutowanie obiektu wynikowego. Transformator wynikowy nie pomógłby tutaj.
Tobias Liefke
0

Prawidłowym sposobem jest użycie transformatorów hibernacji:

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

Iterowanie przez obiekt [] jest zbędne i może mieć pewien spadek wydajności. Szczegółowe informacje na temat wykorzystania transofrmerów znajdziesz tutaj: Transformers for HQL i SQL

Jeśli szukasz jeszcze prostszego rozwiązania, możesz skorzystać z prostego transformatora mapy:

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
źródło
Pytanie nie dotyczyło transformacji wyniku. Chodziło o rzutowanie Querywyników - co jest nadal potrzebne w twoim przykładzie. Twój przykład nie ma nic wspólnego z oryginałem from foo where active.
Tobias Liefke
0

Tylko używanie Transformers To nie zadziałało. Otrzymałem wyjątek rzutowania typu.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) nie zadziałało, ponieważ otrzymywałem Array of Object w elemencie listy zwrotów, a nie ustalony typ MYEngityName elementu listy.

Udało mi się to, gdy wprowadzam następujące zmiany. Po dodaniu sqlQuery.addScalar(-)każdej wybranej kolumny i jej typu oraz dla określonej kolumny typu String nie musimy mapować jej typu. lubićaddScalar("langCode");

I połączyłem MYEngityName z NextEnity, którego nie możemy tylko select *w zapytaniu poda tablicę Object na liście zwrotów.

Poniżej przykładowy kod:

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

To może komuś pomóc. w ten sposób działa dla mnie.

Laxman G
źródło
-1

Tutaj znalazłem najlepsze rozwiązanie , kluczem do tego problemu jest metoda 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
źródło