BootstrapMode for JPA Repositories – JPA存储库的BootstrapMode

最后修改: 2022年 4月 10日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

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.

在这个简短的教程中,我们将重点介绍Spring为JPA存储库提供的不同类型的BootstrapMode,以改变其实例化的协调性

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.

在启动时,Spring Data会扫描存储库并将它们的bean定义注册为单子范围的bean。在初始化过程中,存储库会立即获得一个EntityManager。具体来说,它们会获得JPA元模型并验证已声明的查询。

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.

JPA默认是同步引导的。因此,存储库的实例化被阻止,直到引导过程完成。随着存储库数量的增加,应用程序在开始接受请求之前可能需要很长的时间来启动。

2. Different Options for Bootstrapping Repositories

2.引导储存库的不同选择

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:

让我们开始添加spring-data-jpa依赖项。由于我们使用的是Spring Boot,我们将使用相应的spring-boot-starter-data-jpa依赖性。

<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使用默认的版本库引导行为。

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注解,它被限制在一个单一的测试类中。

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

For the following examples, we’ll use the @DataJpaTest annotation and explore the different repository bootstrap options.

在下面的例子中,我们将使用@DataJpaTest注解并探索不同的资源库引导选项

2.1. Default

2.1 默认情况

The default value for bootstrap mode will instantiate repositories eagerly. Hence, like any other Spring beans, their initialization will occur when injected.

Bootstrap模式的默认值将急切地实例化资源库。因此,像任何其他Spring Bean一样,它们的初始化将在注入时发生

Let’s create a Todo entity:

让我们创建一个Todo实体。

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

接下来,我们将需要它的相关仓库。让我们创建一个扩展了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:

在开始测试后,让我们检查一下日志,我们会发现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.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

2.2.懒惰

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.

通过为JPA资源库使用懒惰的BootstrapMode,Spring注册了我们资源库的Bean定义,但不会立即将其实例化。因此,使用懒惰选项,第一次使用会触发其初始化

Let’s modify our test and apply the lazy option to bootstrapMode:

让我们修改我们的测试,将懒惰选项应用于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.

我们应该只在开发过程中使用懒惰引导,以避免在生产中部署应用程序时出现潜在的初始化错误。我们可以优雅地使用Spring Profiles来实现这一目的。

2.3. Deferred

2.3.递延

Deferred is the right option to use when bootstrapping JPA asynchronously. As a result, repositories don’t wait for the EntityManagerFactory‘s initialization.

在异步引导JPA时,延迟是正确的选择。因此,存储库不需要等待EntityManagerFactory的初始化

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:

让我们通过使用ThreadPoolTaskExecutor–其Spring的实现之一–在配置类中声明一个AsyncTaskExecutor,并重载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();
            });
        }
    };
}

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:

接下来,让我们将EntityManagerFactory Bean添加到我们的配置中,如我们的《JPA与Spring指南》所示,并指出我们希望使用我们的异步执行器来进行后台引导。

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

在这个例子中,应用程序上下文的引导完成会触发存储库的初始化。简而言之,Spring将存储库标记为懒惰,并注册了一个DeferredRepositoryInitializationListener bean。当ApplicationContext触发ContextRefreshedEvent时,它将初始化所有存储库。

Therefore, Spring Data initializes repositories and validates their included queries and metadata before the application startup.

因此,Spring Data在应用程序启动前初始化存储库并验证其包含的查询和元数据

3. Conclusion

3.总结

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.

在这篇文章中,我们研究了初始化JPA存储库的各种方法,以及在哪些情况下使用它们
像往常一样,本文中使用的所有代码样本都可以在GitHub上找到over