Guide de Spring AbstractRoutingDatasource

1. Vue d’ensemble

Dans cet article rapide, nous allons examiner la méthode AbstractRoutingDatasource de Spring comme un moyen de déterminer dynamiquement la DataSource réelle en fonction du contexte actuel .

En conséquence, nous verrons que nous pouvons conserver la logique de recherche DataSource en dehors du code d’accès aux données.

2. Dépendances Maven

Commençons par déclarer spring-context, spring-jdbc, spring-test, et h2 comme dépendances dans le pom.xml :

<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>

La dernière version des dépendances est disponible à l’adresse https://search.maven.org/classic/#search%7Cga%7C1%7C(g%3A%22org.springframework%22%20AND%20(a%3A%22spring- contexte% 22% 20or% 20a% 3a% 22spring-jdbc% 22% 20%% 20a% 3a% 22spring-test% 22))% 20OR% 20 (g% 3A% 22com.h2database% 22% 20AND% 20a% 3a% 22h2% 22)[ici].

3. Contexte de la source de données

AbstractRoutingDatasource nécessite des informations pour savoir à quel DataSource réel il faut router Ces informations sont généralement appelées Context.

Alors que Context utilisé avec AbstractRoutingDatasource peut être n’importe quel Object, un enum est utilisé pour les définir. Dans notre exemple, nous allons utiliser la notion d’un ClientDatabase comme contexte avec l’implémentation suivante:

public enum ClientDatabase {
    CLIENT__A, CLIENT__B
}

Il est intéressant de noter que, dans la pratique, le contexte peut être celui qui a du sens pour le domaine en question.

Par exemple, un autre cas d’utilisation courant implique l’utilisation de la notion d’un Environment pour définir le contexte. Dans un tel scénario, le contexte pourrait être une énumération contenant PRODUCTION , DEVELOPMENT et TESTING .

4. Détenteur de contexte

L’implémentation de détenteur de contexte est un conteneur qui stocke le contexte actuel en tant que référence de lien:/java-threadlocal[ ThreadLocal ].

En plus de contenir la référence, il doit contenir des méthodes statiques pour la définir, l’obtenir et la supprimer. AbstractRoutingDatasource interrogera le ContextHolder pour le contexte et utilisera ensuite le contexte pour rechercher le DataSource réel.

Il est extrêmement important d’utiliser ThreadLocal ici pour que le contexte soit lié au thread en cours d’exécution .

Il est essentiel d’adopter cette approche pour que le comportement soit fiable lorsque la logique d’accès aux données couvre plusieurs sources de données et utilise des transactions:

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. Routeur de source de données

Nous définissons notre ClientDataSourceRouter pour étendre le Spring AbstractRoutingDataSource . Nous implémentons la méthode nécessaire determineCurrentLookupKey pour interroger notre ClientDatabaseContextHolder et renvoyer la clé appropriée.

L’implémentation AbstractRoutingDataSource gère le reste du travail pour nous et renvoie de manière transparente le DataSource approprié:

public class ClientDataSourceRouter
  extends AbstractRoutingDataSource {

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

6. Configuration

Nous avons besoin d’un Map de contextes avec des objets DataSource pour configurer notre AbstractRoutingDataSource . Nous pouvons également spécifier un DataSource par défaut à utiliser s’il n’y a pas de contexte défini.

Les __DataSource __s que nous utilisons peuvent provenir de n’importe où, mais seront généralement créés à l’exécution ou recherchés à l’aide de JNDI:

@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. Usage

Lorsque vous utilisez notre AbstractRoutingDataSource , nous définissons d’abord le contexte, puis nous effectuons notre opération. Nous utilisons une couche de service qui prend le contexte en tant que paramètre et le définit avant de déléguer le code d’accès aux données et d’effacer le contexte après l’appel.

Au lieu d’effacer manuellement le contexte dans une méthode de service, la logique d’effacement peut être gérée par une coupure de point AOP.

Il est important de se rappeler que le contexte est thread bound , en particulier si la logique d’accès aux données doit couvrir plusieurs sources de données et transactions:

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. Conclusion

Dans ce tutoriel, nous avons examiné l’exemple d’utilisation de Spring AbstractRoutingDataSource . Nous avons implémenté une solution utilisant la notion de Client - où chaque client a son DataSource .

Et, comme toujours, des exemples peuvent être trouvés over sur GitHub .