Spring Boot et le défi du multi datasource

Publié le 04/01/2026 Source : sfeir.dev

Ah, **Spring Boot** et son autoconfiguration si pratique !
Quelques lignes dans un application.properties, une dépendance spring-boot-starter-data-jpa, et vous voilà connecté à une base de données, prêt à manipuler vos entités en toute transparence.

Mais que se passe-t-il lorsque votre application doit interagir avec **plusieurs bases de données** ?
Ce cas, moins courant mais fréquent dans les systèmes modulaires (gestion séparée des utilisateurs, des commandes, des historiques…), nécessite une configuration manuelle plus fine.

Voyons ensemble **comment** [**Spring Boot](https://www.sfeir.dev/back/back-spring-boot/) **permet de gérer plusieurs sources de données** tout en conservant la souplesse et la clarté de son modèle.

Comprendre le multi-datasource

Le terme **multi-datasource** désigne une application qui se connecte à **plusieurs bases de données distinctes** au sein du même contexte Spring.
Chaque base possède sa propre configuration (URL, utilisateur, dialecte, schéma…) et souvent son propre modèle de données.

Pourquoi plusieurs bases de données ?

Plusieurs raisons peuvent motiver ce choix :

En somme, le multi-datasource est une réponse à une architecture plus modulaire, parfois un avant-goût d’une approche microservices.

Comment cela fonctionne dans Spring Boot

Par défaut, Spring Boot détecte **une seule source de données** et configure automatiquement tout le nécessaire :
le DataSource, l’EntityManagerFactory et le TransactionManager.

Mais dès qu’une deuxième datasource entre en jeu, cette autoconfiguration n’est plus suffisante.
Il faut alors :

  1. **Définir manuellement** chaque DataSource avec ses propriétés.
  2. **Créer un** **EntityManagerFactory** **dédié** à chaque ensemble d’entités.
  3. **Associer un** **TransactionManager** **distinct** pour gérer les transactions de manière isolée.
  4. **Spécifier clairement** à quel repository et quelle transaction chaque opération doit s’appliquer.

En pratique, cela signifie créer une **classe de configuration par base de données**, et bien séparer les packages d’entités et de repositories.

⚖️ Avantages et inconvénients

➕ Avantages

➖ Inconvénients

Exemple d’implémentation

Prenons une application simple gérant **des utilisateurs** et **des commandes** sur **deux bases H2 distinctes**.

Configuration du fichier application.properties

# ===============================
# PRIMARY DATASOURCE (USERS - H2)
# ===============================
app.datasource.users.url=jdbc:h2:mem:users_db;DB_CLOSE_DELAY=-1
app.datasource.users.username=sa
app.datasource.users.password=password
app.datasource.users.driver-class-name=org.h2.Driver
# Hibernate properties for the primary datasource
app.datasource.users.jpa.hibernate.ddl-auto=update
app.datasource.users.jpa.show-sql=true
app.datasource.users.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

# ===============================
# SECONDARY DATASOURCE (ORDERS - H2)
# ===============================
app.datasource.orders.url=jdbc:h2:mem:orders_db;DB_CLOSE_DELAY=-1
app.datasource.orders.username=sa
app.datasource.orders.password=password
app.datasource.orders.driver-class-name=org.h2.Driver
# Hibernate properties for the secondary datasource
app.datasource.orders.jpa.hibernate.ddl-auto=update
app.datasource.orders.jpa.show-sql=true
app.datasource.orders.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

Configuration de la base principale (utilisateurs)

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "userEntityManagerFactory",
        transactionManagerRef = "userTransactionManager",
        basePackages = {"fr.eletutour.multi.database.users.repository"}
)
public class UserDbConfig {

