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

Руководство по многоквартирному проживанию в спящем режиме 5

Изучите два подхода к реализации мульти-аренды с использованием Hibernate 5.

Автор оригинала: baeldung.

1. введение

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

В этом уроке мы познакомим вас с различными подходами к настройке мульти-аренды в Hibernate 5.

2. Зависимости Maven

Нам нужно будет включить | hibernate-core зависимость в pom.xml файл:


   org.hibernate
   hibernate-core
   5.2.12.Final

Для тестирования мы будем использовать базу данных H2 в памяти, поэтому давайте также добавим эту зависимость в pom.xml файл:


   com.h2database
   h2
   1.4.196

3. Понимание многоквартирного жилья в спящем режиме

Как уже упоминалось в официальном руководстве пользователя Hibernate , в Hibernate существует три подхода к многоквартирному жилью:

  • Отдельная схема – одна схема для каждого клиента в одном и том же физическом экземпляре базы данных
  • Отдельная база данных – один отдельный экземпляр физической базы данных на каждого арендатора
  • Секционированные (Дискриминатор) данные – данные для каждого клиента секционируются значением дискриминатора

Подход к данным с разделением |/(Дискриминатор) еще не поддерживается Hibernate. Продолжение этого вопроса JIRA для дальнейшего прогресса.

Как обычно, Hibernate абстрагирует сложность реализации каждого подхода.

Все, что нам нужно, это обеспечить реализацию этих двух интерфейсов :

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

3.1. MultiTenantConnectionProvider

В принципе, этот интерфейс обеспечивает подключение к базе данных для конкретного идентификатора арендатора.

Давайте рассмотрим два его основных метода:

interface MultiTenantConnectionProvider extends Service, Wrapped {
    Connection getAnyConnection() throws SQLException;

    Connection getConnection(String tenantIdentifier) throws SQLException;
     // ...
}

Если Hibernate не может разрешить идентификатор арендатора для использования, он будет использовать метод get Any Connection для получения соединения. В противном случае он будет использовать метод getConnection .

Hibernate предоставляет две реализации этого интерфейса в зависимости от того, как мы определяем подключения к базе данных:

  • Используя DataSource интерфейс из Java – мы бы использовали реализацию DataSourceBasedMultiTenantConnectionProviderImpl
  • Используя интерфейс ConnectionProvider из Hibernate – мы бы использовали реализацию AbstractMultiTenantConnectionProvider

3.2. CurrentTenantIdentifierResolver

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

Другой способ – использовать идентификатор арендатора из параметра path.

Давайте посмотрим на этот интерфейс:

public interface CurrentTenantIdentifierResolver {

    String resolveCurrentTenantIdentifier();

    boolean validateExistingCurrentSessions();
}

Hibernate вызывает метод resolveCurrentTenantIdentifier для получения идентификатора арендатора. Если мы хотим, чтобы Hibernate проверял, что все существующие сеансы принадлежат одному и тому же идентификатору арендатора, метод validateExistingCurrentSessions должен возвращать true.

4. Схемный Подход

В этой стратегии мы будем использовать разные схемы или пользователей в одном и том же физическом экземпляре базы данных. Этот подход следует использовать, когда нам нужна наилучшая производительность для нашего приложения и мы можем пожертвовать специальными функциями базы данных, такими как резервное копирование для каждого клиента.

Кроме того, мы будем издеваться над интерфейсом CurrentTenantIdentifierResolver , чтобы предоставить один идентификатор клиента в качестве нашего выбора во время теста:

public abstract class MultitenancyIntegrationTest {

    @Mock
    private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;

    private SessionFactory sessionFactory;

    @Before
    public void setup() throws IOException {
        MockitoAnnotations.initMocks(this);

        when(currentTenantIdentifierResolver.validateExistingCurrentSessions())
          .thenReturn(false);

        Properties properties = getHibernateProperties();
        properties.put(
          AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, 
          currentTenantIdentifierResolver);

        sessionFactory = buildSessionFactory(properties);

        initTenant(TenantIdNames.MYDB1);
        initTenant(TenantIdNames.MYDB2);
    }

    protected void initTenant(String tenantId) {
        when(currentTenantIdentifierResolver
         .resolveCurrentTenantIdentifier())
           .thenReturn(tenantId);
        createCarTable();
    }
}

Наша реализация интерфейса MultiTenantConnectionProvider установит схему для использования каждый раз, когда запрашивается соединение :

