简体   繁体   English

如何在 Spring 批量集成测试中使用自动装配的存储库?

[英]How to use autowired repositories in Spring Batch integration test?

I am facing some issues while writing integration tests for Spring Batch jobs.在为 Spring 批处理作业编写集成测试时,我遇到了一些问题。 The main problem is that an exception is thrown whenever a transaction is started inside the batch job.主要问题是,只要在批处理作业中启动事务,就会引发异常。
Well, first things first.嗯,第一件事。 Imagine this is the step of a simple job.想象一下这是一个简单工作的步骤。 A Tasklet for the sake of simplicity.为简单起见的Tasklet Of course, it is used in a proper batch config ( MyBatchConfig ) which I also omit for brevity.当然,它用于适当的批处理配置 ( 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 is a very unspecial CrudRepository . MyRepository是一个非常不特殊的CrudRepository

Now, to test that job I use the following test class.现在,为了测试该作业,我使用以下测试 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();
    }
}

When it comes to the tasklet execution and more precisely to the deleteAll() method call this exception is fired:当涉及到 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)
    ...

Do you have any ideas why this is happening?你知道为什么会这样吗?


As a workaround I currently mock the repository with @MockBean and back it with an ArrayList but this is not what the inventor intended, I guess.作为一种解决方法,我目前使用ArrayList模拟存储库并使用@MockBean支持它,但我猜这不是发明者的意图。

Any advice?有什么建议吗?
Kind regards亲切的问候


Update 1.1 (includes solution)更新 1.1(包括解决方案)
The mentioned data source configuration class is提到的数据源配置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();
            }
        };
    }
}

By default, when you declare a datasource in your application context, Spring Batch will use a DataSourceTransactionManager to drive step transactions, but this transaction manager knows nothing about your JPA context.默认情况下,当您在应用程序上下文中声明数据源时,Spring Batch 将使用DataSourceTransactionManager来驱动步骤事务,但此事务管理器对您的 JPA 上下文一无所知。

If you want to use another transaction manager, you need to override BatchConfigurer#getTransactionManager and return the transaction manager you want to use to drive step transactions.如果要使用另一个事务管理器,则需要覆盖BatchConfigurer#getTransactionManager并返回要用于驱动步骤事务的事务管理器。 In your case, you are only declaring a transaction manager bean in the application context which is not enough.在您的情况下,您只是在应用程序上下文中声明一个事务管理器 bean,这还不够。 Here a quick example:这里有一个简单的例子:

@Bean
public BatchConfigurer batchConfigurer() {
   return new DefaultBatchConfigurer() {
      @Override
      public PlatformTransactionManager getTransactionManager() {
          return new JpaTransactionManager(myTestEntityManagerFactory().getObject());
      }
   };
}

For more details, please refer to the reference documentation .有关详细信息,请参阅参考文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM