Автор оригинала: Vlad Mihalcea.
Вступление
Ответы на вопросы на форуме Hibernate-это неиссякаемый источник вдохновения. Следующий вопрос касается объединения несвязанных сущностей с помощью запросов сущностей, и в этом посте будет объяснено, как это можно сделать при использовании JPA и гибернации.
Модель предметной области
Предполагая, что у нас есть следующие сущности:
Сущность Post имеет атрибут slug , который определяет относительный адрес этого конкретного HTTP-ресурса на нашем сервере приложений. Каждый раз, когда пользователь посещает данную веб-страницу, регистрируется событие Просмотр страницы , которое, помимо других свойств, также имеет атрибут slug , чтобы мы знали, какой веб-ресурс был просмотрен нашим пользователем.
Просто потому, что у них есть общее свойство, это не означает, что нам нужно определить связь между этими сущностями. Однако мы все равно хотели бы присоединиться к ним на основе каждого запроса.
Данные испытаний
Теперь предположим, что в нашей базе данных есть следующие записи:
Post post = new Post();
post.setSlug("/books/high-performance-java-persistence");
post.setTitle("High-Performance Java Persistence");
entityManager.persist(post);
Post post = new Post();
post.setSlug("/presentations");
post.setTitle("Presentations");
entityManager.persist(post);
PageView pageView = new PageView();
pageView.setSlug("/books/high-performance-java-persistence");
pageView.setIpAddress("127.0.0.1");
entityManager.persist(pageView);
PageView pageView = new PageView();
pageView.setSlug("/books/high-performance-java-persistence");
pageView.setIpAddress("192.168.0.1");
entityManager.persist(pageView);
Мы хотим присоединиться к Сообщению и просмотру страниц сущностям, чтобы мы знали, сколько просмотров сгенерировал данный Пост .
Хотя JPA 2.0 внедрил поддержку предложения JOIN ON в запросах JPQL, этот синтаксис требует, чтобы ассоциация присутствовала на уровне сущности.
Однако в нашем случае наши сущности не связаны, поэтому такой связи нет. Таким образом, стандарт JPA не предлагает решения для несвязанных объектов, поэтому мы должны решить эту проблему с помощью функций, специфичных для гибернации.
Спящий режим 5.1 и новее
Начиная с Hibernate 5.1, вы можете легко присоединяться к несвязанным сущностям, используя тот же синтаксис, который вы использовали бы при написании собственного SQL-запроса:
Tuple postViewCount = entityManager.createQuery(
"select p as post, count(pv) as page_views " +
"from Post p " +
"left join PageView pv on p.slug = pv.slug " +
"where p.title = :title " +
"group by p", Tuple.class)
.setParameter("title", "High-Performance Java Persistence")
.getSingleResult();
Post post = (Post) postViewCount.get("post");
assertEquals(
"/books/high-performance-java-persistence",
post.getSlug()
);
int pageViews = (
(Number) postViewCount.get("page_views")
).intValue();
assertEquals(2, pageViews);
Как и ожидалось, было 2 попадания для Сообщения с slug значением //книги/высокая производительность-java-сохраняемость .
Если мы запустим тот же запрос для второго Сообщения :
Tuple postViewCount = entityManager.createQuery(
"select p as post, count(pv) as page_views " +
"from Post p " +
"left join PageView pv on p.slug = pv.slug " +
"where p.title = :title " +
"group by p", Tuple.class)
.setParameter("title", "Presentations")
.getSingleResult();
Post post = (Post) postViewCount.get("post");
assertEquals("/presentations", post.getSlug());
int pageViews = (
(Number) postViewCount.get("page_views")
).intValue();
assertEquals(0, pageViews);
Мы получаем 0 просмотров, так как в настоящее время нет Просмотра страниц , связанного с этой публикацией сущностью.
Именно поэтому мы использовали ЛЕВОЕ СОЕДИНЕНИЕ вместо простого СОЕДИНЕНИЯ, которое эквивалентно ВНУТРЕННЕМУ СОЕДИНЕНИЮ . Если бы мы использовали ВНУТРЕННЕЕ СОЕДИНЕНИЕ для этого запроса, то ни одна строка не была бы возвращена. Однако мы хотим, чтобы Сообщение всегда возвращалось и чтобы просмотры страниц возвращали 0, если для этого конкретного веб-ресурса не было просмотра страницы.
Объединение несвязанных сущностей возможно только при использовании JPQL или HQL. Эта функция недоступна при использовании API критериев JPA.
До перехода в спящий режим 5.1
Если вы используете более старую версию Hibernate, единственный способ объединить две несвязанные сущности-это использовать соединение в стиле тета|/.
Для первой Должности организации:
Tuple postViewCount = entityManager.createQuery(
"select p as post, count(pv) as page_views " +
"from Post p, PageView pv " +
"where p.title = :title and " +
" ( pv is null or p.slug = pv.slug ) " +
"group by p", Tuple.class)
.setParameter("title", "High-Performance Java Persistence")
.getSingleResult();
Post post = (Post) postViewCount.get("post");
assertEquals(
"/books/high-performance-java-persistence",
post.getSlug()
);
int pageViews = (
(Number) postViewCount.get("page_views")
).intValue();
assertEquals(2, pageViews);
Этот запрос дает тот же результат, что и выполненный ранее, потому что с этой конкретной сущностью Post связаны строки просмотра страниц .
Однако, если мы сделаем то же самое для второго Сообщения объекта:
ListpostViewCount = entityManager.createQuery( "select p as post, count(pv) as page_views " + "from Post p, PageView pv " + "where p.title = :title and " + " ( p.slug = pv.slug ) " + "group by p", Tuple.class) .setParameter("title", "Presentations") .getResultList(); assertEquals(0, postViewCount.size());
Мы не получаем никакого результата, потому что соединение в стиле Тета эквивалентно равнодействующему или ВНУТРЕННЕМУ СОЕДИНЕНИЮ, а не ЛЕВОМУ ВНЕШНЕМУ СОЕДИНЕНИЮ.
Поэтому до Hibernate 5.1 вы могли присоединяться только к несвязанным сущностям с помощью оператора реляционной алгебры equi join.
Вывод
Если вам нужно объединить две несвязанные сущности, вам следует обновить по крайней мере до версии Hibernate 5.1. В противном случае, если вы не хотите использовать равносоединение, вы больше не сможете использовать JPQL или HQL. В более старых версиях Hibernate, если вам нужно ВНЕШНЕЕ СОЕДИНЕНИЕ для несвязанных сущностей, вы можете сделать это только с помощью собственных SQL-запросов .