Каков «правильный» способ привести Hibernate Query.list () к List <Type>?


84

Я новичок в Hibernate, и я пишу простой метод для возврата списка объектов, соответствующих определенному фильтру. 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, так как оно выглядело многообещающим, но затем я понял, что на самом деле ничего не получаю 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
Если ребята из hibernate добавят аргумент с типом Class<?>in list(), проблема может быть решена. Обидно использовать такой уродливый API.
Бин Ван

@BinWang, то небезопасное приведение может произойти где-то еще, это не решает проблему - оно просто перемещает его. Излишне говорить HQL API был фактически устаревшим для лет в настоящее время. JPA имеет типобезопасный API запросов, называемый Criteria Query API .
Boris the Spider

2
@PeteyPabPro Хотя я согласен с тем, что следует избегать rawtypes, я не согласен с тем, что результаты запроса следует рассматривать как 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

Теперь сообщение об ошибке исчезло, и в итоговом списке используются фактические объекты вместо объекта Object ... отличный ответ!
Йоханнес

Похоже, это выпущено в спящем режиме 5.0. Я не вижу этого в 4.3.11, но следующая статья относится к 5.3. wiki.openbravo.com/wiki/… Я также вижу сообщение stackoverflow за январь 2014 года со ссылкой на него: stackoverflow.com/a/21354639/854342 .
Curtis Yallop

Это часть 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синтаксис. Это определенно неправильный подход.
Паук Борис

У меня тоже был такой же опыт. Поскольку мой запрос возвращает разные поля из нескольких таблиц, которые не связаны друг с другом. Так что для меня работал только этот способ. Спасибо :)
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);
}

Да, по сути, это то же самое «уродство», которое предложил Борис, только с гипсом внутри петли.
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 и прочтите справочный документ по
спящему режиму

from foo where activeэто не родной запрос. Таким образом, нет необходимости в преобразователе результатов, так как сопоставления по умолчанию будет достаточно. Вопрос не в приведении полей POJO, а в приведении объекта результата. Преобразователь результатов здесь не поможет.
Тобиас Лифке 01

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

Итерация через Object [] является избыточной и может привести к некоторому снижению производительности. Подробную информацию об использовании трансформеров вы найдете здесь: Трансформаторы для 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.
Тобиас Лифке 01

0

Просто используя трансформаторы. У меня не получилось, я получал исключение приведения типа.

sqlQuery.setResultTransformer(Transformers.aliasToBean(MYEngityName.class)) не сработало, потому что я получал массив объектов в элементе списка возврата, а не фиксированный тип MYEngityName элемента списка.

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

И я присоединился к MYEngityName с NextEnity, мы не можем просто select * в он предоставит массив объектов в списке возврата.

Ниже образец кода:

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.