1. 概述
在这个简短的教程中,我们将重点讨论Spring为调整JPA仓库实例化方式提供的不同BootstrapMode类型。
在启动时,Spring Data会扫描仓库并注册它们的单例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. 默认模式
BootstrapMode的默认值会即时创建仓库。因此,就像其他Spring Bean一样,它们的初始化会在注入时发生。
让我们创建一个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.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)
在示例中,我们提前初始化仓库,并在应用启动后使其可用。
2.2. 懒惰模式
通过将JPA仓库的BootstrapMode设置为懒惰,Spring将注册仓库的bean定义,但不会立即实例化它。因此,使用懒惰选项,第一次使用会触发其初始化。
修改我们的测试并为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.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…
这里需要注意一些潜在问题:
- Spring可能会在未初始化仓库的情况下开始接收请求,这可能导致处理第一个请求时增加延迟。
- 全局设置BootstrapMode为懒惰容易出错。Spring将不验证未包含在测试中的仓库中的查询和元数据。
我们应该只在开发阶段使用懒加载,以避免在生产环境中部署可能带有初始化错误的应用程序。我们可以优雅地使用Spring Profile来达到这个目的。
2.3. 延迟模式
当异步启动JPA时,延迟模式是正确的选择。因此,仓库不需要等待EntityManagerFactory
的初始化。
在配置类中,我们声明一个AsyncTaskExecutor
,使用Spring实现之一的ThreadPoolTaskExecutor
,并重写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();
});
}
};
}
接下来,按照我们的Spring和JPA指南,向我们的配置添加一个EntityManagerFactory
bean,并指示我们希望使用异步执行器进行后台启动:
@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;
}
最后,修改我们的测试以启用延迟启动模式:
@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.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)
在这个例子中,应用上下文的启动完成触发仓库的初始化。简而言之,Spring将仓库标记为懒惰,并注册一个DeferredRepositoryInitializationListener
bean。当ApplicationContext
发出一个ContextRefreshedEvent
时,它会初始化所有仓库。
因此,Spring Data在应用启动前初始化仓库并验证其中包含的查询和元数据。
3. 总结
在这篇文章中,我们探讨了初始化JPA仓库的不同方式以及在哪些情况下使用它们。
如往常一样,本文中使用的所有代码示例都可以在GitHub上找到。