DEV Community

Felipe Moysés
Felipe Moysés

Posted on

Como ter múltiplos datasources em seu projeto spring boot

Volta e meia, nos encontramos em corner cases no nosso dia-a-dia
Recentemente tive que, de forma não usual, acessar duas bases no mesmo projeto java com spring data.

Para resolver essa necessidade, tive que realizar algumas alterações na configuração.

Irei mostrar nessa receita rápida uma forma de atingir esse resultado.

Mise en Place

mise en place Foto de Rudy Issa na Unsplash

Para acompanhar esse post você pode seguir a seguinte receita, alguns itens podem ser substituídos por outros análogos ;)

  • Java 17
  • Spring Boot 3.1.3. Obrigatório.
  • Mysql
  • Editor de texto da tua escolha.
  • Docker

O projeto final está disponível no github.

Montando o projeto

Em Spring Initializr, adicionamos as seguintes dependências:

  • Spring Data JPA
  • MySQL Driver
  • Lombok

Spring Initializr

Abrindo nosso projeto em algum editor de código/IDE, iremos criar a mesma estrutura de pacote que está no github, nada de Clean Architecture, pois não é objeto desse post.

O que nos interessa são os pacote config e o arquivo application.properties.

Os pacotes:
Estrutura de pacotes

Arquivo de configuração do projeto

Para cada base de dados teremos uma url, um usuário e uma senha.
Por questão de simplicidade, não utilizaremos variáveis de ambiente para esconder informações sensíveis.

#### FISH
spring.first-datasource.url=jdbc:mysql://127.0.0.1:3309/rare_fishes_db?useSSL=false&allowPublicKeyRetrieval=true
spring.first-datasource.username=user
spring.first-datasource.password=pass

#### SHIPPING
spring.second-datasource.url=jdbc:mysql://127.0.0.1:3307/shipping_db?useSSL=false&allowPublicKeyRetrieval=true
spring.second-datasource.username=user
spring.second-datasource.password=pass
Enter fullscreen mode Exit fullscreen mode

Datasources

O prato principal é a configuração de nossa aplicação.
Como dito anteriormente, cada base terá a tua respectiva classe de configuração de dados

Vamos nos debruçar sobre a primeira classe: PrimaryDataSourceConfiguration

Iremos perceber um padrão onde as anotações, beans e outras estruturas, estão sendo feitas manualmente.
Normalmente, é algo transparente, o framework assegura o devido funcionamento. Como estamos tratando de um caso que foge da regra, precisamos realizar esse trabalho.

PrimaryDataSourceConfiguration é uma classe de configuração com algumas coisas especiais, primeira delas é a anotação @EnableTransactionManagement.
Essa anotação indica ao Spring o datasource para realizar o gerenciamento de transações.
Bem, como assim?
Transações na base de dados são unidades de trabalho que contém instruções/operações realizadas no banco.
O Spring realiza isso para você de forma transparente, seja o commit ou o rollback de uma operação, por exemplo.
Claro, existem outros tipos de transações, sugiro essa leitura.

@EnableJpaRepositories, tem a responsabilidade de identificar e habilitar os repositórios JPA.
Ela possui diversos elementos opcionais.
No caso, temos 3 e eles estão relacionados com beans que criaremos a seguir.

entityManagerFactoryRef e o Bean LocalContainerEntityManagerFactoryBean

Criaremos um bean LocalContainerEntityManagerFactoryBean e faremos referência dele em entityManagerFactoryRef.
O Bean em questão servirá para configuramos o nosso datasource, incluímos o vendor jpa(Hibernate, EclipseLink, etc), o próprio datasource, etc.

@Autowired
    private Environment env;

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManager() {
        LocalContainerEntityManagerFactoryBean em = 
            new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(primaryDataSource());
        em.setPackagesToScan(
                "com.nssp.rarefish.model.fish");
        HibernateJpaVendorAdapter vendorAdapter 
            = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap<String, Object> properties = new HashMap<>();
        properties.put("hibernate.dialect",
                env.getProperty("spring.jpa.properties.hibernate.dialect"));
        properties.put("spring.jpa.generate-ddl",
                env.getProperty("spring.jpa.generate-ddl"));
        properties.put("hibernate.hbm2ddl.auto",
                env.getProperty("spring.jpa.hibernate.ddl-auto"));
                em.setJpaPropertyMap(properties);
                return em;

    }
