Який “правильний” спосіб передати Hibernate Query.list () у List <Type>?


84

Я новачок у режимі глибокого сну і пишу простий метод повернення списку об'єктів, що відповідають певному фільтру. List<Foo>здавалося природним типом повернення.

Що б я не робив, здається, я не можу зробити компілятора щасливим, якщо не найняти некрасивого @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();
    }
}

Я хотів би позбутися цьогоSuppressWarnings . Але якщо я це роблю, я отримую попередження

Warning: Unchecked cast from List to List<Foo>

(Я можу проігнорувати це, але спочатку хотів би не отримувати його), і якщо я видалю загальний для відповідності .list()типу повернення, я отримую попередження

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

Я помітив , що org.hibernate.mapping робить оголосити List; але це зовсім інший тип - Queryповертає a java.util.List, як необроблений тип. Я вважаю дивним, що нещодавній Hibernate (4.0.x) не реалізував параметризовані типи, тому я підозрюю, що це я замість цього роблю щось не так.

Це дуже схоже на результат Cast Hibernate до списку об'єктів , але тут у мене немає "жорстких" помилок (система знає тип Foo, і я використовую не SQLQuery, а прямий запит). Тож ніякої радості.

Я також розглянув виняток Hibernate Class Cast, оскільки він виглядав багатообіцяючим, але потім я зрозумів, що насправді не отримую жодної Exception... моя проблема полягає лише у попередженні - стилі кодування, якщо хочете.

Документація на jboss.org, посібники Hibernate та кілька навчальних посібників, схоже, не висвітлюють теми настільки детально (або я не шукав у потрібних місцях?). Коли вони все-таки докладаються до деталей, вони використовують кастинг на льоту - і це в підручниках, яких не було на офіційному сайті jboss.org, тому я трохи насторожений.

Код, після компіляції, працює без явних проблем ... про які я знаю ... поки; а результати - очікувані.

Отже: чи правильно я це роблю? Мені не вистачає чогось очевидного? Чи існує «офіційний» чи «рекомендований» спосіб зробити це ?

Відповіді:


101

Коротка відповідь @SuppressWarnings- правильний шлях.

Довгий відповідь, Hibernate повертає сирої Listвід Query.listметоду см тут . Це не помилка Hibernate або щось, що можна вирішити, тип, що повертається запитом, невідомий під час компіляції.

Тому коли ви пишете

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

Ви робите небезпечний привід з Listдо List<MyObject>- цього не уникнути.

Там немає ніякого способу , ви можете спокійно виконувати кидок , як List може містити що - небудь.

Єдиний спосіб усунути помилку - ще більш потворний

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

4
Я збирався лише проголосувати вашу відповідь, сподіваючись, що прийде краща. Замість цього я дізнався , що ця проблема називається «потворне кидок» з боку як Брюс Еккель (мислення в Java) і Роберт Седжвік - в Седжвік. Я також знайшов stackoverflow.com/questions/509076/… . Зітхайте.
LSerni

6
Мені подобається ваш стиль використанняfinal
Павло,

9
Якщо хлопці, які впадають у сплячий режим, додають аргумент із типом Class<?>in list(), проблема може бути вирішена. Соромно використовувати такий потворний API.
Bin Wang

@BinWang, тоді небезпечний акторський склад відбудеться десь ще, це не вирішує проблему - воно просто рухається. Само собою зрозуміло, що HQL API вже фактично застаріває вже багато років . JPA має API безпечного запиту типу, який називається API критеріїв запитів .
Борис Павук

2
@PeteyPabPro Незважаючи на те, що я згоден з тим, що сировинних типів слід уникати, я не згоден з тим, що результати запиту слід розглядати як List<Object>. Результати слід відтворювати за очікуваним типом та додавати модульні тести, щоб гарантувати, що запит повертає правильні результати. Неприпустимо, щоб помилки із запитами з'являлися " пізніше в коді ". Ваш приклад - аргумент проти практики кодування, яка повинна стати анафемою у 21 столітті. Я б припустив, ніколи не прийнятно мати List<Object>.
Борис Павук

26

Дозвіл полягає у використанні TypedQuery замість цього. При створенні запиту з EntityManager замість цього називайте його так:

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

Це також працює однаково для іменованих запитів, власних іменованих запитів тощо. Відповідні методи мають ті самі імена, що й ті, що повертають ванільний запит. Просто використовуйте це замість запиту, коли ви знаєте тип повернення.


