Ir al contenido

Configurando Múltiples Fuentes de Datos en un Spring Boot

Spring Database
Autor
Harpal Singh
Software Engineer
Traducido por
Namastecode
Tabla de contenido

1. Introducción

En este breve tutorial, aprenderemos cómo configurar múltiples fuentes de datos en una aplicación Spring Boot.

Imaginemos que estamos trabajando en una aplicación de comercio electrónico y necesitamos dos bases de datos separadas para almacenar clientes y pedidos. Configuraremos estas como fuentes de datos separadas y veremos cómo utilizarlas en nuestra aplicación.

2. Configuración de Propiedades

Comencemos definiendo algunas propiedades en application.properties para nuestras fuentes de datos de Cliente y Pedido:

#Customer db
spring.datasource.customer.url = [url]
spring.datasource.customer.username = [username]
spring.datasource.customer.password = [password] 
spring.datasource.customer.driverClassName = org.postgresql.Driver

#Order db
spring.datasource.order.url = [url]
spring.datasource.order.username = [username]
spring.datasource.order.password = [password]
spring.datasource.order.driverClassName = org.sqlite.JDBC

Arriba, definimos las fuentes de datos para las dos bases de datos y sus respectivas propiedades. Además, podemos configurar diferentes tipos de bases de datos.

Ahora, podemos usar estas propiedades dentro de nuestra aplicación.

3. Configuración de Fuentes de Datos

A continuación, crearemos una clase de configuración donde definiremos dos beans de DataSource para cada base de datos:

@Configuration
public class CustomerOrderDataSourcesConfiguration {

  @Primary 
  @Bean(name = "customerDataSource")
  @ConfigurationProperties(prefix = "spring.datasource.customer") 
  public DataSource customerDataSource() {
      return DataSourceBuilder.create().build();
  }

  @Bean(name = "orderDataSource")
  @ConfigurationProperties(prefix = "spring.datasource.order")
  public DataSource orderDataSource() {
      return DataSourceBuilder.create().build();  
  }

}

Estamos nombrando explícitamente nuestros beans para poder decidir a qué fuente de datos referirnos utilizando la anotación @Qualifier. Además, hemos marcado el customerDataSource como @Primary, y lo consideraremos la base de datos principal para nuestra aplicación. Así que, en caso de que no utilicemos la anotación @Qualifier, Spring inyectará la fuente de datos del cliente donde sea necesario.

Veamos cómo podemos usar estos beans.

4. Usando las Fuentes de Datos

Podemos inyectar las dos fuentes de datos cuando sea necesario en nuestra aplicación.
Por ejemplo, podemos usar la anotación @Autowired para inyectar la fuente de datos dentro de una clase de servicio:

@Autowired  
@Qualifier("orderDataSource")
private DataSource orderDataSource;

Sin embargo, la mayor parte del tiempo, queremos conectar la fuente de datos a un repositorio de Spring Data JPA y usar eso en lugar de interactuar directamente con la fuente de datos.

Veamos cómo configurar eso a continuación.

5. Configuración de @Repository Con una Fuente de Datos Específica

Primero, necesitamos crear una clase de configuración donde utilizamos la anotación @EnableJpaRepositories para escanear los repositorios y definir la fábrica del administrador de entidades para cada fuente de datos:

@Configuration
@EnableJpaRepositories(
  basePackages = "com.namastecode.repositories.customer",
  entityManagerFactoryRef = "customerEntityManagerFactory", 
  transactionManagerRef = "customerTransactionManager"
)
public class CustomerDataSourceConfiguration{
  // More code
}

Ahora, necesitamos crear el bean entityManagerFactory dentro de esta clase:

@Primary
@Bean(name = "customerEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory(
  EntityManagerFactoryBuilder builder,
  @Qualifier("customerDataSource") DataSource dataSource) {
  return builder
    .dataSource(dataSource)
    .packages("com.namastecode.domain.customer") // entities package
    .persistenceUnit("customers")
    .build();
}

A continuación, crearemos el bean transactionManager para que cada fuente de datos tenga su propio gestor de transacciones y no haya conflictos entre ellas:

@Primary
@Bean(name = "customerTransactionManager")
public PlatformTransactionManager customerTransactionManager(
  @Qualifier("customerEntityManagerFactory") EntityManagerFactory customerEntityManagerFactory) {
  return new JpaTransactionManager(customerEntityManagerFactory);
}

Arriba, definimos ambos beans como @Primary, ya que la fuente de datos del cliente es la predeterminada. No necesitamos hacer eso para la fuente de datos de los pedidos.

Tenemos todo listo para crear nuestras entidades bajo el paquete com.namastecode.domain.customer:

@Entity
@Table(name = "customers")
public class Customer {
  // More code
}

De manera similar, podemos crear los repositorios bajo el paquete com.namastecode.repositories.customer:

@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
  // More code
}

Ahora, tenemos dos fuentes de datos separadas con sus recursos. Sin embargo, podría haber una situación en la que queramos configurar las fuentes de datos, por lo que deseamos utilizar una única transacción en ambas fuentes de datos. A continuación, veamos cómo hacerlo.

6. Configurando un Gestor de Transacciones Enlazadas

Si queremos realizar transacciones en ambas fuentes de datos, podemos configurar un bean ChainedTransactionManager que, más adelante, podremos utilizar en nuestras operaciones:

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

A continuación, supongamos que tenemos un CustomerOrderService donde estamos utilizando tanto CustomerRepository como OrderRepository. Así que, para usar nuestro gestor de transacciones encadenadas en esta clase de servicio, podemos definir explícitamente el gestor de transacciones:

@Service
public class CustomerOrderService {

  @Autowired
  private CustomerRepository customerRepository;
  
  @Autowired
  private OrderRepository orderRepository;

  @Transactional(transactionManager = "chainedTransactionManager") 
  public void updateCustomerAndOrder(Customer customer, Order order) {
    customerRepository.save(customer);
    orderRepository.save(order);
  }

}

Si alguna de las operaciones de guardado falla, la transacción se revertirá para ambas fuentes de datos.

7. Conclusión

En este tutorial, aprendimos cómo configurar múltiples fuentes de datos en una aplicación de Spring Boot.

Primero, definimos las propiedades de cada fuente de datos, creamos los beans de fuente de datos, configuramos el @Repository con una fuente de datos específica y, finalmente, vimos cómo configurar un administrador de transacciones compartido para ambas fuentes de datos.

Relacionados

Configurando un Cliente Web de Java para Solicitudes HTTPS
WebClient HTTPS Spring
Ejecutando PostgreSQL en un Contenedor Docker
DevOps Postgres Docker Database
Testcontainers en Pruebas de Integración de Spring Boot
Spring Testcontainers Testing