Автор оригинала: 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-запросов .