1. Introduction
In this brief tutorial, we’ll focus on the different types of BootstrapMode
for JPA repositories that Spring provides for altering the orchestration of their instantiation.
At startup, Spring Data scans for repositories and registers their bean definitions as singleton-scoped beans. During their initialization, repositories obtain an EntityManager
immediately. Specifically, they get the JPA metamodel and validate declared queries.
JPA is bootstrapped synchronously by default. Consequently, the instantiation of repositories is blocked until the bootstrap process completes . As the number of repositories grows, the application could take a long time to start before it begins accepting requests.
2. Different Options for Bootstrapping Repositories
Let’s start by adding the spring-data-jpa
dependency. As we are using Spring Boot, we’ll use the corresponding spring-boot-starter-data-jpa dependency:
1
2
3
4
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
|
We can tell Spring to use the default repository bootstrap behavior via a configuration property:
1
|
spring.data.jpa.repositories.bootstrap-mode=default
|
We can do the same by using annotations-based configuration:
1
2
3
4
5
|
@SpringBootApplication
@EnableJpaRepositories(bootstrapMode = BootstrapMode.DEFAULT)
public class Application {
// ...
}
|
A third approach, restrained to a single test class, is to use the @DataJpaTest
annotation:
1
2
3
4
|
@DataJpaTest(bootstrapMode = BootstrapMode.LAZY)
class BootstrapmodeLazyIntegrationTest {
// ...
}
|
For the following examples, we’ll use the @DataJpaTest
annotation and explore the different repository bootstrap options .
2.1. Default
The default value for bootstrap mode will instantiate repositories eagerly. Hence, like any other Spring beans, their initialization will occur when injected .
Let’s create a Todo
entity:
1
2
3
4
5
6
7
8
|
@Entity
public class Todo {
@Id
private Long id;
private String label;
// standard setters and getters
}
|
Next, we’ll need its associated repository. Let’s create one that extends CrudRepository
:
1
2
|
public interface TodoRepository extends CrudRepository<Todo, Long> {
}
|
Finally, let’s add a test that uses our repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@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();
}
}
|
After starting our test, let’s check the logs where we’ll find out how Spring bootstrapped our TodoRepository
:
1
2
3
4
5
|
[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.baeldung.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.baeldung.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.baeldung.boot.bootstrapmode.repository.TodoRepository.
[2022-03-22 14:46:49,858]-[main] INFO com.baeldung.boot.bootstrapmode.BootstrapmodeDefaultIntegrationTest - Started BootstrapmodeDefaultIntegrationTest in 3.547 seconds (JVM running for 4.877)
|
In our example, we initialize repositories early and make them available once the application has started .
2.2. Lazy
By using the lazy BootstrapMode
for JPA repositories, Spring registers our repository’s bean definition but does not instantiate it right away. Thus, using the lazy option, the first use triggers its initialization.
Let’s modify our test and apply the lazy option to bootstrapMode :
1
|
@DataJpaTest(bootstrapMode = BootstrapMode.LAZY)
|
Then, let’s launch our test with our fresh configuration, and check the corresponding logs:
1
2
3
4
5
|
[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.baeldung.boot.bootstrapmode.repository.TodoRepository - Factory: org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
[2022-03-22 15:09:01,971]-[main] INFO com.baeldung.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.baeldung.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.baeldung.boot.bootstrapmode.repository.TodoRepository…
|
We should pay attention to a couple of drawbacks here:
- Spring might start accepting requests without having initialized repositories, thereby increasing latency when the first ones are handled.
- Setting up BootstrapMode to lazy globally is error-prone. Spring will not validate queries and metadata contained in repositories that are not included in our tests.
We should use lazy bootstrapping only during development to avoid deploying an application in production with a potential initialization error . We can elegantly use Spring Profiles for this purpose.
2.3. Deferred
Deferred is the right option to use when bootstrapping JPA asynchronously. As a result, repositories don’t wait for the EntityManagerFactory
’s initialization .
Let’s declare an AsyncTaskExecutor
in a configuration class by using ThreadPoolTaskExecutor
- one of its Spring implementations - and override the submit method, which returns a Future:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Bean
AsyncTaskExecutor delayedTaskExecutor() {
return new ThreadPoolTaskExecutor() {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(() -> {
Thread.sleep(5000);
return task.call();
});
}
};
}
|
Next, let’s add an EntityManagerFactory
bean to our configuration as shown in our Guide to JPA with Spring, and indicate that we want to use our asynchronous executor for background bootstrapping:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, AsyncTaskExecutor delayedTaskExecutor) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setPackagesToScan("com.baeldung.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;
}
|
Finally, let’s modify our test to enable deferred bootstrap mode:
1
|
@DataJpaTest(bootstrapMode = BootstrapMode.DEFERRED)
|
Let’s launch our test again and check the logs:
1
2
3
4
5
6
|
[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.baeldung.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.baeldung.boot.bootstrapmode.repository.TodoRepository…
[2022-03-23 10:31:22,572]-[main] INFO com.baeldung.boot.bootstrapmode.BootstrapmodeDeferredIntegrationTest - Started BootstrapmodeDeferredIntegrationTest in 6.769 seconds (JVM running for 7.519)
|
In this example, the application context bootstrap completion triggers the repositories’ initialization. In short, Spring marks repositories as lazy and registers a DeferredRepositoryInitializationListener
bean. When the ApplicationContext fires a ContextRefreshedEvent
, it initializes all repositories.
Therefore, Spring Data initializes repositories and validates their included queries and metadata before the application startup.
3. Conclusion
In this article, we looked at various ways to initialize JPA repositories and in which cases to use them .
As usual, all the code samples used in this article can be found over on GitHub.
Reference https://www.baeldung.com/jpa-bootstrap-mode