Hibernate Database multitenancy

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

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

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

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

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

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

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

[свернуть]

В скрипте выше и в коде примера используется схема данных из примера управления сущностями в Hibernate.

Настройка Hibernate

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

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

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

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

Провайдер должен реализовать два метода: selectConnectionProvider() и getAnyConnectionProvider(). Первый метод достаточно очевиден — он получает tenant id, то есть идентификатор клиента, и строит по нему соединение. В моём случае наименование базы и идентификатор клиента совпадают.

Второй метод используется как метод получения соединения «по умолчанию», когда tenant id неизвестен или не требуется, например при старте Hibernate и сканировании таблиц. При этом в базе данных, которую вернёт getAnyConnectionProvider(), будет сгенерирована структура данных, если Hibernate соответствующим образом настроен. Именно поэтому при создании трёх баз данных, структуру данных я создал только в двух.

Так же в провайдере выше показывается, что можно создавать любые конфигурации соединений, использовать пулы соединений и вообще всё, что придёт в голову.

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

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

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

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