Enter fullscreen mode Exit fullscreen mode

transactionManagerRef e o Bean primaryTransactionManager

Utilizando nosso bean criado no passo anterior, estamos habilitando em nossa aplicação a infraestrutura necessária para realizar operações como criar, dar rollback, efetuar commit, das transações.

@Primary
    @Bean
    public PlatformTransactionManager primaryTransactionManager() {
        JpaTransactionManager transactionManager
            = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
                primaryEntityManager().getObject());
        return transactionManager;
    }
Enter fullscreen mode Exit fullscreen mode

Datasource

Instanciamos o nosso DriverManagerDataSource com as informações de URL, de nome de usuário e de senha.

@Primary
    @Bean
    public DataSource primaryDataSource() {
        DriverManagerDataSource dataSource 
            = new DriverManagerDataSource();
        dataSource.setDriverClassName(
                env.getProperty("spring.datasource.drive-class-name"));
        dataSource.setUrl(env.getProperty("spring.first-datasource.url"));
        dataSource.setUsername(env.getProperty("spring.first-datasource.username"));
        dataSource.setPassword(env.getProperty("spring.first-datasource.password"));
        return dataSource;
    }

Enter fullscreen mode Exit fullscreen mode

Dica: podemos substituir dessa forma pela seguinte

@Primary
@ConfigurationProperties(prefix = "spring.first-datasource")
@Bean
    public DataSource primaryDataSource() {
            return DataSourceBuilder.create().build();
}
Enter fullscreen mode Exit fullscreen mode

Juntando tudo

package com.nssp.rarefish.config;

import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "primaryEntityManager",
    transactionManagerRef = "primaryTransactionManager",
    basePackages = {"com.nssp.rarefish.model.fish"})
public class PrimaryDataSourceConfiguration {
    @Autowired
    private Environment env;

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManager() {
        LocalContainerEntityManagerFactoryBean em = 
            new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(primaryDataSource());
        em.setPackagesToScan(
                "com.nssp.rarefish.model.fish");
        HibernateJpaVendorAdapter vendorAdapter 
            = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap<String, Object> properties = new HashMap<>();
        properties.put("hibernate.dialect",
                env.getProperty("spring.jpa.properties.hibernate.dialect"));
        properties.put("spring.jpa.generate-ddl",
                env.getProperty("spring.jpa.generate-ddl"));
        properties.put("hibernate.hbm2ddl.auto",
                env.getProperty("spring.jpa.hibernate.ddl-auto"));
                em.setJpaPropertyMap(properties);
                return em;

    }

    @Primary
    @Bean
    public DataSource primaryDataSource() {
        DriverManagerDataSource dataSource 
            = new DriverManagerDataSource();
        dataSource.setDriverClassName(
                env.getProperty("spring.datasource.drive-class-name"));
        dataSource.setUrl(env.getProperty("spring.first-datasource.url"));
        dataSource.setUsername(env.getProperty("spring.first-datasource.username"));
        dataSource.setPassword(env.getProperty("spring.first-datasource.password"));
        return dataSource;
    }

    @Primary
    @Bean
    public PlatformTransactionManager primaryTransactionManager() {
        JpaTransactionManager transactionManager
            = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
                primaryEntityManager().getObject());
        return transactionManager;
    }
}
Enter fullscreen mode Exit fullscreen mode

E com isso, temos 2 datasources de bancos de dados distintos prontos a serem utilizados!

Obrigado!

Top comments (1)

Collapse
 
aresxue profile image
Ares_xue

This is not elegant enough, and you need to modify the code to add the data source later. A better implementation can refer to this: github.com/baomidou/dynamic-dataso...