【问题标题】:Spring Batch JpaPagingItemReader has no EntityManager in tests (NPE)Spring Batch JpaPagingItemReader 在测试中没有 EntityManager (NPE)
【发布时间】:2019-10-27 04:18:11
【问题描述】:

我有一个使用 JpaPagingItemReader 的小型 Spring Batch 作业。当我从命令行启动时,这项工作运行良好,但是当我想测试@StepScope 组件JpaPagingItemReader 时,我得到一个NullPointerException。我不明白为什么这项工作运作良好,但我无法根据文档对其进行测试。

我只有一个简单的Employee 类和基本的JPAannotations。

这是我的作业配置类:

@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

这是测试执行的堆栈跟踪:

2019-06-12 17:17:32.845-信息-[主要]{ b.g.t.StepScopeIntegrationTest 45 } --> 步骤执行! 2019-06-12 17:17:32.845 - 信息 - [主] { b.g.t.StepScopeIntegrationTest 47 } --> Context = {} / Step = retrieve-salary-slip-step 2019-06-12 17:17:32.970 - 信息 - [主] { b.g.t.StepScopeIntegrationTest 41 } --> 在执行之前,我们有 4 个条目 2019-06-12 17:17:33.002 - 信息 - [主] { b.g.t.StepScopeIntegrationTest 53 } --> 页 = 0 2019-06-12 17:17:33.002 - 信息 - [主要] { b.g.t.StepScopeIntegrationTest 54 } --> PageSize = 1

java.lang.NullPointerException 在 org.springframework.batch.item.database.JpaPagingItemReader.doReadPage(JpaPagingItemReader.java:192) 在 org.springframework.batch.item.database.AbstractPagingItemReader.doRead(AbstractPagingItemReader.java:108) 在 org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.read(AbstractItemCountingItemStreamItemReader.java:89) 在 org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader$$FastClassBySpringCGLIB$$ebb633d0.invoke() 在 org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) 在 org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) 在 org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:136) 在 org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) 在 org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) 在 org.springframework.batch.item.database.JpaPagingItemReader$$EnhancerBySpringCGLIB$$b998315d.read() 在 be.groups.test.StepScopeIntegrationTest.testReader(StepScopeIntegrationTest.java:55) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73) 在 org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83) 在 org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) 在 org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) 在 org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) 在 org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) 在 org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) 在 org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 在 org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 在 org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 在 org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 在 org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) 在 org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:363) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) 在 org.junit.runner.JUnitCore.run(JUnitCore.java:137) 在 com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) 在 com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) 在 com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) 在 com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

我正在使用 h2 数据库,并在我的数据库中填充了 import.sql 和 schema-h2.sql 脚本提供的启动数据。我正在使用 Spring-Boot 2.0.5.RELEASE 和 Spring Batch Core 4.1.2.RELEASE。

【问题讨论】:

  • 看起来EntityManagerFactory 没有在您的测试类中正确设置(因此它没有注入阅读器)。您确定 JPA 配置是由 Spring Boot 加载的吗?您可以尝试添加@SpringBootTest 吗?两个旁注:1. 我不明白为什么你的阅读器应该是步进范围的,2. 在你的测试方法中调用 read 之前应该打开阅读器以兑现 ItemStream 合同。
  • @MahmoudBenHassine 非常感谢! 1)你是对的,读者不必被限定。 2)我首先通过调用ItemReader.open() 进行了测试,现在它可以工作了。也许您可以在文档中添加这一点?目前在文档中,我们有一个不需要调用 open() 方法的“ItemReader”测试的简单示例......
  • 太棒了!很高兴它有帮助。关于docs,有一个代码注释说“读者已初始化”,我认为这指的是open方法。然而,并不是所有的读者都实现了ItemStream,人们可以直接为这些读者调用read。在您的情况下,JpaPagingItemReader 是一个项目流,这就是为什么它需要在用于读取数据之前打开/初始化。 HTH。

标签: java jpa spring-batch h2


【解决方案1】:

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

这是 Jimmy Praet 的原始评论

你的 writer 的 open() 方法没有被调用,因为 writer 未注册为流。你的bean定义方法应该 返回一个 ItemStreamReader 而不是 ItemReader 以便它被注册 自动作为流。

这是参考 https://jira.spring.io/si/jira.issueviews:issue-html/BATCH-2256/BATCH-2256.html

【讨论】:

    【解决方案2】:

    按照 cmets 中 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());
        }
    }
    

    现在测试运行良好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-18
      • 1970-01-01
      • 1970-01-01
      • 2019-01-04
      • 2022-01-27
      • 2018-09-27
      • 2015-03-24
      相关资源
      最近更新 更多