Рубрики
Без рубрики

Как ПРИСОЕДИНИТЬСЯ к несвязанным сущностям с помощью JPA и гибернации

Узнайте, как присоединяться к несвязанным сущностям при использовании запросов сущностей с JPA и Hibernate. Hibernate 5.1 добавляет поддержку этой функции в JPQL и HQL-запросах.

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

Однако, если мы сделаем то же самое для второго Сообщения объекта:

List postViewCount = 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-запросов .