JPA Buddy
announcement - icon

JPA is huge! It covers nearly every aspect of communication between relational databases and the Java application and is deeply integrated into all major frameworks.

If you're using IntelliJ, JPA Buddy is super helpful. The plugin gently guides you through the subtleties of the most popular JPA implementations, visually reminds you of JPA features, generates code that follows best practices, and integrates intelligent inspections to improve your existing persistence code.

More concretely, it provides powerful tooling to generate Spring Data JPA repositories and methods, Flyway Versioned Migrations, Liquibase Differential Changelogs, DDL and SQL statements, DTO objects, and MapStruct interfaces.

Oh, and it actually generates JPA entities from an existing database and gradually update the data model as the database evolves! Yeah.

>> Become a lot more productive with JPA Buddy

Persistence top

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

>> CHECK OUT THE COURSE

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:

<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:

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

We can do the same by using annotations-based configuration:

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

A third approach, restrained to a single test class, is to use the @DataJpaTest annotation:

@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:

@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:

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

Finally, let's add a test that uses our repository:

@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:

[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:

@DataJpaTest(bootstrapMode = BootstrapMode.LAZY)

Then, let's launch our test with our fresh configuration, and check the corresponding logs:

[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:

@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:

@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:

@DataJpaTest(bootstrapMode = BootstrapMode.DEFERRED)

Let's launch our test again and check the logs:

[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.

Persistence bottom
Get started with Spring Data JPA through the reference Learn Spring Data JPA course: >> CHECK OUT THE COURSE
Persistence footer banner
Comments are closed on this article!