【问题标题】:Spring Batch Reader Writer transfer of data not working as expectedSpring Batch Reader Writer 数据传输未按预期工作
【发布时间】:2021-08-12 16:09:09
【问题描述】:

我创建了一个通用 Spring Batch 作业,用于处理数据并将其存储到 CSV 中。我需要一些来自阅读器的数据传递给我正在尝试使用 JobExecution 执行的编写器。然而令人惊讶的是,代码似乎首先调用 getWriter() 而不是 getReader() 函数。 我的配置如下。有人可以解释为什么会发生这种情况,以及是否有其他方法可以将数据从读取器传递到写入器。

@Bean
@StepScope
public ItemReader<Map<String, Object>> getDataReader() throws Exception {
    return springBatchReader.getReader();
}

@Bean
@StepScope
public FlatFileItemWriter<Map<String, Object>> getDataWriter() throws Exception {
    return (FlatFileItemWriter<Map<String, Object>>) springBatchWriter.getWriter();
}

@Bean
public Job SpringBatchJob(Step generateReport) throws Exception {
    return jobBuilderFactory.get("SpringBatchJob" + System.currentTimeMillis())
            .preventRestart()
            .incrementer(new RunIdIncrementer())
            .flow(generateReport)
            .end()
            .build();
}

@Bean
public Step generateReport() throws Exception {
    return stepBuilderFactory.get("generateReport").<Map<String, Object>, Map<String, Object>>chunk(batchSize)
            .reader(getDataReader()).writer(getDataWriter()).build();
}

我想从 Reader 传递给 Writer 的数据是 CSV 的列名。 由于我的阅读器运行可变 SQL 查询(传递 SQL 查询以作为命令行参数运行),因此结果集/列不是静态的,而是根据给定的查询而变化。在 setHeaderCallback 中为编写者提供要为特定执行写入的列名是从 Reader 向 Writer 发送数据的基本原理。

Reader 简单地运行给定的查询并将数据放入 Map 而不是任何 POJO,因为数据的可变性。这里 Map 的键代表列名,而相应的对象保存该列的值。所以本质上我希望编写器 setHeaderCallback 能够访问传递的 Map 的密钥或以某种方式将密钥从 Reader 传递给 Writer。

编写器代码如下:

public FlatFileItemWriter<Map<String, Object>> getWriter() throws Exception {
        String reportName = getReportName();
        saveToContext(reportName, reportPath);

        FileSystemResource resource = new FileSystemResource(String.join(File.separator, reportPath, getReportName()));

        FlatFileItemWriter<Map<String, Object>> flatFileItemWriter = new FlatFileItemWriter<>();
        flatFileItemWriter.setResource(resource);
//NEED HELP HERE..HOW TO SET THE HEADER TO BE THE KEYS OF THE MAP
//flatFileItemWriter.setHeaderCallback();
        flatFileItemWriter.setLineAggregator(new DelimitedLineAggregator<Map<String, Object>>() {

            {
                setDelimiter(delimiter);
                setFieldExtractor(
                        new PassThroughFieldExtractor<>()
                );
            }
        });
        flatFileItemWriter.afterPropertiesSet();
        return flatFileItemWriter;
    } 

【问题讨论】:

    标签: java spring spring-batch batch-processing


    【解决方案1】:

    这些方法的执行顺序无关紧要。您不应该寻找一种使用执行上下文将数据从读取器传递到写入器的方法,Spring Batch 提供的面向块的 Tasklet 实现将为您完成。

    执行上下文可用于将数据从一个步骤传递到另一个步骤,但不能在同一步骤中从读取器传递到写入器。

    编辑:根据 cmets 更新答案:

    您的问题是您在 getWriter 方法中调用 saveToContext(reportName, reportPath);。此方法在配置时调用,而不是在运行时调用。

    您真正需要的是通过作业参数提供列名,或者通过步骤将它们放入执行上下文中,然后使用配置了这些标题的步骤范围的 Header 回调。

    您可以在此处找到示例:https://stackoverflow.com/a/56719077/5019386。此示例适用于 lineMapper,但您可以对 headerCallback 执行相同操作。如果您不想使用作业参数方法,您可以创建一个 tasklet 步骤来确定列名并将它们放入执行上下文中,然后使用执行上下文中的这些名称配置步骤范围的标头回调,例如:

    @Bean
    @StepScope
    public FlatFileHeaderCallback headerCallback(@Value("#{jobExecutionContext['columnNames']}") String columnNames) {
        return new FlatFileHeaderCallback() {
            @Override
            public void writeHeader(Writer writer) throws IOException {
                // use columnNames here
            }
        };
    }
    

    【讨论】:

    • 谢谢马哈茂德。但是,您能否指导一下如何将面向块的 Tasklet 用于此目的(更新我在问题中尝试执行的操作的详细信息。)我在网上找到的资源对于手头的任务似乎有点混乱。谢谢!
    • 您已经在使用面向块的 tasklet。所以它将处理从读者到作者的传递项目。您可以查看implementation if you want,但本质上,项目是通过一个变量传递的:Chunk inputs = chunkProvider.provide(); chunkProcessor.process(inputs);
    • 更新了用例详细说明了问题 Mahmoud。请您建议是否有任何替代方法可用,而无需明确传递数据。对 Spring-batch 相当陌生!
    • 感谢您的更新。最初的问题具有误导性,并且缺少有关您要求动态获取标题回调的列名的关键细节。你可以更新这个问题stackoverflow.com/questions/67477713/…,而不是创建一个新问题,我问你这些列名来自哪里。
    • 无论如何,没问题。我用更多细节和一个例子更新了答案。
    猜你喜欢
    • 1970-01-01
    • 2017-08-20
    • 2015-04-01
    • 1970-01-01
    • 2020-01-18
    • 2015-10-02
    • 2016-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多