如何在 Spring 批量集成测试中使用自动装配的存储库?
How to use autowired repositories in Spring Batch integration test?
我在为 Spring 批处理作业编写集成测试时遇到了一些问题。主要问题是每当在批处理作业中启动事务时都会抛出异常。
好吧,首先是第一件事。想象这是一个简单工作的步骤。 Tasklet
为了简单起见。当然,它用于适当的批处理配置 (MyBatchConfig
),为简洁起见,我也将其省略。
@Component
public class SimpleTask implements Tasklet {
private final MyRepository myRepository;
public SimpleTask(MyRepository myRepository) {
this.myRepository = myRepository;
}
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
myRepository.deleteAll(); // or maybe saveAll() or some other @Transactional method
return RepeatStatus.FINISHED;
}
}
MyRepository
是一个很普通的CrudRepository
.
现在,为了测试该作业,我使用以下测试 class。
@SpringBatchTest
@EnableAutoConfiguration
@SpringJUnitConfig(classes = {
H2DataSourceConfig.class, // <-- this is a configuration bean for an in-memory testing database
MyBatchConfig.class
})
public class MyBatchJobTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@Autowired
private MyRepository myRepository;
@Test
public void testJob() throws Exception {
var testItems = List.of(
new MyTestItem(1),
new MyTestItem(2),
new MyTestItem(3)
);
myRepository.saveAll(testItems); // <--- works perfectly well
jobLauncherTestUtils.launchJob();
}
}
当涉及到 tasklet 执行时,更准确地说是 deleteAll()
方法调用时,会触发此异常:
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@68f48807] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@49a6f486] bound to thread [SimpleAsyncTaskExecutor-1]
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:448)
...
你知道为什么会这样吗?
作为一种解决方法,我目前使用 @MockBean
模拟存储库并使用 ArrayList
支持它,但我猜这不是发明者的意图。
有什么建议吗?
亲切的问候
更新 1.1(包括解决方案)
提到的数据源配置class是
@Configuration
@EnableJpaRepositories(
basePackages = {"my.project.persistence.repository"},
entityManagerFactoryRef = "myTestEntityManagerFactory",
transactionManagerRef = "myTestTransactionManager"
)
@EnableTransactionManagement
public class H2DataSourceConfig {
@Bean
public DataSource myTestDataSource() {
var dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean myTestEntityManagerFactory() {
var emFactory = new LocalContainerEntityManagerFactoryBean();
var adapter = new HibernateJpaVendorAdapter();
adapter.setDatabasePlatform("org.hibernate.dialect.H2Dialect");
adapter.setGenerateDdl(true);
emFactory.setDataSource(myTestDataSource());
emFactory.setPackagesToScan("my.project.persistence.model");
emFactory.setJpaVendorAdapter(adapter);
return emFactory;
}
@Bean
public PlatformTransactionManager myTestTransactionManager() {
return new JpaTransactionManager(myTestEntityManagerFactory().getObject());
}
@Bean
public BatchConfigurer testBatchConfigurer() {
return new DefaultBatchConfigurer() {
@Override
public PlatformTransactionManager getTransactionManager() {
return myTestTransactionManager();
}
};
}
}
默认情况下,当您在应用程序上下文中声明数据源时,Spring Batch 将使用 DataSourceTransactionManager
来驱动步骤事务,但此事务管理器对您的 JPA 上下文一无所知。
如果您想使用另一个事务管理器,您需要覆盖 BatchConfigurer#getTransactionManager
和 return 您想要用来驱动步骤事务的事务管理器。在您的情况下,您仅在应用程序上下文中声明一个事务管理器 bean 是不够的。这里有一个简单的例子:
@Bean
public BatchConfigurer batchConfigurer() {
return new DefaultBatchConfigurer() {
@Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(myTestEntityManagerFactory().getObject());
}
};
}
详情请参考reference documentation。
我在为 Spring 批处理作业编写集成测试时遇到了一些问题。主要问题是每当在批处理作业中启动事务时都会抛出异常。
好吧,首先是第一件事。想象这是一个简单工作的步骤。 Tasklet
为了简单起见。当然,它用于适当的批处理配置 (MyBatchConfig
),为简洁起见,我也将其省略。
@Component
public class SimpleTask implements Tasklet {
private final MyRepository myRepository;
public SimpleTask(MyRepository myRepository) {
this.myRepository = myRepository;
}
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
myRepository.deleteAll(); // or maybe saveAll() or some other @Transactional method
return RepeatStatus.FINISHED;
}
}
MyRepository
是一个很普通的CrudRepository
.
现在,为了测试该作业,我使用以下测试 class。
@SpringBatchTest
@EnableAutoConfiguration
@SpringJUnitConfig(classes = {
H2DataSourceConfig.class, // <-- this is a configuration bean for an in-memory testing database
MyBatchConfig.class
})
public class MyBatchJobTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@Autowired
private MyRepository myRepository;
@Test
public void testJob() throws Exception {
var testItems = List.of(
new MyTestItem(1),
new MyTestItem(2),
new MyTestItem(3)
);
myRepository.saveAll(testItems); // <--- works perfectly well
jobLauncherTestUtils.launchJob();
}
}
当涉及到 tasklet 执行时,更准确地说是 deleteAll()
方法调用时,会触发此异常:
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder@68f48807] for key [org.springframework.jdbc.datasource.DriverManagerDataSource@49a6f486] bound to thread [SimpleAsyncTaskExecutor-1]
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:448)
...
你知道为什么会这样吗?
作为一种解决方法,我目前使用 @MockBean
模拟存储库并使用 ArrayList
支持它,但我猜这不是发明者的意图。
有什么建议吗?
亲切的问候
更新 1.1(包括解决方案)
提到的数据源配置class是
@Configuration
@EnableJpaRepositories(
basePackages = {"my.project.persistence.repository"},
entityManagerFactoryRef = "myTestEntityManagerFactory",
transactionManagerRef = "myTestTransactionManager"
)
@EnableTransactionManagement
public class H2DataSourceConfig {
@Bean
public DataSource myTestDataSource() {
var dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean myTestEntityManagerFactory() {
var emFactory = new LocalContainerEntityManagerFactoryBean();
var adapter = new HibernateJpaVendorAdapter();
adapter.setDatabasePlatform("org.hibernate.dialect.H2Dialect");
adapter.setGenerateDdl(true);
emFactory.setDataSource(myTestDataSource());
emFactory.setPackagesToScan("my.project.persistence.model");
emFactory.setJpaVendorAdapter(adapter);
return emFactory;
}
@Bean
public PlatformTransactionManager myTestTransactionManager() {
return new JpaTransactionManager(myTestEntityManagerFactory().getObject());
}
@Bean
public BatchConfigurer testBatchConfigurer() {
return new DefaultBatchConfigurer() {
@Override
public PlatformTransactionManager getTransactionManager() {
return myTestTransactionManager();
}
};
}
}
默认情况下,当您在应用程序上下文中声明数据源时,Spring Batch 将使用 DataSourceTransactionManager
来驱动步骤事务,但此事务管理器对您的 JPA 上下文一无所知。
如果您想使用另一个事务管理器,您需要覆盖 BatchConfigurer#getTransactionManager
和 return 您想要用来驱动步骤事务的事务管理器。在您的情况下,您仅在应用程序上下文中声明一个事务管理器 bean 是不够的。这里有一个简单的例子:
@Bean
public BatchConfigurer batchConfigurer() {
return new DefaultBatchConfigurer() {
@Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(myTestEntityManagerFactory().getObject());
}
};
}
详情请参考reference documentation。