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 .