当前位置: 首页 > 知识库问答 >
问题:

spring批处理JpaPagingItemReader在测试中没有EntityManager(NPE)

越嘉茂
2023-03-14

我有一个使用JPapagingItemReader的小型spring批处理作业。当我从命令行启动时,该作业运行良好,但当我要测试@stepscope组件JPapagingItemReader时,我得到一个NullPointerException。我不明白为什么这个工作能很好地工作,但是我不能根据文档测试它。

我只有一个简单的employee类,带有基本的JPA注释。

以下是我的作业配置类:

@Slf4j
@Configuration
@EnableBatchProcessing
public class PaySalaryJobConfiguration {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Autowired
    public PaySalaryJobConfiguration(
            JobBuilderFactory jobBuilderFactory,
            StepBuilderFactory stepBuilderFactory
    ) {
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
    }

    @Bean
    @StepScope
    public ItemProcessor<Employee, Employee> employeeProcessor() {
        return item -> {
            log.info("Process item: {}", item);
            return item;
        };
    }

    @Bean
    @StepScope
    public JpaPagingItemReader<Employee> someoneReader(
            EntityManagerFactory entityManagerFactory
    ) {
        return new JpaPagingItemReaderBuilder<Employee>()
                .name("someone-reader")
                .entityManagerFactory(entityManagerFactory)
                .queryString("Select e from Employee e order by e.id asc")
                .pageSize(1)
                .build();
    }

    @Bean
    @StepScope
    public FlatFileItemWriter<Employee> csvWriter(
            @Value("#{jobParameters['output.path.csv']}") String outputPath
    ) {
        DelimitedLineAggregator<Employee> lineAggregator = new DelimitedLineAggregator<>();
        lineAggregator.setDelimiter(",");

        BeanWrapperFieldExtractor<Employee> fieldExtractor = new BeanWrapperFieldExtractor<>();
        fieldExtractor.setNames(new String[]{"id", "firstName", "lastName", "salary"});
        lineAggregator.setFieldExtractor(fieldExtractor);

        return new FlatFileItemWriterBuilder<Employee>()
                .name("csv-salary-writer")
                .resource(new FileSystemResource(outputPath))
                .lineAggregator(lineAggregator)
                .encoding("UTF-8")
                .build();
    }

    @Bean
    public Step writeSalarySlipToCsv (
            JpaPagingItemReader<Employee> someoneReader,
            FlatFileItemWriter<Employee> csvWriter
    ) {
        return stepBuilderFactory
                .get("retrieve-salary-slip-step")
                .<Employee, Employee>chunk(1)
                .reader(someoneReader)
                .processor(employeeProcessor())
                .writer(csvWriter)
                .stream(someoneReader)
                .build();
    }

    @Bean
    public Job paySalaryJob(
            Step writeSalarySlipToCsv
    ) {
        return jobBuilderFactory
                .get("pay-salary-job")
                .incrementer(new RunIdIncrementer())
                .start(writeSalarySlipToCsv)
                .build();
    }
}

下面是我的测试类:

@Slf4j
@SpringBatchTest
@RunWith(SpringRunner.class)
@EnableAutoConfiguration
@ContextConfiguration(classes = PaySalaryJobConfiguration.class)
public class StepScopeIntegrationTest {

