So implementieren Sie eine Multi Tenant Spring Boot-Anwendung (jeder Benutzer hat eine eigene Datenbank)

Nov 21 2020

Ich erstelle eine REST-API mit Spring Boot und möchte eine Multi-Tenant-Struktur für den Umgang mit Daten implementieren. Ich möchte eine Datenbank namens Maindas haben wird UserTabelle, die Daten über Benutzer hava werden (Benutzername, Passwort ... und ein Feld , databasedas die Datenbank ernannt dieses Benutzers bezeichnen wird). Jedes Mal, wenn ein Benutzer sich anmeldet, wird seine jeweilige Datenbank erstellt (dies ist einer der Punkte, an denen ich auf Schwierigkeiten stoße). Ich habe verschiedene Tutorials gelesen und alle sind mit vordefinierten Datasources in der application.propertiesDatei. Dies ist hier eindeutig nicht der Fall, da die Datenbank für jeden Benutzer "on the fly" erstellt wird oder auf sie zugegriffen wird, wenn sie bereits erstellt wurde.

Der Workflow sieht folgendermaßen aus (so einfach wie möglich erklärt):

  1. Benutzeranmeldung
  2. Die App erstellt die Benutzerentität, speichert sie in der MainDatenbank und erstellt die entsprechende Datenbank für den Benutzer
  3. Die App prüft bei jedem Anruf, ob der Benutzer authentifiziert ist, und holt dann Daten aus seiner Datenbank

Dann gibt es viele Fragen zum Füllen der DBs, wenn diese automatisch erstellt werden. Aber das Wichtigste zuerst :)

Mein Stack: POSTGRESQL, Spring Boot

Danke im Voraus.

Antworten

3 MilindBarve Nov 21 2020 at 18:16

Die Mandantenfähigkeit kann nach Bedarf erreicht werden, indem Sie die folgenden Schritte ausführen.

  1. Fügen Sie zwei Konfigurationsklassen hinzu, eine für die gemeinsam genutzte Datenbank und eine für die Mandantendatenbank, mit der LocalContainerEntityManagerFactoryBean konfiguriert wird. Diese Bean sollte die erforderlichen Mandantenfähigkeitseigenschaften für LocalContainerEntityManagerFactoryBean festlegen, z
 Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
        this.properties.getProperties(), new HibernateSettings());


    properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
    properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, this.connectionProvider);
    properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, this.resolver);
    properties.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");

Diese Klasse sollte auch den benannten Bean transactionManager für jeden Typ implementieren. z.B

 @Bean(name = "tenantTransactionManager")
  public PlatformTransactionManager transactionManager() {
    JpaTransactionManager tm = new JpaTransactionManager();
    tm.setEntityManagerFactory(this.entityManagerFactory().getObject());
    return tm;
  }

  1. Implementieren Sie die Schnittstelle CurrentTenantIdentifierResolver und die Methode resolveCurrentTenantIdentifier. Dies sollte den Datenbanknamen des Mandanten basierend auf dem aktuell angemeldeten Benutzer zurückgeben. Oder Standarddatenbankname, wenn kein Benutzer angemeldet ist

  2. Ein Thread-sicherer Kontextinhaber, der sich den aktuellen Mandantennamen merkt

  3. Kommentieren Sie die Service-Implementierungen für die Entitätsklassen mit @Transactional Annotation und übergeben Sie den Bean-Namen des entsprechenden Entitätsmanagers, z

@Transactional("tenantTransactionManager") // for tenant database

und

@Transactional("transactionManager") // for shared database.

  1. Richten Sie eine Methode zur Erstellung eines Datenbankschemas ein, wenn sich ein neuer Benutzer anmeldet. und pflegen Sie den Namen der Mandantendatenbank als eine der Spalten in der Benutzertabelle im gemeinsam genutzten Schema.

  2. Wenn Sie Spring Security verwenden, implementieren Sie die UserDetailsService-Schnittstelle und implementieren Sie die Methode loadUserByUsername so, dass ein Objekt der TenantUser-Klasse zurückgegeben wird, das zusätzliche Informationen (Tenant-Datenbankname) für die Benutzeranmeldung enthält.

public class TenantUser extends org.springframework.security.core.userdetails.User {
 

  /** The tenand id. */
  private String tenantId;

Hoffe, diese Schritte helfen Ihnen, das zu erreichen, was Sie wollen. Es sind viele Artikel verfügbar, in denen alle diese Schritte ausführlich erläutert werden. Meine Implementierung ist tief in mein Projekt eingebettet, daher befindet sie sich nicht in einem Zustand, der als Arbeitsbeispiel geteilt werden kann.

Gerne beantworten wir weitere Fragen

Renis1235 Nov 23 2020 at 13:43

Die vollständige Lösung für mein Problem habe ich hier gefunden:

Mandantenfähigkeit: Verwalten mehrerer Datenquellen mit Spring Data JPA

Vielen Dank an den Autor @ Cepr0.

Das einzige, was fehlt, ist das Erstellen der Datenbank im laufenden Betrieb. Ich werde die Antwort hier aktualisieren, wenn ich meine Implementierung abgeschlossen habe.

AKTUALISIEREN

Ich habe die Datenbank mit dem folgenden Code erstellt, der von @Milind Barve empfohlen wurde. Also vielen Dank.

 Class.forName("org.postgresql.Driver");
 Connection con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/","postgres", "password");
 Statement smt = con.createStatement();

 smt.executeUpdate("CREATE DATABASE [name_of_db_here] WITH OWNER DEFAULT");

UPDATE: Beim Initialisieren des Schemas jeder neu erstellten Datenbank habe ich eine SQL-Datei mit allen Tabellenerstellungen erstellt und FlyWay verwendet, um jede neu erstellte Datenbank zu initialisieren

// INITIALIZE THE DB
            Flyway flyway = Flyway.configure()
                    .dataSource(dataSource)
                    .target(MigrationVersion.LATEST)
                    .load();

            flyway.migrate();