Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java EE (J2EE) и Spring > JPA: загружать или не загружать связанные сущности


Автор: LuMee 7.8.2007, 19:40
Много где видел похожие вопросы, но так и не нашел внятного ответа. 
Итак, пусть имеются две сущности, скажем, тема форума и сообщение в теме:
Код

@Entity
public class Topic
{
    ...
    @OneToMany(...)
    private Collection<Post> posts;
    ...
}

@Entity
public class Post
{
    ...
}

Вопросы таковы:
1. можно ли вытащить с помощью EntityManager'а объект Topic, не извлекая для него список Post'ов? А то, если уж говорить о форуме, то для извлечения списка топиков для отображения его на главной странице совершенно необязательно тащить еще и сообщения (так никакой памяти не хватит)
2. если удался п. 1, то как надежно проверить, что коллекция posts не проинициализирована? Она будет просто null, или там что похитрее?
3. опять же, если все нормально прошло с п.1, то как потом при необходимости все-таки проинициализировать эту коллекцию (тут уже догадки есть, но пока нет возможности проверить)?
По п.1, знаю, есть у аннотации OneToMany параметр fetchType, но, как я понял, установка ему значения FetchType.LAZY не гарантирует, что дочерние сущности не будут извлечены, ибо сервер приложений может данный параметр просто проигнорировать. Есть ли надежный способ убедить его лишние сущности не тащить, или остается только положиться на его умение следить за ресурсами?

Автор: Stampede 7.8.2007, 19:55
Коль скоро речь идет о веб приложении (форум), то я бы вообще не полагался на автоматический подгруз связанных сущностей. Дело в том, что в вебных интерфейсах практически для любых видов коллекций нужно предусматривать постраничный просмотр. И это требование делает объявления вида @OneToMany практически бесполезными.

Автор: LuMee 8.8.2007, 07:30
Мда, неудачно я видимо пример привел... А если рассматривать более общую ситуацию, нежели сущности для отображения на странице? Пусть, скажем, это будут некие сущности, описывающие какие-то внутренности системы или еще чего. У меня пока складывается впечатление, что самым простым и надежным способом добиться желаемого результата будет вообще забыть про OneToMany и вытаскивать все данные самостоятельно, однако это уже не так красиво.

Автор: ecologist 8.8.2007, 08:55
По моему мнению EJB использует идеологию Hibernate, а там LAZY будет нормально работать только в рамках транзакции. Отсюда у меня вывод:
Использовать "ленивые" коллекции можно и нужно, но каждый раз это должно продумываться и тестироваться.

Автор: LuMee 8.8.2007, 12:18
Оно понятно, что можно и нужно (не зря же люди их делали smile), вопрос в том, как обеспечить желаемое их поведение.

По моему мнению EJB использует идеологию Hibernate, а там LAZY будет нормально работать только в рамках транзакции
Вот с этого места можно немного поподробнее? А то с Hibernate лично дел не имел.

Автор: Maverick 9.8.2007, 14:21
Как все-таки проинициализировать коллекцию... ? smile 

Автор: AntonSaburov 9.8.2007, 15:26
Код

        try {
            Session session = template.getSessionFactory().getCurrentSession();
            Agency result = (Agency) session.get(Agency.class, new Integer(agencyRowId));
            if(result!=null) {
                Hibernate.initialize(result.getTerminal());
                Hibernate.initialize(result.getMnemonics());
            }
            return result;
        } catch (HibernateException e) {
            throw new DataAccessException(e);
        }



Строчку с session - там получение сессии Hibernate. А дальше - первый шаг - получение объекта Agency. И после инициализация двух коллекций - терминалы и мнемоники. Ну это просто кусок выдраный из кода.

Вот что важно - Hibernate.initialize(...)

Автор: Maverick 10.8.2007, 16:13
А в Ejb3?

Добавлено через 2 минуты и 36 секунд
у меня вот такую хрень выдает
Код

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: bnkMain.Agent.declarations, no session or session was closed


если прописать fetchtype =EAGER, то возникает другая....

ну не хочу я пока использовать эту ленивую загрузку, чего она лезет... 

Автор: AntonSaburov 10.8.2007, 16:49
Видимо ты пытаешься инициализировать коллекцию уже за границей открытия сессии.

По сути ты можешь вытащить коллекцию ТОЛЬКО пока сессия еще открыта. Как только ты ее закрываешь, то все.

Образно - открыли дверку (сессию) - и ты можешь таскать данные, записывать и прочая. Как только дверка закрыта (сессия) - все. Тебе так и написали.

Автор: Maverick 13.8.2007, 08:45
Дело в том, что я не использую прямо сессии и ленивую загрузку... я вообще не хочу это делать прямо - не настолько опытен еще.... а все равно вылазит  всякое...

Добавлено через 1 минуту и 45 секунд
Код

Session session = template.getSessionFactory().getCurrentSession();


если можно - чуть подробнее об этой строке.... что такое template? Это экземпляр чего?

Добавлено через 2 минуты и 13 секунд
И в каком методе сидят эти строчки?