class SchemaMultiTenantConnectionProvider
  extends AbstractMultiTenantConnectionProvider {

    private ConnectionProvider connectionProvider;

    public SchemaMultiTenantConnectionProvider() throws IOException {
        this.connectionProvider = initConnectionProvider();
    }

    @Override
    protected ConnectionProvider getAnyConnectionProvider() {
        return connectionProvider;
    }

    @Override
    protected ConnectionProvider selectConnectionProvider(
      String tenantIdentifier) {
 
        return connectionProvider;
    }

    @Override
    public Connection getConnection(String tenantIdentifier)
      throws SQLException {
 
        Connection connection = super.getConnection(tenantIdentifier);
        connection.createStatement()
          .execute(String.format("SET SCHEMA %s;", tenantIdentifier));
        return connection;
    }

    private ConnectionProvider initConnectionProvider() throws IOException {
        Properties properties = new Properties();
        properties.load(getClass()
          .getResourceAsStream("/hibernate.properties"));

        DriverManagerConnectionProviderImpl connectionProvider 
          = new DriverManagerConnectionProviderImpl();
        connectionProvider.configure(properties);
        return connectionProvider;
    }
}

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

Давайте настроим hibernate.properties для использования модели множественной аренды схемы и нашей реализации MultiTenantConnectionProvider интерфейса :

hibernate.connection.url=jdbc:h2:mem:mydb1;DB_CLOSE_DELAY=-1;\
  INIT=CREATE SCHEMA IF NOT EXISTS MYDB1\\;CREATE SCHEMA IF NOT EXISTS MYDB2\\;
hibernate.multiTenancy=SCHEMA
hibernate.multi_tenant_connection_provider=\
  com.baeldung.hibernate.multitenancy.schema.SchemaMultiTenantConnectionProvider

Для целей нашего теста мы настроили свойство hibernate.connection.url для создания двух схем. Это не должно быть необходимо для реального приложения, так как схемы уже должны быть на месте.

Для нашего теста мы добавим одну запись Car в tenant myDb1. Мы проверим, что эта запись была сохранена в нашей базе данных и что она не находится в клиенте mdb2 :

@Test
void whenAddingEntries_thenOnlyAddedToConcreteDatabase() {
    whenCurrentTenantIs(TenantIdNames.MYDB1);
    whenAddCar("myCar");
    thenCarFound("myCar");
    whenCurrentTenantIs(TenantIdNames.MYDB2);
    thenCarNotFound("myCar");
}

Как мы можем видеть в тесте, мы меняем арендатора при вызове метода , когда текущим арендатором является .

5. Подход к базе данных

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

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

Для интерфейса MultiTenantConnectionProvider мы будем использовать коллекцию Map , чтобы получить идентификатор Поставщика подключения для каждого арендатора:

class MapMultiTenantConnectionProvider
  extends AbstractMultiTenantConnectionProvider {

    private Map connectionProviderMap
     = new HashMap<>();

    public MapMultiTenantConnectionProvider() throws IOException {
        initConnectionProviderForTenant(TenantIdNames.MYDB1);
        initConnectionProviderForTenant(TenantIdNames.MYDB2);
    }

    @Override
    protected ConnectionProvider getAnyConnectionProvider() {
        return connectionProviderMap.values()
          .iterator()
          .next();
    }

    @Override
    protected ConnectionProvider selectConnectionProvider(
      String tenantIdentifier) {
 
        return connectionProviderMap.get(tenantIdentifier);
    }

    private void initConnectionProviderForTenant(String tenantId)
     throws IOException {
        Properties properties = new Properties();
        properties.load(getClass().getResourceAsStream(
          String.format("/hibernate-database-%s.properties", tenantId)));
        DriverManagerConnectionProviderImpl connectionProvider 
          = new DriverManagerConnectionProviderImpl();
        connectionProvider.configure(properties);
        this.connectionProviderMap.put(tenantId, connectionProvider);
    }
}

Каждый Поставщик соединений заполняется через файл конфигурации hibernate-database-<идентификатор клиента>.properties, в котором содержатся все сведения о соединении:

hibernate.connection.driver_class=org.h2.Driver
hibernate.connection.url=jdbc:h2:mem:;DB_CLOSE_DELAY=-1
hibernate.connection.username=sa
hibernate.dialect=org.hibernate.dialect.H2Dialect

Наконец, давайте снова обновим hibernate.properties , чтобы использовать модель мульти-аренды базы данных и нашу реализацию интерфейса MultiTenantConnectionProvider :

hibernate.multiTenancy=DATABASE
hibernate.multi_tenant_connection_provider=\
  com.baeldung.hibernate.multitenancy.database.MapMultiTenantConnectionProvider

Если мы запустим тот же самый тест, что и в подходе схемы, тест пройдет снова.

6. Заключение

В этой статье рассматривается поддержка гибернации 5 для мультитенантности с использованием отдельных подходов к базе данных и отдельным схемам. Мы приводим очень упрощенные реализации и примеры, чтобы исследовать различия между этими двумя стратегиями.

Полные примеры кода, использованные в этой статье, доступны в нашем проекте GitHub .