Een gids voor multitenancy in slaapstand 5

1. Inleiding

Met multitenancy kunnen meerdere clients of tenants een enkele resource of, in de context van dit artikel, een enkele database-instantie gebruiken. Het doel is om de informatie die elke tenant nodig heeft te isoleren uit de gedeelde database.

In deze tutorial introduceren we verschillende benaderingen om multitenancy in Slaapstand 5 te configureren.

2. Maven afhankelijkheden

We moeten de slaapstand-core afhankelijkheid in de pom.xml het dossier:

 org.hibernate hibernate-core 5.2.12.Final 

Voor het testen gebruiken we een H2 in-memory database, dus laten we deze afhankelijkheid ook toevoegen aan de pom.xml het dossier:

 com.h2database h2 1.4.196 

3. Inzicht in multitenancy in slaapstand

Zoals vermeld in de officiële Hibernate-gebruikershandleiding, zijn er drie benaderingen voor multitenancy in Hibernate:

  • Afzonderlijk schema - één schema per tenant in hetzelfde fysieke database-exemplaar
  • Afzonderlijke database - één afzonderlijk fysiek database-exemplaar per tenant
  • Gepartitioneerde (discriminator) gegevens - de gegevens voor elke tenant worden gepartitioneerd door een discriminatorwaarde

De Gepartitioneerde (discriminator) gegevensbenadering wordt nog niet ondersteund door Hibernate. Volg deze JIRA-kwestie op voor toekomstige vooruitgang.

Zoals gebruikelijk abstraheert Hibernate de complexiteit rond de implementatie van elke benadering.

Alles wat we nodig hebben, is bieden een implementatie van deze twee interfaces:

  • MultiTenantConnectionProvider - zorgt voor aansluitingen per huurder

  • CurrentTenantIdentifierResolver - lost de tenant-id op die moet worden gebruikt

Laten we elk concept meer in detail bekijken voordat we door de database gaan en voorbeelden van schemabenaderingen bekijken.

3.1.MultiTenantConnectionProvider

In feite biedt deze interface een databaseverbinding voor een concrete tenant-id.

Laten we eens kijken naar de twee belangrijkste methoden:

interface MultiTenantConnectionProvider breidt Service uit, Wrapped {Connection getAnyConnection () gooit SQLException; Verbinding getConnection (String tenantIdentifier) ​​genereert SQLException; // ...}

Als Hibernate de te gebruiken tenant-ID niet kan omzetten, wordt de methode gebruikt getAnyConnection om een ​​connectie te krijgen. Anders gebruikt het de methode getConnection.

Hibernate biedt twee implementaties van deze interface, afhankelijk van hoe we de databaseverbindingen definiëren:

  • Met behulp van de DataSource-interface van Java - we zouden de DataSourceBasedMultiTenantConnectionProviderImpl implementatie
  • De ... gebruiken ConnectionProvider interface van Hibernate - we zouden de AbstractMultiTenantConnectionProvider implementatie

3.2.CurrentTenantIdentifierResolver

Er zijn veel mogelijke manieren om een ​​tenant-ID om te zetten. Onze implementatie kan bijvoorbeeld één tenant-ID gebruiken die is gedefinieerd in een configuratiebestand.

Een andere manier zou kunnen zijn om de tenant-id van een padparameter te gebruiken.

Laten we deze interface eens bekijken:

openbare interface CurrentTenantIdentifierResolver {String resolvCurrentTenantIdentifier (); boolean validateExistingCurrentSessions (); }

Hibernate roept de methode op resolCurrentTenantIdentifier om de tenant-id op te halen. Als we willen dat Hibernate alle bestaande sessies valideert, behoren tot dezelfde tenant-id, de methode validateExistingCurrentSessions moet true retourneren.

4. Schemabenadering

In deze strategie gebruiken we verschillende schema's of gebruikers in dezelfde fysieke database-instantie. Deze benadering moet worden gebruikt wanneer we de beste prestaties voor onze applicatie nodig hebben en speciale databasefuncties zoals back-up per tenant kunnen opofferen.

We zullen ook de spot drijven met de CurrentTenantIdentifierResolver interface om één tenant-ID op te geven als onze keuze tijdens de test:

openbare abstracte klasse MultitenancyIntegrationTest {@Mock privé CurrentTenantIdentifierResolver currentTenantIdentifierResolver; privé SessionFactory sessionFactory; @Before public void setup () gooit IOException {MockitoAnnotations.initMocks (dit); when (currentTenantIdentifierResolver.validateExistingCurrentSessions ()) .thenReturn (false); Eigenschappen properties = getHibernateProperties (); properties.put (AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver); sessionFactory = buildSessionFactory (eigenschappen); initTenant (TenantIdNames.MYDB1); initTenant (TenantIdNames.MYDB2); } protected void initTenant (String tenantId) {when (currentTenantIdentifierResolver .resolveCurrentTenantIdentifier ()) .thenReturn (tenantId); createCarTable (); }}

Onze implementatie van de MultiTenantConnectionProvider interface zal stel het schema in dat moet worden gebruikt elke keer dat een verbinding wordt aangevraagd:

klasse SchemaMultiTenantConnectionProvider breidt AbstractMultiTenantConnectionProvider {private ConnectionProvider connectionProvider uit; openbare SchemaMultiTenantConnectionProvider () gooit IOException {this.connectionProvider = initConnectionProvider (); } @Override beschermde ConnectionProvider getAnyConnectionProvider () {return connectionProvider; } @Override beschermde ConnectionProvider selectConnectionProvider (String tenantIdentifier) ​​{return connectionProvider; } @Override openbare verbinding getConnection (String tenantIdentifier) ​​genereert SQLException {Connection connection = super.getConnection (tenantIdentifier); connection.createStatement () .execute (String.format ("SET SCHEMA% s;", tenantIdentifier)); retourverbinding; } private ConnectionProvider initConnectionProvider () gooit IOException {Properties properties = new Properties (); properties.load (getClass () .getResourceAsStream ("/ hibernate.properties")); DriverManagerConnectionProviderImpl connectionProvider = nieuwe DriverManagerConnectionProviderImpl (); connectionProvider.configure (eigenschappen); return connectionProvider; }}

We gebruiken dus één in-memory H2-database met twee schema's: één per tenant.

Laten we het slaapstand.eigenschappen om de multitenancy-modus van het schema en onze implementatie van het MultiTenantConnectionProvider koppel:

hibernate.connection.url = jdbc: h2: mem: mydb1; DB_CLOSE_DELAY = -1; \ INIT = SCHEMA MAKEN INDIEN NIET BESTAAT MYDB1 \; SCHEMA MAKEN INDIEN NIET BESTAAT MYDB2 \; hibernate.multiTenancy = SCHEMA hibernate.multi_tenant_connection_provider = \ com.baeldung.hibernate.multitenancy.schema.SchemaMultiTenantConnectionProvider

Voor onze test hebben we het slaapstand.connection.url eigenschap om twee schema's te maken. Dit zou niet nodig moeten zijn voor een echte toepassing, aangezien de schema's al aanwezig zouden moeten zijn.

Voor onze test voegen we er een toe Auto vermelding in de huurder myDb1. We zullen controleren of dit item is opgeslagen in onze database en dat het niet in de tenant staat myDb2:

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

Zoals we in de test kunnen zien, veranderen we de huurder wanneer we bellen naar het whenCurrentTenantIs methode.

5. Database-aanpak

De database-multi-tenancy-benadering gebruikt verschillende fysieke database-exemplaren per tenant. Omdat elke huurder volledig geïsoleerd is, we moeten deze strategie kiezen wanneer we speciale databasefuncties zoals back-up per tenant meer nodig hebben dan de beste prestaties.

Voor de databasebenadering gebruiken we hetzelfde MultitenancyIntegrationTest klasse en de CurrentTenantIdentifierResolver interface zoals hierboven.

Voor de MultiTenantConnectionProvider interface gebruiken we een Kaart collectie om een ConnectionProvider per tenant-ID:

klasse MapMultiTenantConnectionProvider breidt AbstractMultiTenantConnectionProvider uit {privé Map connectionProviderMap = nieuwe HashMap (); openbare MapMultiTenantConnectionProvider () gooit IOException {initConnectionProviderForTenant (TenantIdNames.MYDB1); initConnectionProviderForTenant (TenantIdNames.MYDB2); } @Override beschermde ConnectionProvider getAnyConnectionProvider () {return connectionProviderMap.values ​​() .iterator () .next (); } @Override beschermde ConnectionProvider selectConnectionProvider (String tenantIdentifier) ​​{return connectionProviderMap.get (tenantIdentifier); } private void initConnectionProviderForTenant (String tenantId) gooit IOException {Properties properties = new Properties (); properties.load (getClass (). getResourceAsStream (String.format ("/ hibernate-database-% s.properties", tenantId))); DriverManagerConnectionProviderImpl connectionProvider = nieuwe DriverManagerConnectionProviderImpl (); connectionProvider.configure (eigenschappen); this.connectionProviderMap.put (tenantId, connectionProvider); }}

Elk ConnectionProvider wordt ingevuld via het configuratiebestand slaapstand-database-.properties, die alle verbindingsdetails heeft:

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

Laten we tot slot het slaapstand.eigenschappen opnieuw om de multitenancy-modus van de database en onze implementatie van het MultiTenantConnectionProvider koppel:

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

Als we exact dezelfde test uitvoeren als in de schemabenadering, slaagt de test opnieuw.

6. Conclusie

Dit artikel behandelt Hibernate 5-ondersteuning voor multitenancy met behulp van de afzonderlijke database en afzonderlijke schemabenaderingen. We bieden zeer simplistische implementaties en voorbeelden om de verschillen tussen deze twee strategieën te onderzoeken.

De volledige codevoorbeelden die in dit artikel worden gebruikt, zijn beschikbaar op ons GitHub-project.