1
Як зауваження, це не працює для суто власних запитів, які ви створюєте вбудовано. Повернутий запит є лише запитом, а не типовим запитом :(
Taugenichts

Тепер повідомлення про помилку зникло, і отриманий список використовує фактичні об'єкти замість об'єкта "Об'єкт" .. чудова відповідь!
Йоганнес

Здається, це випущено в режимі глибокого сну 5.0. Я не бачу цього в 4.3.11, але наступна стаття посилається на 5.3. wiki.openbravo.com/wiki/… Я також бачу повідомлення про стакковерфлоу у січні 2014 року, яке посилається на нього: stackoverflow.com/a/21354639/854342 .
Кертіс Яллоп,

Це частина hibernate-jpa-2.0-api. Ви можете використовувати це в режимі глибокого сну 4.3, оскільки я зараз використовую його в режимі глибокого сну 4.3.
Taugenichts

6

Ви можете уникнути попередження компілятора, використовуючи такі способи вирішення:

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

Але з цим кодом є деякі проблеми:

  • створив зайвий ArrayList
  • непотрібний цикл над усіма елементами, повернутими із запиту
  • довший код.

І різниця полягає лише в косметичних засобах, тому використання таких обхідних шляхів - на мій погляд - безглуздо.

Ви повинні жити з цими попередженнями або придушувати їх.


1
Я погоджуюсь з безглуздістю. Я придушу, навіть якщо це піде проти мого зерна. Все одно дякую і +1.
LSerni

2
> Ви повинні жити з цими попередженнями або придушувати їх. Завжди краще придушувати помилкові попередження, або ви можете пропустити правильне попередження в спамі ненадутих неправильних попереджень
SpongeBobFan

6

Щоб відповісти на ваше запитання, немає "належного способу" зробити це. Тепер, якщо вас турбує лише попередження, найкращий спосіб уникнути його поширення - це обернути Query.list()метод у DAO:

public class MyDAO {

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

Таким чином ви можете скористатися @SuppressWarnings("unchecked")єдиним разу.


Ласкаво просимо до Stack Overflow ! У будь-якому випадку, не забудьте взяти екскурсію
Sнаđошƒаӽ

3

Єдиний спосіб, який мені працював, був з ітератором.

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

З іншими методами, які я знайшов, у мене були проблеми з гіпсом


Які "проблеми з гіпсом"? Я завжди просто складав список безпосередньо, як вищезгадане є більш стислим або безпечним?
Джованні Ботта,

Я не можу безпосередньо брати участь. Виникли проблеми, оскільки він не міг зробити акторський склад з "Об'єкта до місця призначення"
Попа Андрій

Ви знаєте, що Hibernate може побудувати Destinstionдля вас, чи не так? Використання select newсинтаксису. Це, звичайно, не правильний підхід.
Борис Павук

Я теж мав такий самий досвід. Оскільки мій запит повертає різні поля з декількох таблиць, які не пов’язані між собою. Тож єдиний спосіб, який працював у мене, - це цей. Дякую :)
Чінтан Патель

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

Так, це в основному та сама "потворність", яку запропонував Борис, з акторським складом у циклі.
LSerni

2

Ви використовуєте ResultTransformer так:

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
Я не можу перевірити це зараз, але ... що це змінює? qвсе ще є Queryі, отже q.list(), все ще сирим java.util.Listтипом. Акторський склад все ще не перевіряється; якщо внутрішньо змінений тип об’єкта нічого не
допоможе

Так, закидання все ще не встановлено, але при правильному іменуванні полів, встановлення resultTransformer виконує роботу з приведенням Об’єктів як бажаного POJO. Перегляньте цей допис про stackoverflow і прочитайте це про
stackoverflow

from foo where activeце НЕ рідної запит. Отже, немає необхідності в трансформаторі результатів, оскільки відображення за замовчуванням буде достатньо. Питання не в приводі полів POJO, а в приведенні об’єкта результату. Результатний трансформатор тут не допоможе.
Тобіас Лієфке

0

Правильний спосіб - використовувати 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);

Ітерація об'єкта [] надлишкова, і це може спричинити певну продуктивність. Детальну інформацію про використання трансформаторів ви знайдете тут: Трансформатори для HQL та SQL

Якщо ви шукаєте ще більш просте рішення, ви можете використати нестандартний трансформатор карт:

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

Питання не в трансформації результатів. Йшлося про визначення Queryрезультатів - що все ще потрібно у вашому прикладі. І ваш приклад не має нічого спільного з оригіналом from foo where active.
Тобіас Лієфке

0

Просто використання Transformers Це не спрацювало для мене, я отримував виняток типу cast.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) не працював, оскільки я отримував масив об’єктів у елементі списку повернення, а не фіксований тип елемента списку MYEngityName.

Це спрацювало для мене, коли я роблю наступні зміни. Коли я додаю sqlQuery.addScalar(-)кожен вибраний стовпець та його тип, а для певного стовпця типу String нам не потрібно відображати його тип. люблюaddScalar("langCode");

І я приєднався до MYEngityName з NextEnity, ми не можемо просто select *в Запиті це дасть масив Object у списку повернення.

Нижче зразка коду:

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

Комусь це може допомогти. таким чином працюйте для мене.


-1

Я знайшов краще рішення тут , ключ цього питання є 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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.