Ein Leitfaden für Spring AbstractRoutingDatasource

1. Überblick

In diesem kurzen Artikel betrachten wir Spring’s AbstractRoutingDatasource als eine Möglichkeit, die tatsächliche DataSource basierend auf dem aktuellen Kontext dynamisch zu bestimmen.

Als Ergebnis sehen wir, dass wir die DataSource -Lookup-Logik außerhalb des Datenzugriffscodes halten können.

2. Abhängigkeiten von Maven

Beginnen wir damit, spring-context, spring-jdbc, spring-test, und h2 als Abhängigkeiten in der pom.xml zu deklarieren:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.8.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.195</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Die neueste Version der Abhängigkeiten finden Sie unter https://search.maven.org/classic/#search%7Cga%7C1%7C(g%3A%22org.springframework%22%20AND%20(a%3A%22spring- Kontext% 22% 20OR% 20a% 3A% 22spring-jdbc% 22% 20OR% 20a% 3A% 22% Federtest% 22))% 20OR% 20 (g% 3A% 22com.h2database% 22% 20AND% 20a% 3A% 22h2% 22)[hier].

3. Datenquellenkontext

AbstractRoutingDatasource erfordert Informationen, um zu wissen, an welche tatsächliche DataSource weitergeleitet werden soll. Diese Informationen werden normalerweise als Context. bezeichnet.

Während der Context , der mit AbstractRoutingDatasource verwendet wird, ein beliebiges Object sein kann, wird ein enum zur Definition verwendet. In unserem Beispiel verwenden wir den Begriff einer ClientDatabase als Kontext mit der folgenden Implementierung:

public enum ClientDatabase {
    CLIENT__A, CLIENT__B
}

Es ist erwähnenswert, dass der Kontext in der Praxis das sein kann, was für die betreffende Domäne sinnvoll ist.

Ein anderer häufiger Anwendungsfall umfasst beispielsweise die Verwendung des Begriffs Environment , um den Kontext zu definieren. In einem solchen Szenario könnte der Kontext ein Enum sein, das PRODUCTION , DEVELOPMENT und TESTING enthält.

4. Kontexthalter

Die Kontexthalterimplementierung ist ein Container, der den aktuellen Kontext als Link speichert:/java-threadlocal[ ThreadLocal ].

Neben der Referenz sollte es statische Methoden zum Setzen, Abrufen und Löschen enthalten. AbstractRoutingDatasource fragt den ContextHolder nach dem Kontext ab und verwendet dann den Kontext, um die tatsächliche DataSource zu suchen.

Es ist äußerst wichtig, ThreadLocal hier zu verwenden, damit der Kontext an den aktuell ausgeführten Thread gebunden ist ** .

Es ist wichtig, diesen Ansatz zu ergreifen, damit das Verhalten zuverlässig ist, wenn die Datenzugriffslogik mehrere Datenquellen umfasst und Transaktionen verwendet:

public class ClientDatabaseContextHolder {

    private static ThreadLocal<ClientDatabase> CONTEXT
      = new ThreadLocal<>();

    public static void set(ClientDatabase clientDatabase) {
        Assert.notNull(clientDatabase, "clientDatabase cannot be null");
        CONTEXT.set(clientDatabase);
    }

    public static ClientDatabase getClientDatabase() {
        return CONTEXT.get();
    }

    public static void clear() {
        CONTEXT.remove();
    }
}

5. Datenquellen-Router

Wir definieren unseren ClientDataSourceRouter , um die Spring AbstractRoutingDataSource zu erweitern. Wir implementieren die erforderliche Methode determineCurrentLookupKey , um unseren ClientDatabaseContextHolder abzufragen und den entsprechenden Schlüssel zurückzugeben.

Die AbstractRoutingDataSource -Implementierung erledigt den Rest der Arbeit für uns und gibt die entsprechende DataSource transparent zurück:

public class ClientDataSourceRouter
  extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return ClientDatabaseContextHolder.getClientDatabase();
    }
}

6. Aufbau

Wir benötigen eine Map von Kontexten für DataSource -Objekte, um unsere AbstractRoutingDataSource zu konfigurieren. Wir können auch einen Standardwert für DataSource angeben, der verwendet werden soll, wenn kein Kontext festgelegt ist.

Die von uns verwendeten __DataSource __s können von überall her stammen, werden jedoch normalerweise zur Laufzeit erstellt oder mithilfe von JNDI nachgeschlagen:

@Configuration
public class RoutingTestConfiguration {

    @Bean
    public ClientService clientService() {
        return new ClientService(new ClientDao(clientDatasource()));
    }

    @Bean
    public DataSource clientDatasource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        DataSource clientADatasource = clientADatasource();
        DataSource clientBDatasource = clientBDatasource();
        targetDataSources.put(ClientDatabase.CLIENT__A,
          clientADatasource);
        targetDataSources.put(ClientDatabase.CLIENT__B,
          clientBDatasource);

        ClientDataSourceRouter clientRoutingDatasource
          = new ClientDataSourceRouter();
        clientRoutingDatasource.setTargetDataSources(targetDataSources);
        clientRoutingDatasource.setDefaultTargetDataSource(clientADatasource);
        return clientRoutingDatasource;
    }

   //...
}

7. Verwendungszweck

Bei Verwendung unserer AbstractRoutingDataSource setzen wir zuerst den Kontext und führen dann unsere Operation aus. Wir verwenden eine Service-Schicht, die den Kontext als Parameter übernimmt und diesen festlegt, bevor er an den Datenzugriffscode delegiert und den Kontext nach dem Aufruf gelöscht wird.

Als Alternative zum manuellen Löschen des Kontexts innerhalb einer Servicemethode kann die Löschlogik durch einen AOP-Punktschnitt behandelt werden.

Beachten Sie, dass der Kontext thread-gebunden ist , insbesondere wenn die Datenzugriffslogik mehrere Datenquellen und Transaktionen umfasst:

public class ClientService {

    private ClientDao clientDao;

   //standard constructors

    public String getClientName(ClientDatabase clientDb) {
        ClientDatabaseContextHolder.set(clientDb);
        String clientName = this.clientDao.getClientName();
        ClientDatabaseContextHolder.clear();
        return clientName;
    }
}

8. Fazit

In diesem Tutorial haben wir uns das Beispiel angesehen, wie Spring AbstractRoutingDataSource verwendet wird. Wir haben eine Lösung mit dem Begriff eines Client implementiert - wobei jeder Client seine DataSource hat.