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

BootstrapMode для репозиториев JPA

· 5 мин. чтения

1. Введение

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

При запуске Spring Data сканирует репозитории и регистрирует их определения bean-компонентов как bean-компоненты с одноэлементной областью. Во время инициализации репозитории немедленно получают EntityManager . В частности, они получают метамодель JPA и проверяют объявленные запросы.

По умолчанию JPA загружается синхронно. Следовательно, создание репозиториев блокируется до тех пор, пока не завершится процесс начальной загрузки . По мере роста числа репозиториев запуск приложения может занять много времени, прежде чем оно начнет принимать запросы.

2. Различные варианты загрузки репозиториев

Начнем с добавления зависимости spring-data-jpa . Поскольку мы используем Spring Boot, мы будем использовать соответствующую зависимость spring-boot-starter-data-jpa :

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Мы можем указать Spring использовать поведение начальной загрузки репозитория по умолчанию через свойство конфигурации:

spring.data.jpa.repositories.bootstrap-mode=default

Мы можем сделать то же самое, используя конфигурацию на основе аннотаций:

@SpringBootApplication
@EnableJpaRepositories(bootstrapMode = BootstrapMode.DEFAULT)
public class Application {
// ...
}

Третий подход, ограниченный одним тестовым классом, заключается в использовании аннотации @DataJpaTest :

@DataJpaTest(bootstrapMode = BootstrapMode.LAZY)
class BootstrapmodeLazyIntegrationTest {
// ...
}

В следующих примерах мы будем использовать аннотацию @DataJpaTest и рассмотрим различные варианты начальной загрузки репозитория .

2.1. По умолчанию

Значение по умолчанию для режима начальной загрузки будет жадно создавать экземпляры репозиториев. Следовательно, как и любые другие bean-компоненты Spring, их инициализация будет происходить при внедрении .

Давайте создадим объект Todo :

@Entity
public class Todo {
@Id
private Long id;
private String label;

// standard setters and getters
}

Далее нам понадобится связанный с ним репозиторий. Давайте создадим тот, который расширяет CrudRepository :

public interface TodoRepository extends CrudRepository<Todo, Long> {
}

Наконец, давайте добавим тест, использующий наш репозиторий:

@DataJpaTest
class BootstrapmodeDefaultIntegrationTest {

@Autowired
private TodoRepository todoRepository;

@Test
void givenBootstrapmodeValueIsDefault_whenCreatingTodo_shouldSuccess() {
Todo todo = new Todo("Something to be done");

assertThat(todoRepository.save(todo)).hasNoNullFieldsOrProperties();
}
}

После запуска нашего теста давайте проверим журналы, где мы узнаем, как Spring загрузил наш TodoRepository :

[2022-03-22 14:46:47,597]-[main] INFO  org.springframework.data.repository.config.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode.
[2022-03-22 14:46:47,737]-[main] TRACE org.springframework.data.repository.config.RepositoryConfigurationDelegate - Spring Data JPA - Registering repository: todoRepository - Interface: com.foreach.boot.bootstrapmode.repository.TodoRepository - Factory: org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
[2022-03-22 14:46:49,718]-[main] DEBUG org.springframework.data.repository.core.support.RepositoryFactorySupport - Initializing repository instance for com.foreach.boot.bootstrapmode.repository.TodoRepository…
[2022-03-22 14:46:49,792]-[main] DEBUG org.springframework.data.repository.core.support.RepositoryFactorySupport - Finished creation of repository instance for com.foreach.boot.bootstrapmode.repository.TodoRepository.
[2022-03-22 14:46:49,858]-[main] INFO com.foreach.boot.bootstrapmode.BootstrapmodeDefaultIntegrationTest - Started BootstrapmodeDefaultIntegrationTest in 3.547 seconds (JVM running for 4.877)

В нашем примере мы заранее инициализируем репозитории и делаем их доступными после запуска приложения .

2.2. Ленивый

Используя ленивый BootstrapMode для репозиториев JPA, Spring регистрирует определение bean-компонента нашего репозитория, но не создает его экземпляр сразу. Таким образом, при использовании ленивой опции первое использование запускает ее инициализацию .

Давайте изменим наш тест и применим опцию lazy к bootstrapMode :

@DataJpaTest(bootstrapMode = BootstrapMode.LAZY)

Затем давайте запустим наш тест с нашей новой конфигурацией и проверим соответствующие журналы:

[2022-03-22 15:09:01,360]-[main] INFO  org.springframework.data.repository.config.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in LAZY mode.
[2022-03-22 15:09:01,398]-[main] TRACE org.springframework.data.repository.config.RepositoryConfigurationDelegate - Spring Data JPA - Registering repository: todoRepository - Interface: com.foreach.boot.bootstrapmode.repository.TodoRepository - Factory: org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
[2022-03-22 15:09:01,971]-[main] INFO com.foreach.boot.bootstrapmode.BootstrapmodeLazyIntegrationTest - Started BootstrapmodeLazyIntegrationTest in 1.299 seconds (JVM running for 2.148)
[2022-03-22 15:09:01,976]-[main] DEBUG org.springframework.data.repository.config.RepositoryConfigurationDelegate$LazyRepositoryInjectionPointResolver - Creating lazy injection proxy for com.foreach.boot.bootstrapmode.repository.TodoRepository…
[2022-03-22 15:09:02,588]-[main] DEBUG org.springframework.data.repository.core.support.RepositoryFactorySupport - Initializing repository instance for com.foreach.boot.bootstrapmode.repository.TodoRepository…

Здесь следует обратить внимание на пару недостатков:

  • Spring может начать принимать запросы без инициализации репозиториев, тем самым увеличивая задержку при обработке первых запросов.
  • Настройка BootstrapMode на ленивый глобально подвержена ошибкам. Spring не будет проверять запросы и метаданные, содержащиеся в репозиториях, которые не включены в наши тесты.

Мы должны использовать ленивую загрузку только во время разработки, чтобы избежать развертывания приложения в рабочей среде с потенциальной ошибкой инициализации . Для этой цели мы можем элегантно использовать профили Spring .

2.3. Отложено

Deferred — правильный вариант для использования при асинхронной загрузке JPA. В результате репозитории не ждут инициализации EntityManagerFactory .

Давайте объявим AsyncTaskExecutor в классе конфигурации с помощью ThreadPoolTaskExecutor — одной из его реализаций Spring — и переопределим метод submit , который возвращает Future :

@Bean
AsyncTaskExecutor delayedTaskExecutor() {
return new ThreadPoolTaskExecutor() {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(() -> {
Thread.sleep(5000);
return task.call();
});
}
};
}

Затем давайте добавим bean-компонент EntityManagerFactory в нашу конфигурацию, как показано в нашем Руководстве по JPA с Spring , и укажем, что мы хотим использовать наш асинхронный исполнитель для фоновой начальной загрузки:

@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, AsyncTaskExecutor delayedTaskExecutor) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();

factory.setPackagesToScan("com.foreach.boot.bootstrapmode");
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
factory.setDataSource(dataSource);
factory.setBootstrapExecutor(delayedTaskExecutor);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "create-drop");
factory.setJpaPropertyMap(properties);
return factory;
}

Наконец, давайте изменим наш тест, чтобы включить режим отложенной загрузки:

@DataJpaTest(bootstrapMode = BootstrapMode.DEFERRED)

Снова запустим наш тест и проверим логи:

[2022-03-23 10:31:16,513]-[main] INFO  org.springframework.data.repository.config.RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFERRED mode.
[2022-03-23 10:31:16,543]-[main] TRACE org.springframework.data.repository.config.RepositoryConfigurationDelegate - Spring Data JPA - Registering repository: todoRepository - Interface: com.foreach.boot.bootstrapmode.repository.TodoRepository - Factory: org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
[2022-03-23 10:31:16,545]-[main] DEBUG org.springframework.data.repository.config.RepositoryConfigurationDelegate - Registering deferred repository initialization listener.
[2022-03-23 10:31:17,108]-[main] INFO org.springframework.data.repository.config.DeferredRepositoryInitializationListener - Triggering deferred initialization of Spring Data repositories…
[2022-03-23 10:31:22,538]-[main] DEBUG org.springframework.data.repository.core.support.RepositoryFactorySupport - Initializing repository instance for com.foreach.boot.bootstrapmode.repository.TodoRepository…
[2022-03-23 10:31:22,572]-[main] INFO com.foreach.boot.bootstrapmode.BootstrapmodeDeferredIntegrationTest - Started BootstrapmodeDeferredIntegrationTest in 6.769 seconds (JVM running for 7.519)

В этом примере завершение начальной загрузки контекста приложения запускает инициализацию репозиториев. Короче говоря, Spring помечает репозитории как ленивые и регистрирует bean-компонент DeferredRepositoryInitializationListener . Когда ApplicationContext запускает ContextRefreshedEvent , он инициализирует все репозитории.

Поэтому Spring Data инициализирует репозитории и проверяет включенные в них запросы и метаданные перед запуском приложения .

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

В этой статье мы рассмотрели различные способы инициализации репозиториев JPA и в каких случаях их использовать .

Как обычно, все примеры кода, использованные в этой статье, можно найти на GitHub .