You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Batch如何避免无数据异常时创建输出文件?

Spring Batch 实现无异常数据时不生成输出文件

问题背景

我们有一个用于检查数据库数据的Spring Batch批处理作业:当数据存在异常时,向输出文件写入内容;无异常时则不写入。但目前无论数据是否异常,输出文件都会被创建,我们希望在数据正常时不生成输出文件。

当前配置XML

<batch:job id="DEFAULT_JOB_ID">
    <batch:step id="step">
        <batch:tasklet transaction-manager="transactionManager" start-limit="100">
            <batch:chunk reader="dbReader01" processor="testProc" writer="classifierWriter01" commit-interval="100" >
                <batch:streams>
                    <batch:stream ref="fileWriter01"/>
                </batch:streams>
            </batch:chunk>
            <batch:listeners>
                <batch:listener ref="defaultChunkListener" />
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
    <batch:listeners>
        <batch:listener ref="defaultJobExecutionListener" />
    </batch:listeners>
</batch:job>

<bean id="classifierWriter01" class="org.springframework.batch.item.support.ClassifierCompositeItemWriter" scope="step">
    <property name="classifier">
        <bean class="org.springframework.classify.BackToBackPatternClassifier">
            <property name="routerDelegate">
                <bean class="src.test.DefaultClassifier" />
            </property>
            <property name="matcherMap">
                <util:map id="matcherMap" scope="step">
                    <!-- FLAG == 0 THEN write something -->
                    <entry key="0" value-ref="fileWriter01" />
                    <!-- FLAG == 1 THEN write nothing -->
                    <entry key="1" value-ref="dummyWriter" />
                </util:map>
            </property>
        </bean>
    </property>
</bean>

<bean id="fileWriter01" class="org.springframework.batch.item.file.FlatFileItemWriter"   scope="step">
    <property name="encoding" value="UTF-8" />
    <property name="resource" value="file:#{jobParameters['outFile']}" />
    <property name="appendAllowed" value="false"/>
    <property name="shouldDeleteIfExists" value="true"/>
    <property name="lineAggregator">
        <bean class="src.test.DelimitedLineAggregator">
            <property name="delimiter" value="," />
            <property name="allEnclosing" value="true" />
            <property name="enclosure" value='"' />
            <property name="fieldExtractor">
                <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor" >
                    <property name="names"
                        value="seq,errCode,errMsg" />
                </bean>
            </property>
        </bean>
    </property>
</bean>
        
<bean id="dummyWriter" class="src.test.DummyItemWriter" scope="step"/>

核心问题

使用batch:streams配置fileWriter01时,输出文件会在作业启动时就被创建;如果不配置batch:streams,又会抛出异常:org.springframework.batch.item.WriterNotOpenException: Writer must be open before it can be written to。我们需要实现仅当有异常数据时才生成输出文件。

解决方案

1. 自定义延迟加载Writer(推荐)

不要把fileWriter01放到batch:streams中,而是自定义一个包装Writer,仅当有数据要写入时才初始化并打开FlatFileItemWriter,避免提前创建文件。

自定义Writer代码:

public class LazyFlatFileItemWriter<T> implements ItemWriter<T> {

    private FlatFileItemWriter<T> delegate;
    private boolean isOpen = false;

    @Override
    public void write(List<? extends T> items) throws Exception {
        if (items == null || items.isEmpty()) {
            return;
        }
        if (!isOpen) {
            delegate.open(new ExecutionContext());
            isOpen = true;
        }
        delegate.write(items);
    }

    @PreDestroy
    public void close() throws Exception {
        if (isOpen) {
            delegate.close();
        }
    }

    // 注入委托Writer的setter方法
    public void setDelegate(FlatFileItemWriter<T> delegate) {
        this.delegate = delegate;
    }
}

修改XML配置:

  • 添加自定义Writer的Bean定义:
<bean id="lazyFileWriter" class="src.test.LazyFlatFileItemWriter" scope="step">
    <property name="delegate" ref="fileWriter01" />
</bean>
  • 修改classifierWriter01的matcherMap,引用这个自定义Writer:
<util:map id="matcherMap" scope="step">
    <!-- FLAG == 0 THEN write something -->
    <entry key="0" value-ref="lazyFileWriter" />
    <!-- FLAG == 1 THEN write nothing -->
    <entry key="1" value-ref="dummyWriter" />
</util:map>
  • 删除batch:streams配置块:
<!-- 移除这段配置 -->
<batch:streams>
    <batch:stream ref="fileWriter01"/>
</batch:streams>

2. 作业结束后清理空文件(简单方案)

保留现有配置,在Job执行完成后检查输出文件,若为空则删除。通过自定义JobExecutionListener实现:

监听器代码:

public class CleanEmptyFileListener implements JobExecutionListener {

    private Resource outputResource;

    @Override
    public void beforeJob(JobExecution jobExecution) {}

    @Override
    public void afterJob(JobExecution jobExecution) {
        try {
            File file = outputResource.getFile();
            if (file.exists() && file.length() == 0) {
                file.delete();
            }
        } catch (IOException e) {
            // 日志记录异常,根据实际情况处理
            e.printStackTrace();
        }
    }

    // 设置输出资源的setter方法
    public void setOutputResource(Resource outputResource) {
        this.outputResource = outputResource;
    }
}

修改XML配置:

  • 添加监听器Bean:
<bean id="cleanEmptyFileListener" class="src.test.CleanEmptyFileListener" scope="step">
    <property name="outputResource" value="file:#{jobParameters['outFile']}" />
</bean>
  • 将监听器添加到Job的监听器列表:
<batch:job id="DEFAULT_JOB_ID">
    <!-- 原有Step配置 -->
    <batch:listeners>
        <batch:listener ref="defaultJobExecutionListener" />
        <batch:listener ref="cleanEmptyFileListener" />
    </batch:listeners>
</batch:job>

关键说明

  • FlatFileItemWriter本身可以自行创建文件,但默认在open()方法中执行创建操作。通过batch:streams配置会让Spring Batch在Step启动时就调用所有stream中Writer的open()方法,导致文件提前创建。
  • 去掉batch:streams后,必须确保Writer在第一次写入前被正确打开,自定义延迟Writer就是通过延迟调用open()方法实现按需创建文件。

内容的提问来源于stack exchange,提问作者l4ru3

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 04:17:05