    @Autowired
    private JpaPagingItemReader<Employee> someoneReader;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Before
    public void setUp() {
        log.info("Before execution we have {} entries", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EMPLOYEE", Integer.class ));
    }

    public StepExecution getStepExecution() {
        log.info("Step Execution !");
        StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution("retrieve-salary-slip-step", 1564L);
        log.info("Context = {} / Step = {} ", stepExecution.getExecutionContext(), stepExecution.getStepName());
        return stepExecution;
    }

    @Test
    public void testReader() throws Exception {
        log.info("Page = {}", someoneReader.getPage());
        log.info("PageSize = {}", someoneReader.getPageSize());
        Assert.assertNotNull(someoneReader.read());
    }
}

问题出现在第192行的JPAPagingItemReader类中:

@Override
@SuppressWarnings("unchecked")
protected void doReadPage() {

    EntityTransaction tx = null;

    if (transacted) {
        tx = entityManager.getTransaction(); // EntityManager is null...
        tx.begin();

        entityManager.flush();
        entityManager.clear();
    }//end if

下面是来自测试执行的stacktrace:

2019-06-12 17:17:32.845-info-[main]{b.g.t.StepScopeIntegrationTest 45}-->分步执行!2019-06-12 17:17:32.845-Info-[main]{B.G.T.StepScopeIntegrationTest 47}-->上下文={}/Step=检索-工资-工资单-步骤2019-06-12 17:17:32.970-Info-[main]{B.G.T.StepScopeIntegrationTest 41}-->在执行之前,我们有4个条目2019-06-12 17:17:33.002-Info-[main]{B.G.T.StepScopeIntegrationTest 53}-->页面=0 2019-06-12 17:17:33.002-Info-[main]{B.G.T.StepScopeIntegrationTest 54}

java.lang.NullPointerException(位于org.springframework.batch.item.database.jpapagingitemreader.doReadPage(位于org.springframework.batch.item.database.abstractpagingitemreader.java:192)(位于org.springframework.batch.item.database.abstractpagingitemreader.doRead(abstractpagingitemreader.java:108)(位于nvoke(DelegatingIntroductionInterceptor.java:124)位于org.springframework.aop.framework.cglibaopproxy$DynamicAdvisedInterceptor.Intercept(Cglibaopproxy.java:688)位于org.springframework.batch.item.database.jpapagingitemreader$$enhancerbyspringcglib$$b998315d.read()位于VA:47)位于org.junit.internal.runners.statements.invokeMethod.evaluate(invokeMethod.Java:17)位于org.springframework.test.context.junit4.statements.runbeforestexecutioncallbacks.evaluate(runbeforestexecutioncallbacks.Java:73)位于org.springframework.test.context.junit4.statements.runaftertestexecutioncallbacks.evaluatet.context.junit4.SpringJunit4ClassRunner.Runchild(SpringJunit4ClassRunner.:97)在org.junit.runners.ParentRunner$3。Run(ParentRunner.Java:290)在org.junit.runners.ParentRunner.Runchildre(ParentRunner.Java:290)在org.junit.runners.ParentRunner.Runchildre(ParentRunner.Java:288)在org.junit.runners.ParentRunner.Access$000(.rt.execution.junit.IdeateStrunner$repeater.startrunnerWithArgs(ideateStrunner.java:47)位于com.intellij.rt.execution.junit.junitstarter.PrepareStreamsandStart(junitstarter.java:242)位于com.intellij.rt.execution.junit.junit.main(junitstarter.java:70)

我使用的是h2数据库,在启动时我用import.sql和schema-h2.html" target="_blank">sql脚本提供的数据填充数据库。我正在使用spring-boot 2.0.5版和spring批处理核心4.1.2版。

共有2个答案

邹举
2023-03-14

这是不正确的。您在测试范围之外也会遇到同样的问题。正如本JIRA bug中所评论的,您需要定义bean来返回ItemStreamReader对象,而不是JPapAgingItemReader。

以下是吉米·普雷特的原话

没有调用writer的open()方法,因为writer没有注册为流。您的bean定义方法应该返回一个ItemStreamReader而不是ItemReader,这样它就会自动注册为一个流。

下面是参考https://jira.spring.io/si/jira.issueViews:issue-html/batch-2256/batch-2256.html

常元章
2023-03-14

正如Mahmoud Ben Hassine在评论中所建议的,我将测试类更改为:

@Slf4j
//@SpringBootTest
@SpringBatchTest
@RunWith(SpringRunner.class)
@EnableAutoConfiguration
@ContextConfiguration(classes = {
        PaySalaryJobConfiguration.class
})
@ActiveProfiles("test")
public class StepScopeIntegrationTest {

    @Autowired
    private JpaPagingItemReader<Employee> someoneReader;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private StepExecution stepExecution;

    @Before
    public void setUp() {
        log.info("Before execution we have {} entries", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EMPLOYEE", Integer.class ));
    }

    public StepExecution getStepExecution() {
        log.info("Step Execution !");
        stepExecution = MetaDataInstanceFactory.createStepExecution();
        log.info("Context = {} / Step = {} ", stepExecution.getExecutionContext(), stepExecution.getStepName());
        return stepExecution;
    }

    @Test
    public void testReader() throws Exception {
        log.info("Page = {}", someoneReader.getPage());
        log.info("PageSize = {}", someoneReader.getPageSize());
        someoneReader.open(stepExecution.getExecutionContext());
        Assert.assertNotNull(someoneReader.read());
    }
}

现在测试工作很好。

 类似资料:
  • 我使用JpaPagingItemReader和Spring批处理作业从数据库中读取数据。我有一个要求,我只能写10K记录到外部服务器在每次尝试。我将块大小和页面大小设置为10000。这是否有助于在每次尝试中只读取、处理和写入10K记录,直到JpaPagingItemReader以响应null耗尽并读取来自DB的所有数据为止?我混淆了Pagesize和setMaxItemCount值。哪种方法可以确

  • 我正在寻找一些关于测试Spring批处理步骤和步骤执行的一般性意见和建议。 我的基本步骤是从api读入数据,处理实体对象,然后写入数据库。我已经测试了快乐之路,这一步成功地完成了。我现在想做的是在处理器阶段数据丢失时测试异常处理。我可以单独测试processor类,但我更愿意测试整个步骤,以确保在步骤/作业级别正确反映流程故障。 我已经阅读了spring批量测试指南,如果我是诚实的,我对它有点迷茫

  • 我今天好像赢不了... 有没有一种方法可以从Spock SpringBootTest集成测试中的关系中读取,而无需将测试注释为或添加不切实际的? 或者,是否有方法从测试用例中启动Spring-Batch作业? 让我详细说明... 我试图为我的Spring Batch reporting流程提供一个简单的Spring Boot Integration测试,该测试从Dangled web of DB2

  • 背景: 我目前正在开发一个应用程序,该应用程序使用Spring Batch读取一组文件并将文件的内容缓存在内存中。缓存的数据然后由划分为不同类的业务逻辑层使用,每个类通过使用缓存中的数据执行特定的业务功能。 要求: 我被分配了对业务逻辑层进行单元测试的任务。由于业务逻辑层假设数据将在缓存中可用,所以在测试业务逻辑类之前,我需要一种在单元测试中触发Spring批处理作业的方法。 问题是: 我正在考虑

  • 我正在尝试为 Spring 启动应用程序中的控制器编写单元测试。该应用程序运行顺利,我的问题是运行其测试。 下面是测试代码: 当我尝试运行它时,我会得到: 这对我来说感觉很奇怪,因为我提供了

  • 我的阅读器配置如下: 另外,在读取器进入测试方法之前,不调用open(executionContext)方法,这是不对的,因为我使用这些作业参数来检索调用读取方法时需要可用的一些数据。这可能与上面的问题有关,因为关于使用后期绑定进行测试的文档说“读取器被初始化并绑定到输入数据”。