Автор: Maverick 13.8.2007, 10:12
Ставлю fetch = EAGER вот такая штука вылазит

Код

Caused by: org.hibernate.HibernateException: cannot simultaneously fetch multiple bags


Автор: Maverick 13.8.2007, 11:04
нельзя ли расписать примерчик... очень прошу - кто уже разобрался...

есть два entity со связь @OneToMany, даже три - вложенных... вот так примерно...

Код


//-*-*-*-*-*-*-*-*
@Entity
 // экономический агент
public class Agent implements Serializable {

    @OneToMany(mappedBy = "agent", fetch = FetchType.EAGER, cascade =CascadeType.ALL)
    private List<Declaration>  declarations = new ArrayList<Declaration>();

//-*-*-*-*-*-*-*-*
@Entity 
// таможенная декларация
public class Declaration implements Serializable {

    @ManyToOne
    private Agent agent;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    /** Номер декларации */
    private String issue;
    /** Признак завершения оформления*/
    private Boolean clear;
    
    @OneToMany(mappedBy = "declaration")
    private List<Payment> payments = new ArrayList<Payment>();

//-*-*-*-*-*-*-*-*
@Entity
// таможенные платеж дл декларации
public class Payment   implements Serializable{
 
    @ManyToOne
    private Declaration declaration;
       


как с ними обращаться, чтобы все коллекции отвечали нормально? где-какие сессии открывать? как их проинициалировать? я пытаюсь и так и этак... пол инета перерыл...

Добавлено через 49 секунд
хотя бы направьте - в каком направлении рыть...  заранее благодарен... 

Автор: Maverick 13.8.2007, 13:53
В последнее время -у меня стойкое мнение, что я один русскоязычный на планете занимаюсь EJB3... 
http://www.jroller.com/eyallupu/entry/hibernate_exception_simultaneously_fetch_multiple описана ... но ее решение в данном случае мне не подходит - там чистый Hibernate... да оно и не работает нормально  под EJB3...  

Автор: Maverick 13.8.2007, 14:30
Переключил проект на TopLink - все заработало сразу и нормально...  smile  как это называется? Hibernate глючит?

Автор: AntonSaburov 13.8.2007, 14:39
Цитата(Maverick @  13.8.2007,  08:45 Найти цитируемый пост)
если можно - чуть подробнее об этой строке.... что такое template? Это экземпляр чего?

Эту строчку как раз не надо рассматривать - это наш внутренний класс - но там вообщем надо получить сессию. 
Вот из той же доки по Hibernate

Код

package util;
import org.hibernate.*;
import org.hibernate.cfg.*;
public class HibernateUtil 
{
  private static final SessionFactory sessionFactory;
  static {
    try {
      // Create the SessionFactory from hibernate.cfg.xml
      sessionFactory = new Configuration().configure().buildSessionFactory();
    } catch (Throwable ex) {
      // Make sure you log the exception, as it might be swallowed
      System.err.println("Initial SessionFactory creation failed." + ex);
      throw new ExceptionInInitializerError(ex);
    }
  }
  public static SessionFactory getSessionFactory() {
    return sessionFactory;
  }
}

Код

Session session = HibernateUtil.getSessionFactory().getCurrentSession();



Автор: Maverick 13.8.2007, 14:51
и где это надо использовать? в сессионном фасаде? или в самом ентити? как же инициализировать коллекцию все-таки? где именно это надо сделать? 

AntonSaburov, раскажите хотя бы кратко как это сделать и вы сделаете этот топик одним из самых популярных в интернете... Вы пользуетесь EJB3 в работе?

Автор: AntonSaburov 13.8.2007, 14:58
Цитата(Maverick @  13.8.2007,  14:51 Найти цитируемый пост)
AntonSaburov, раскажите хотя бы кратко как это сделать и вы сделаете этот топик одним из самых популярных в интернете... Вы пользуетесь EJB3 в работе?

Ох, в реальном проекте у нас к сожалению используется еще EJB 2 и Hibernate (убедили начальство, что это проще). 

А вот для "Студенческого отдела кадров" я копался. Но вот описать прямо с нуля - сложно. В NetBeans это делается весьма удобно - наверно проще будет просто описать действия по пунктам. Постараюсь что-то простое выложить. А дальше уже самими копаться придется smile

Автор: Maverick 13.8.2007, 15:01
AntonSaburov, я копаюсь со страшной силой... сижу как раз в NetBeans - действительно удобно проектировать и компелировать, и деплоить.... но, не работает проект сделанный теоретически полностью правильно...

Добавлено через 2 минуты и 4 секунды
Как можно было сделать библиотеку Hibernate так, чтобы в одном классе нельзя было хранить две подгруженные постоянно коллекции? У меня в голове не укладывается... ведь это есть в каждом втором проекте... 

Автор: AlexeyVorotnikov 13.8.2007, 15:21
Цитата(Maverick @  13.8.2007,  16:01 Найти цитируемый пост)
чтобы в одном классе нельзя было хранить две подгруженные постоянно коллекции

Это в каком смысле?

Автор: Maverick 13.8.2007, 15:30
В том смысле, что если скажем у тебя есть экономический агент, у которого есть список деклараций и список субсчетов одновременно - то стандартно их проиницилизировать через Hibernate в EJB3 не получиться....

Добавлено через 29 секунд
да и вообще непонятно - как инициализировать их... 

Автор: hamsterKSU 13.8.2007, 23:36
может проблема и решена я так и непонял но насчет инициализации колекций меня в Hibernate спасло следующие
@Fetch(FetchMode.SUBSELECT)
@OneToMany(mappedBy = "...", fetch = FetchType.EAGER)

Автор: Maverick 14.8.2007, 10:13
К сожалению не помогло... проблема остается открытой... 

Автор: Maverick 14.8.2007, 11:19
хух... заработало... решение проблемы здесь - в этом http://www.jroller.com/eyallupu/entry/solving_simultaneously_fetch_multiple_bags...
единственным недостатком является то, что приходиться отказаться от чистого EJB3 и использовать расширение Hibernate @IndexColumn... однако, странновато.... EJB3 сыроват...  smile 

Автор: VSergeyV 24.8.2009, 15:48
Как можно повлиять на связанные сущности при выборке?

Пример топискатера
Код

@Entity
public class Topic
{
    long id;
    ...
    @OneToMany(...)
    private Collection<Post> posts;
    ...
}
@Entity
public class Post
{
   String status;
    ...
}

Допустим выбираем Topic по id, а в posts нужно выбрать не просто все посты для темы с заданным id (что делается по умолчанию),  но и еще со status="А".

Автор: victorq10 30.7.2010, 14:52
Если еще кто зайдет на эту тему, то мой совет такой.

Осоциации для бинов OneToMany и ManyToOne служат для того чтобы можно было пользоваться JPQL.

Пример:
Выбираем топики со статусом, к примеру, 'A'
Код

SELECT t FROM Topic t JOIN t.posts p WHERE p.status = 'A'

or
Код

SELECT t FROM Post p JOIN p.topic t WHERE p.status = 'A'

or
Код

SELECT t FROM Post p, p.topic t WHERE p.topic = t.id AND p.status = 'A'



Выбираем все посты где у топиков type = 1
Код

SELECT p FROM Topic t JOIN t.posts p WHERE t.type = 1

or
Код

SELECT p FROM Post p JOIN p.topic t WHERE t.type = 1

or
Код

SELECT p FROM Post p, p.topic t WHERE p.topic = t.id AND t.type = 1



Все запросы должны возвращать одно и тоже.
Последний запрос (без JOIN) может использоваться и без объявленных 
связей OneToMany и/или ManyToOne у соответствующих объектах.

Заключение:
OneToMany, ManyToOne, OneToOne, ManyToMany описывают поля для того, 
чтобы было удобно строить запросы на JPQL. JPA делает важную вещь - 
возвращает объекты!!!

Не кто не мешает по ассоциации OneToMany получить полную коллекцию 
связанных объектов и в цикле отобрать те что нужно, но такое решение при 
больших коллекциях, может дольше работать, но при достаточной мощности
сервера, это можно не заметить. Когда уже сильно будет тормозить, 
можно оптимизировать smile smile smile


У меня волнует другой вопрос, с которым я столкнулся.
как избежать циклическую загрузки по связям ManyToOne.

К примеру выбираем "Клиентов" с базы, у клиентов есть "Менеджер", он в свою очередь принадлежит 
к "ГрупеМенеджеров", так же  у клиектов имеет "СтатусКлиента". 
В результате будет один запрос с таблици "Клиентов" и много одиночных запросов к таблицам:
 "Менеджер", "ГрупеМенеджеров", "СтатусКлиента".

если 1000 клиентов, 120 СтатусовКлиента, 40 менеджеров, 5 ГрупМенеджеров, 
и все записи из таблиц будут использоваться,
то получим минимум 1 + 120 + 40 + 5 = 167 запросов. TopLink сделает где-то 2000 запросов (у меня чуть другие данные, но суть та же)

Нашел ответ что нужно проектировать структуру базы с учетом связей. Как бы это хорошо,
но может есть более лояльный подход.

Другой вариант оптимизации использовать выбрав только то что нужно
Код

SELECT new ClientInfo(c.id, c.name) FROM Client c


Третий вариант оптимизации использовать LEFT JOIN FETCH:
Код

SELECT c FROM client c
LEFT JOIN FETCH c.manager
LEFT JOIN FETCH c.manager.groupManager
LEFT JOIN FETCH c.clientStatus

этот вариант у меня не заработал в TopLink, в других не пробовал (пример для Hibernate).
у меня работает только один уровень с FETCH. (число одиночных запросов уменьшилось smile

Четвертый вариант использовать FetchGroup, не совсем понял как его использовать 
и он зависит от реализации JPA.

Что делать в такой ситуации? 
Как избежать циклических запросов по связям ManyToOne когда в базе присутствуют сложные зависимости?




Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)