Ръководство за многостранно финансиране в хибернация 5

1. Въведение

Мултитенансът позволява на множество клиенти или наематели да използват един ресурс или, в контекста на тази статия, един екземпляр на база данни. Целта е да се изолира информацията, от която се нуждае всеки наемател, от споделената база данни .

В този урок ще представим различни подходи за конфигуриране на мултитенанс в Hibernate 5.

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

Ще трябва да включим зависимостта от хибернация-ядро във файла 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 абстрахира сложността около изпълнението на всеки подход.

Всичко, от което се нуждаем, е да осигурим изпълнение на тези два интерфейса :

  • MultiTenantConnectionProvider - осигурява връзки на клиент

  • CurrentTenantIdentifierResolver - разрешава използвания идентификатор на наемателя

Нека да разгледаме по-подробно всяка концепция, преди да преминем през примери за база данни и схеми.

3.1. MultiTenantConnectionProvider

По принцип този интерфейс осигурява връзка с база данни за конкретен идентификатор на наемател.

Нека видим двата му основни метода:

interface MultiTenantConnectionProvider extends Service, Wrapped { Connection getAnyConnection() throws SQLException; Connection getConnection(String tenantIdentifier) throws SQLException; // ... }

Ако Hibernate не може да разреши идентификатора на наемателя, който да използва, той ще използва метода getAnyConnection, за да получи връзка. В противен случай ще използва метода getConnection .

Hibernate предоставя две реализации на този интерфейс в зависимост от това как дефинираме връзките с базата данни:

  • Използвайки интерфейса DataSource от Java - бихме използвали изпълнението на DataSourceBasedMultiTenantConnectionProviderImpl
  • Използвайки интерфейса ConnectionProvider от Hibernate - бихме използвали изпълнението на AbstractMultiTenantConnectionProvider

3.2. CurrentTenantIdentifierResolver

Има много възможни начини за разрешаване на идентификатор на наемател . Например, нашата реализация може да използва един идентификатор на клиент, дефиниран в конфигурационен файл.

Друг начин може да бъде използването на идентификатора на наемателя от параметър на пътя.

Нека видим този интерфейс:

public interface CurrentTenantIdentifierResolver { String resolveCurrentTenantIdentifier(); boolean validateExistingCurrentSessions(); }

Hibernate извиква метода resolCurrentTenantIdentifier, за да получи идентификатора на наемателя. Ако искаме 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, за да създадем две схеми. Това не би трябвало да е необходимо за реално приложение, тъй като схемите трябва да са вече на място.

За нашия тест ще добавим един запис за автомобил в наемателя myDb1. Ще проверим, че този запис е съхраняван в нашата база данни и че не е в наемателя myDb2 :

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

Както можем да видим в теста, ние променяме наемателя при извикване на метода whenCurrentTenantIs .

5. Подход към базата данни

Подходът за мулти-наемане на база данни използва различни физически екземпляри на база данни на клиент . Тъй като всеки наемател е напълно изолиран, трябва да изберем тази стратегия, когато се нуждаем от специални функции на базата данни като архивиране на наемател повече, отколкото се нуждаем от най-добро представяне.

For the Database approach, we'll use the same MultitenancyIntegrationTest class and the CurrentTenantIdentifierResolver interface as above.

For the MultiTenantConnectionProvider interface, we'll use a Map collection to get a ConnectionProvider per tenant identifier:

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); } }

Each ConnectionProvider is populated via the configuration file hibernate-database-.properties, which has all the connection details:

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

Finally, let's update the hibernate.properties again to use the database multitenancy mode and our implementation of the MultiTenantConnectionProvider interface:

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

Ако изпълним точно същия тест, както при подхода към схемата, тестът преминава отново.

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

Тази статия обхваща поддръжката на Hibernate 5 за многонационално използване на подходите на отделна база данни и отделни схеми. Ние предлагаме много опростени изпълнения и примери за изследване на разликите между тези две стратегии.

Пълните примерни кодове, използвани в тази статия, са достъпни в нашия проект GitHub.