Vodič za višenamjensko spavanje u hibernaciji 5

1. Uvod

Multitenancy omogućava više klijenata ili stanara da koriste jedan resurs ili, u kontekstu ovog članka, jednu instancu baze podataka. Svrha je kako bi izolirao informacije koje su potrebne svakom stanaru iz zajedničke baze podataka.

U ovom uputstvu predstavit ćemo razne pristupe konfiguriranju multitenancije u hibernaciji 5.

2. Ovisnosti Mavena

Morat ćemo uključiti hibernacija-jezgra ovisnost u pom.xml datoteka:

 org.hibernate hibernate-core 5.2.12.Final 

Za testiranje ćemo koristiti H2 bazu podataka u memoriji, pa dodajmo i ovu ovisnost na pom.xml datoteka:

 com.h2data baza podataka h2 1.4.196 

3. Razumijevanje višenamjenskog spavanja u hibernaciji

Kao što je spomenuto u službenom vodiču za hibernaciju, postoje tri pristupa multitenanti u hibernaciji:

  • Odvojena shema - jedna shema po stanaru u istoj fizičkoj instanci baze podataka
  • Odvojena baza podataka - jedna zasebna instanca fizičke baze podataka po stanaru
  • Podijeljeni podaci (diskriminator) - podaci za svakog stanara podijeljeni su s vrijednošću diskriminacije

The Hibernate još uvijek ne podržava particionirani (diskriminatorni) pristup podacima. Pratite ovo pitanje JIRA-e za budući napredak.

Kao i obično, Hibernate apstrahira složenost oko provedbe svakog pristupa.

Sve što trebamo jest osigurati provedbu ova dva sučelja:

  • MultiTenantConnectionProvider - pruža veze po stanaru

  • CurrentTenantIdentifierResolver - rješava identifikator stanara koji će se koristiti

Pogledajmo detaljnije svaki koncept prije nego što prođemo kroz primjere pristupa bazi podataka i shemi.

3.1.MultiTenantConnectionProvider

U osnovi, ovo sučelje pruža vezu baze podataka za konkretni identifikator stanara.

Pogledajmo njegove dvije glavne metode:

sučelje MultiTenantConnectionProvider proširuje uslugu, omotano {Connection getAnyConnection () baca SQLException; Veza getConnection (String tenantIdentifier) ​​baca SQLException; // ...}

Ako Hibernate ne može razriješiti identifikator stanara koji će se koristiti, koristit će metodu getAnyConnection da biste dobili vezu. Inače će koristiti metodu getConnection.

Hibernate nudi dvije implementacije ovog sučelja, ovisno o tome kako definiramo veze s bazom podataka:

  • Koristeći DataSource sučelje s Jave - koristili bismo DataSourceBasedMultiTenantConnectionProviderImpl provedba
  • Koristiti ConnectionProvider sučelje iz hibernacije - koristili bismo AbstractMultiTenantConnectionProvider provedba

3.2.CurrentTenantIdentifierResolver

Tamo su mnogo mogućih načina za rješavanje identifikatora stanara. Na primjer, naša bi implementacija mogla koristiti jedan identifikator stanara definiran u konfiguracijskoj datoteci.

Drugi način može biti upotreba identifikatora stanara iz parametra puta.

Pogledajmo ovo sučelje:

javno sučelje CurrentTenantIdentifierResolver {Niz rješavaCurrentTenantIdentifier (); logička validateExistingCurrentSessions (); }

Hibernate poziva metodu resolCurrentTenantIdentifier da biste dobili identifikator stanara. Ako želimo da Hibernate provjeri valjanost svih postojećih sesija pripadaju istom identifikatoru stanara, metodi validateExistingCurrentSessions treba vratiti istina.

4. Pristup shemi

U ovoj strategiji koristit ćemo različite sheme ili korisnike u istoj instanci fizičke baze podataka. Ovaj pristup treba koristiti kada nam je potrebna najbolja izvedba naše aplikacije i može žrtvovati posebne značajke baze podataka, poput sigurnosne kopije po stanaru.

Također, rugat ćemo se CurrentTenantIdentifierResolver sučelje za pružanje jednog identifikatora stanara kao naš izbor tijekom testa:

javna apstraktna klasa MultitenancyIntegrationTest {@Mock private CurrentTenantIdentifierResolver currentTenantIdentifierResolver; privatna SessionFactory sessionFactory; @Prije javnog void postavljanja () baca IOException {MockitoAnnotations.initMocks (this); kada (currentTenantIdentifierResolver.validateExistingCurrentSessions ()). thenReturn (false); Svojstva svojstva = getHibernateProperties (); svojstva.put (AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver); sessionFactory = buildSessionFactory (svojstva); initTenant (TenantIdNames.MYDB1); initTenant (TenantIdNames.MYDB2); } zaštićena void initTenant (string tenantId) {kada (currentTenantIdentifierResolver .resolveCurrentTenantIdentifier ()). thenReturn (tenantId); createCarTable (); }}

Naša provedba MultiTenantConnectionProvider sučelje će postavite shemu da se koristi svaki put kad se zatraži veza:

klasa SchemaMultiTenantConnectionProvider proširuje AbstractMultiTenantConnectionProvider {private ConnectionProvider connectionProvider; javni SchemaMultiTenantConnectionProvider () baca IOException {this.connectionProvider = initConnectionProvider (); } @Override protected ConnectionProvider getAnyConnectionProvider () {return connectionProvider; } @Override zaštićen ConnectionProvider selectConnectionProvider (String tenantIdentifier) ​​{return connectionProvider; } @Override public Connection getConnection (String tenantIdentifier) ​​baca SQLException {Connection connection = super.getConnection (tenantIdentifier); connection.createStatement () .execute (String.format ("SET SCHEMA% s;", tenantIdentifier)); povratna veza; } private ConnectionProvider initConnectionProvider () baca IOException {Svojstva svojstva = nova svojstva (); svojstva.load (getClass () .getResourceAsStream ("/ hibernate.properties")); DriverManagerConnectionProviderImpl connectionProvider = novi DriverManagerConnectionProviderImpl (); connectionProvider.configure (svojstva); povratna vezaProvider; }}

Dakle, koristit ćemo jednu memorijsku H2 bazu podataka s dvije sheme - po jednu za svakog stanara.

Konfigurirajmo hibernirati.svojstva za korištenje načina višenamjenske sheme i našu implementaciju MultiTenantConnectionProvider sučelje:

hibernate.connection.url = jdbc: h2: mem: mydb1; DB_CLOSE_DELAY = -1; \ INIT = IZRADI ŠEMU AKO NE POSTOJI MYDB1 \; IZRADI ŠEMU AKO NE POSTOJI MYDB2 \; hibernate.multiTenancy = SCHEMA hibernate.multi_tenant_connection_provider = \ com.baeldung.hibernate.multitenancy.schema.SchemaMultiTenantConnectionProvider

Za potrebe našeg testa konfigurirali smo hibernirati.povezivanje.url svojstvo stvaranja dvije sheme. To ne bi trebalo biti potrebno za stvarnu aplikaciju, jer bi sheme trebale biti već na mjestu.

Za naš ćemo test dodati jedan Automobil upis u stanar myDb1. Provjerit ćemo je li ovaj unos pohranjen u našoj bazi podataka i da nije u stanaru myDb2:

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

Kao što možemo vidjeti u testu, mijenjamo stanara kad zovemo whenCurrentTenantIs metoda.

5. Pristup bazi podataka

Višenamjenski pristup bazi podataka koristi različite instance fizičke baze podataka po stanaru. Budući da je svaki stanar potpuno izoliran, ovu bismo strategiju trebali odabrati kada su nam potrebne posebne značajke baze podataka poput sigurnosne kopije po stanaru više nego što nam trebaju najbolje performanse.

Za pristup bazi podataka koristit ćemo isto MultitenancyIntegrationTest razred i CurrentTenantIdentifierResolver sučelje kao gore.

Za MultiTenantConnectionProvider sučelje, koristit ćemo a Karta zbirka za dobivanje a ConnectionProvider po identifikatoru stanara:

klasa MapMultiTenantConnectionProvider proširuje AbstractMultiTenantConnectionProvider {private Map connectionProviderMap = new HashMap (); javni MapMultiTenantConnectionProvider () baca IOException {initConnectionProviderForTenant (TenantIdNames.MYDB1); initConnectionProviderForTenant (TenantIdNames.MYDB2); } @Override protected ConnectionProvider getAnyConnectionProvider () {return connectionProviderMap.values ​​() .iterator () .next (); } @Override zaštićen ConnectionProvider selectConnectionProvider (String tenantIdentifier) ​​{return connectionProviderMap.get (tenantIdentifier); } private void initConnectionProviderForTenant (String tenantId) baca IOException {Svojstva svojstva = nova svojstva (); svojstva.load (getClass (). getResourceAsStream (String.format ("/ hibernate-database-% s.properties", tenantId))); DriverManagerConnectionProviderImpl connectionProvider = novi DriverManagerConnectionProviderImpl (); connectionProvider.configure (svojstva); this.connectionProviderMap.put (tenantId, connectionProvider); }}

Svaki ConnectionProvider popunjava se putem konfiguracijske datoteke hibernate-database-.properties, koja sadrži sve detalje veze:

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

Napokon, ažurirajmo hibernirati.svojstva opet za korištenje multitenancy načina baze podataka i našu implementaciju MultiTenantConnectionProvider sučelje:

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

Ako pokrenemo potpuno isti test kao u pristupu shemi, test ponovno prolazi.

6. Zaključak

Ovaj članak pokriva podršku za Hibernate 5 za multitenanciju pomoću zasebne baze podataka i pristupa zasebnim shemama. Pružamo vrlo pojednostavljene implementacije i primjere kako bismo ispitali razlike između ove dvije strategije.

Cjeloviti uzorci koda korišteni u ovom članku dostupni su na našem GitHub projektu.