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
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
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.
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
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;
}
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;
}
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;
}
Dica: podemos substituir dessa forma pela seguinte
@Primary
@ConfigurationProperties(prefix = "spring.first-datasource")
@Bean
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
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;
}
}
E com isso, temos 2 datasources de bancos de dados distintos prontos a serem utilizados!
Obrigado!
Top comments (1)
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...