Hibernate Schema Multitenancy

singletenant-multitenant (мультиарендность) — это подход к проектированию приложения, когда один экземпляр приложений обслуживает несколько клиентов с непересекающимися наборами данных. Например сайт по учёту персональных финансов имеет одну копию кода, одно хранилище данных и много клиентов, при этом каждому клиенту доступны только его собственные данные. Наиболее популярен этот подход, по очевидным причинам, в облачных SaaS решениях — каждый клиент видит общий, единый, экземпляр приложения как свою собственную копию.

Чаще всего проблемы с реализацией multitenancy кроются не на уровне логики приложения, а на уровне хранения данных: мы должны уметь отделять данные одного клиента от другого и не давать им шанса смешаться. Существует три основных подхода к решению этой проблемы:

  • Разделение на уровне базы данных — под каждого клиента создаётся (или запускается) отдельный экземпляр БД, уникальный для этого клиента и в нём распологаются данные только этого клиента.
  • Разделение на уровне схемы — в одной и той же базе данных создаются разные схемы (schema/namespace) в которых создаются копии структуры таблиц, необходимых для работы клиента и данные каждого клиента живут в отдельно схеме.
  • Разделение на уровне таблиц — и база данных одна, и схема одна, и даже таблицы те же самые. Но в каждой таблице заводится столбец (дискриминатор), указывающий, какому клиенту принадлежит какая таблица.

поддерживает первые два подхода к multitenancy, а третий подход можно достаточно несложно сымитировать. В прошлой статье я описывал database multitenancy, а а в этой опишу schema multitenancy.

Подготовка базы данных

В режиме schema multitenancy hibernate сам не создаёт схемы для разных клиентов и не создаёт структуру хранения, всё это авторы приложения должны сделать вручную. Для этой статьи я использую , в котором создам пустую базу данных и две схемы для двух разных клиентов. Клиентские схемы я наполню таблицами вручную.

SQL скрипт, создающий структуру данных

[свернуть]

Схема данных, код сущностей и код примера идентичны таковым из примера database multitenancy.

Настройка Hibernate

Включение multitenancy в hibernate производится в два действия — выбираем стратегию разделения данных и реализуем дружественный этой стратегии метод переключения соединений.

Стратегию проще всего назначить в hibernate.cfg.xml, хотя можно и программно, в ходе создания SessionFactory.

Свойство «hibernate.multiTenancy» выбирает стратегию разделения данных. В рамках моего примера я выбираю «SCHEMA«, так как планирую данные клиентов хранить в разных схемах одной базы данных. Обратите внимание, что в названии свойства «hibernate.multiTenancy» присутствует заглавная T,  если написать всё в нижнем регистре, multitenancy не включится.

Второе свойство, «hibernate.multi_tenant_connection_provider» выбирает реализацию провайдера соединений с базой данных, который будет возвращать соединение сообразно переданному в него tenant id — идентификатору клиента.

В этот раз я напрямую реализовал интерфейс MultiTenandProvider. Мой собственный код по переключению схем в базе находится в методе setSchemaTo, который переключает схему в базе и, если что-то идёт не так, возвращает соединение в пул и кидает исключение.

Из методов интерфейса следует обратить внимание на пары getAnyConnection()/releaseAnyConnection()  и getConnection()/releaseConnection(). Первая пара использутся для получения соединения «по умолчанию», когда tenant id неизвестен или не требуется, например при старте Hibernate и сканировании таблиц. Вторая пара используется для работы с tenant специфичными соединениями.

Так же в моём собственном провайдере используется пул HikariCP для поддержания нескольких соединений с базой.

Использование Multitenancy

Использовать multitenancy просто: при открытии сессии достаточно указать tenant id и всё остальное Hibernate сделает сам. Для примера я создам два разных объекта с двумя разными tenant id и потом выберу все объекты с разными tenant id

Код создания объектов

[свернуть]
Запуск примера показывает, что данные для одного и того же запроса возвращаются разные, в зависимости от tenant id:
Код примера доступен на github. Для запуска примера требуется установить PostgreSQL сервер и разрешить к нему доступ. Если сервер будет установлен не на локальной машине, требуется изменить его адрес коде создания пула соединений.