Создание суррогатов представлений (view) базы данных на уровне приложения с помощью Hibernate

Если по тем или иным причинам нет возможности создать полноценное представление прямо в БД, то Hibernate позволяет создать суррогат такого представления с помощью аннотации @org.hibernate.annotations.Subselect прямо в приложении.

Создаваемый таким образом суррогат представления будет зависеть от конкретной БД, так как SELECT для этого представления должен быть написан на SQL диалекте используемой БД. Это, конечно, уменьшает переносимость такого кода.

Чтобы проиллюстрировать создание представления выполним следующие действия.

Создадим минимальное Spring Boot приложение с поддержкой JPA/Hibernate

Создадим два класса предметной области (каждый в своём файле):

Для простоты мы не будем использовать возможности Hibernate устанавливать связи один ко многим у классов предметной области.

Создадим специальную сущность, в которой будет содержаться SQL запрос для формирования представления внутри приложения, а также необходимый набор полей.

Во-первых, сущность помечается аннотацией @org.hibernate.annotations.Immutable, что логично для сущности соответствующей представлению (view).

Во второй аннотации, @org.hibernate.annotations.Subselect, мы указываем SQL запрос, на основании которого будет формироваться представление. Важно следить, чтобы имена получающихся колонок представления — в нашем случае id, person_name и group_number — соответствовали именам полей класса с учётом всех стандартных или кастомных (если они есть) преобразований. В противном случае отображение строк в объекты работать не будет.

Так у нас колонка id в запросе соответствует полю id в классе, person_name в запросе соответствует personName в классе с учётом стандартных преобразований имён полей в имена колонок, ну и group_number соответствует groupNumber.

В третью аннотацию, @org.hibernate.annotations.Synchronize, передаётся массив имён таблиц (именно таблиц, как они есть в БД), которые участвуют в формировании представления. Если этого не сделать или указать не все таблицы, то возникнет вероятность, что в представлении окажутся неактуальные данные.

Создадим для всех трёх сущностей репозитории (каждый в своём файле):

Создадим тест, в котором проверим, что с помощью репозитория PersonWithGroupNameRepository можно извлечь данные из таблиц «PERSON» и «USER_GROUP», как если бы в БД существовало соответствующее представление:

Сперва мы создаём одну строку в таблице USER_GROUP и две строки в таблице PERSON с groupId созданной группы.

Затем происходит самое интересное: мы вызываем стандартный в Spring Data JPA метод findAll() на бине репозитория PersonWithGroupNameRepository и он отрабатывает так, будто в БД есть представление (view), связывающее две таблицы и мы делаем запрос к нему. Хотя в БД никакого представления нет, оно существует только на уровне Hibernate приложения.

Затем мы сортируем выборку, чтобы результаты ассертов были предсказуемыми в плане порядка следования элементов и убеждаемся, что в суррогатном представлении есть две строки, как мы и ожидаем.

Использование суррогатных представлений на уровне приложения может быть очень удобно, когда нет возможности создать реальное представление внутри БД.