JPA: пожалуйста, помогите понять "join fetch"
У меня есть следующая структура сущностей: Бизнес → Кампания → Продвижение, где у ОДНОГО бизнеса может быть много кампаний, а одна кампания может иметь много рекламных акций. Оба отношения "один ко многим" объявляются ЛАЗИ. Одно место в моем коде, мне нужно с нетерпением получить обе коллекции из бизнеса, поэтому я:
Query query = entityManager.createQuery("select b from Business b " +
"left join fetch b.campaigns c " +
"left join fetch c.promotions where b.id=:id");
query.setParameter("id", b.getId());
business = (Business) query.getResultList().get(0);
Однако запрос возвращает список результатов, содержащий в нем 4 бизнес-объекта, все 4 объекта ссылаются на один и тот же экземпляр Business. В моей базе данных в этом бизнесе есть 3 кампании, и во всех трех кампаниях есть 3 рекламных акции.
У меня есть два вопроса:
-
Во-первых, я использую List to содержит много сторон отношений, но когда программа запускается, я получаю исключение "org.hibernate.HibernateException: не может одновременно получить несколько мешков". Затем я искал это исключение, и похоже, что я должен использовать Set вместо List. Поэтому я сменил коллекцию на Set, и она сработала. Может ли кто-нибудь сказать мне, почему List не будет работать в этой ситуации?
-
Я ожидаю, что запрос вернет один результат, потому что он запрашивает id, являющийся первичным ключом, и поэтому должен возвращать только один результат. Но оказывается, что он возвращает 4 экземпляра в списке. Это проблема? Или это ожидаемое поведение?
Любая помощь будет принята с благодарностью.
Ответы
Ответ 1
Сгенерированный sql будет выглядеть примерно так:
select * from Business b
left outer join campaigns c on c.business_id = b.id
left join promotions p on p.campaign_id = c.id
where b.id=:id
Внутренне Hibernate будет иметь только один экземпляр Business, однако дубликаты будут сохранены в результирующем наборе. Это ожидаемое поведение. Поведение, которое вам требуется, может быть достигнуто либо с помощью предложения DISTINCT, либо с помощью LinkedHashSet для фильтрации результатов:
Collection result = new LinkedHashSet(query.getResultList());
который будет возвращать только уникальные результаты, сохраняя порядок вставки.
"org.hibernate.HibernateException: не может одновременно извлекать несколько пакетов" происходит всякий раз, когда вы пытаетесь с нетерпением получать более одной коллекции упорядоченным способом (и, возможно, дублируемыми элементами). Это имеет смысл, если вы рассматриваете сгенерированный SQL. Hibernate не имеет способа узнать, был ли дублированный объект вызван соединением или фактическими дублирующимися данными в дочерней таблице. Посмотрите это для хорошего объяснения.
Ответ 2
- Список может иметь избыточные элементы из-за декартова продукта, и люди не знали об этом, поэтому парни из спячки начали метать эту ошибку, чтобы заставить людей использовать Set вместо List, чтобы избежать этой проблемы. Источник 1
Ответ 3
-
Я предполагаю, что если вы используете Список, спящий режим предполагает, что порядок важен, и он выведет порядок элементов из возвращаемых строк, но если вы присоединитесь к более низкой таблице, hibernate будет получать каждую кампанию несколько раз который смущает спящий режим. Опять же, я просто предполагаю, что это
-
Это ожидаемое поведение. Используйте RootEntityResultTransformer для получения одного корневого объекта: http://www.hibernate.org/hib_docs/v3/api/org/hibernate/transform/RootEntityResultTransformer.html
Ответ 4
Другим обходным решением вместо использования Set является использование двух запросов для получения данных: - 1-й для загрузки кампании и связанных с ней рекламных акций компании. Затем загрузите Бизнес и его кампании с помощью fetch
Query query1 = entityManager.createQuery("select c from Business b join b.campaigns c left join fetch c.promotions where b.id=:id");
query1.setParameter("id", b.getId());
query1.getResultList();
Query query2 = entityManager.createQuery("select b from Business b left join fetch b.campaigns where b.id=:id");
query2.setParameter("id", b.getId());
business = (Business) query2.getResultList().get(0);