    @Primary
    @Bean(name = "userProperties")
    @ConfigurationProperties("app.datasource.users")
    public DataSourceProperties userDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "userDataSource")
    public DataSource userDataSource(@Qualifier("userProperties") DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    @Primary
    @Bean(name = "userEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean userEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("userDataSource") DataSource dataSource) {

        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");

        return builder
                .dataSource(dataSource)
                .packages("fr.eletutour.multi.database.users.domain")
                .persistenceUnit("User")
                .properties(properties)
                .build();
    }

    @Primary
    @Bean(name = "userTransactionManager")
    public PlatformTransactionManager userTransactionManager(
            @Qualifier("userEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory.getObject());
    }
}

Configuration de la base secondaire (commandes)

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "orderEntityManagerFactory",
        transactionManagerRef = "orderTransactionManager",
        basePackages = {"fr.eletutour.multi.database.orders.repository"}
)
public class OrderDbConfig {

    @Bean(name = "orderProperties")
    @ConfigurationProperties("app.datasource.orders")
    public DataSourceProperties orderDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "orderDataSource")
    public DataSource orderDataSource(@Qualifier("orderProperties") DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    @Bean(name = "orderEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("orderDataSource") DataSource dataSource) {

        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");

        return builder
                .dataSource(dataSource)
                .packages("fr.eletutour.multi.database.orders.domain")
                .persistenceUnit("Order")
                .properties(properties)
                .build();
    }

    @Bean(name = "orderTransactionManager")
    public PlatformTransactionManager orderTransactionManager(
            @Qualifier("orderEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory.getObject());
    }
}

Configuration du ChainedTransactionManager

@Configuration
public class ChainedTransactionConfig {

    @Bean(name = "chainedTransactionManager")
    public PlatformTransactionManager chainedTransactionManager(
            @Qualifier("userTransactionManager") PlatformTransactionManager userTxManager,
            @Qualifier("orderTransactionManager") PlatformTransactionManager orderTxManager) {
        return new ChainedTransactionManager(userTxManager, orderTxManager);
    }
}

Service métier avec transaction globale

@Service
public class AppService {

    private final UserRepository userRepository;
    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    public AppService(UserRepository userRepository,
                      OrderRepository orderRepository,
                      ProductRepository productRepository) {
        this.userRepository = userRepository;
        this.orderRepository = orderRepository;
        this.productRepository = productRepository;
    }

    /**
     * Cette méthode exécute une transaction globale sur les deux bases H2.
     * Si une étape échoue, toutes les opérations sont annulées.
     */
    @Transactional("chainedTransactionManager")
    public String createUserAndOrder(CreateUserAndOrderRequest request) {
        // Création de l'utilisateur (users_db)
        User user = new User(
                request.getUsername(),
                request.getFirstName(),
                request.getLastName(),
                request.getEmail()
        );
        User savedUser = userRepository.save(user);

        // Création du produit (orders_db)
        Product product = new Product(
                request.getProductName(),
                request.getProductPrice()
        );
        Product savedProduct = productRepository.save(product);

        // Création de la commande (orders_db)
        Order order = new Order(savedProduct, savedUser.getId());
        Order savedOrder = orderRepository.save(order);

        return String.format("""
                ✅ Utilisateur '%s' (ID: %d) créé dans users_db.
                ✅ Produit '%s' (ID: %d) créé dans orders_db.
                ✅ Commande (ID: %d) créée dans orders_db, liée à l’utilisateur.""",
                savedUser.getUsername(),
                savedUser.getId(),
                savedProduct.getName(),
                savedProduct.getId(),
                savedOrder.getId());
    }
}

Conclusion

Spring Boot simplifie énormément la gestion d’une **unique** source de données, mais il reste suffisamment flexible pour en gérer plusieurs sans artifice.
En définissant clairement vos DataSource, EntityManagerFactory et TransactionManager, vous gardez le contrôle complet sur la persistance.

Le multi-datasource n’est pas nécessairement la voie la plus simple, mais il est souvent la plus saine lorsqu’on cherche à isoler les responsabilités, préparer une migration vers des microservices ou simplement séparer les mondes métier.

Et c’est là toute la beauté de Spring : la complexité est maîtrisée, et chaque couche garde son indépendance.