Перейти к основному содержимому

Настройка нескольких Data Sources в Spring Boot

Profile picture
Автор
Harpal Singh
Software Engineer
Profile picture
Автор
Namastecode
Оглавление

1. Введение

В этом коротком руководстве мы узнаем, как настроить несколько data sources в приложении на Spring Boot.

Представим, что мы работаем над e-commerce app и нам нужны две отдельные databases для хранения customers и orders. Мы настроим их как отдельные data sources и посмотрим, как использовать их в нашем application.

2. Настройка Properties

Давайте начнём с определения некоторых свойств в application.properties для наших источников данных Customer и Order:

#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

Выше мы определяем data sources для двух databases и их соответствующие properties. Кроме того, мы можем настроить разные типы databases.

Теперь мы можем использовать эти properties внутри нашего приложения.

3. Настройка Data Sources

Далее мы создадим класс конфигурации, в котором определим по два DataSource bean’а для каждой database:

@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();  
  }

}

Мы явно даём имена нашим beans, чтобы мы могли выбирать, к какому data source обращаться с помощью аннотации @Qualifier. Кроме того, мы пометили customerDataSource как @Primary и считаем его основной базой данных для нашего приложения. Таким образом, если мы не используем аннотацию @Qualifier, Spring внедрит customerDataSource там, где он требуется.

Давайте посмотрим, как мы можем использовать эти beans.

4. Использование Data Sources

Мы можем внедрять оба источника данных (data sources) по мере необходимости в нашем приложении. Например, мы можем использовать аннотацию @Autowired для внедрения data source внутрь service class:

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

Однако чаще всего мы хотим подключить data source к Spring Data JPA repository и использовать его вместо непосредственного взаимодействия с data source.

Давайте посмотрим, как это настроить дальше.

5. Конфигурирование @Repository с конкретным источником данных

Сначала нам нужно создать configuration class, где мы будем использовать аннотацию @EnableJpaRepositories для сканирования repositories и определять entity manager factory для каждого data source:

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

Теперь нам нужно создать entityManagerFactory bean внутри этого класса:

@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();
}

Далее мы создадим bean transactionManager, чтобы у каждого источника данных был свой менеджер транзакций и они не конфликтовали друг с другом:

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

Выше мы помечаем оба beans как @Primary, поскольку customer data source используется по умолчанию. Для orders data source этого делать не нужно.

У нас есть всё необходимое, чтобы создать наши сущности в пакете com.namastecode.domain.customer:

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

Аналогично, мы можем создать репозитории в пакете com.namastecode.repositories.customer:

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

Теперь у нас есть два отдельных data sources с их resources. Однако может возникнуть ситуация, когда мы захотим настроить data sources так, чтобы использовать single transaction для обоих data sources. Далее посмотрим, как это сделать.

6. Настройка Chained Transaction Manager

Если мы хотим выполнять transactions одновременно в обоих data sources, мы можем настроить bean ChainedTransactionManager, который позже сможем использовать в наших operations:

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

Далее предположим, что у нас есть CustomerOrderService, в которой мы используем и CustomerRepository, и OrderRepository. Чтобы использовать наш chained transaction manager в этом классе сервиса, мы можем явно определить transaction manager:

@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);
  }

}

Как показано выше, если любая из save operations завершится неудачей, transaction будет откатан для обоих data sources.

7. Заключение

В этом руководстве мы узнали, как настроить несколько источников данных в приложении Spring Boot.

Сначала мы определили свойства каждого data source, создали data source beans, настроили @Repository на использование конкретного data source и, наконец, рассмотрели, как настроить shared transaction manager для обоих data sources.

Related

Настройка Java Web Client для HTTPS Requests
Давайте разберёмся, как настроить Java Web Client для HTTPS-запросов. Защитим наше HTTP-общение шифрованием, чтобы сохранить данные пользователей в безопасности.
Запуск PostgreSQL в Docker Container
Давайте узнаем, как запустить PostgreSQL в Docker container с помощью этого пошагового руководства. Используя Docker, мы сможем легко настроить и обеспечить portability нашей PostgreSQL database.
Testcontainers в Spring Boot Integration Tests
Узнаем, как использовать testcontainers в приложении Spring boot при создании integration tests.