项目处理

The ItemReader 和 ItemWriter 接口 都非常适合其特定任务,但是如果您想在写入之前插入业务逻辑该怎么办?对于读取和写入,一个选项是使用组合模式:创建一个包含另一个 ItemWriterItemWriter 或一个包含另一个 ItemReaderItemReader。以下代码显示了一个示例

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

    ItemWriter<T> itemWriter;

    public CompositeItemWriter(ItemWriter<T> itemWriter) {
        this.itemWriter = itemWriter;
    }

    public void write(Chunk<? extends T> items) throws Exception {
        //Add business logic here
       itemWriter.write(items);
    }

    public void setDelegate(ItemWriter<T> itemWriter){
        this.itemWriter = itemWriter;
    }
}

前面的类包含另一个 ItemWriter,在提供了一些业务逻辑后,它会委托给该类。此模式也可以轻松地用于 ItemReader,例如,根据主要 ItemReader 提供的输入获取更多参考数据。如果您需要自己控制对 write 的调用,它也很有用。但是,如果您只想在实际写入之前“转换”传入的项目,则无需自己进行 write。您可以只修改项目。对于这种情况,Spring Batch 提供了 ItemProcessor 接口,如下面的接口定义所示

public interface ItemProcessor<I, O> {

    O process(I item) throws Exception;
}

一个 ItemProcessor 很简单。给定一个对象,对其进行转换并返回另一个对象。提供的对象类型可能相同,也可能不同。关键是可以在处理过程中应用业务逻辑,并且完全由开发人员创建该逻辑。ItemProcessor 可以直接连接到步骤。例如,假设 ItemReader 提供一个类型为 Foo 的类,并且在写入之前需要将其转换为类型 Bar。以下示例显示了一个执行转换的 ItemProcessor

public class Foo {}

public class Bar {
    public Bar(Foo foo) {}
}

public class FooProcessor implements ItemProcessor<Foo, Bar> {
    public Bar process(Foo foo) throws Exception {
        //Perform simple transformation, convert a Foo to a Bar
        return new Bar(foo);
    }
}

public class BarWriter implements ItemWriter<Bar> {
    public void write(Chunk<? extends Bar> bars) throws Exception {
        //write bars
    }
}

在前面的示例中,有一个名为 Foo 的类,一个名为 Bar 的类,以及一个名为 FooProcessor 的类,它符合 ItemProcessor 接口。转换很简单,但这里可以执行任何类型的转换。BarWriter 写入 Bar 对象,如果提供任何其他类型则抛出异常。类似地,如果提供除 Foo 之外的任何内容,则 FooProcessor 也会抛出异常。然后,可以将 FooProcessor 注入到 Step 中,如下例所示

  • Java

  • XML

Java 配置
@Bean
public Job ioSampleJob(JobRepository jobRepository, Step step1) {
	return new JobBuilder("ioSampleJob", jobRepository)
				.start(step1)
				.build();
}

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<Foo, Bar>chunk(2, transactionManager)
				.reader(fooReader())
				.processor(fooProcessor())
				.writer(barWriter())
				.build();
}
XML 配置
<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="fooProcessor" writer="barWriter"
                   commit-interval="2"/>
        </tasklet>
    </step>
</job>

ItemProcessorItemReaderItemWriter 之间的区别在于,ItemProcessor 对于 Step 是可选的。

链接 ItemProcessors

在许多情况下,执行单个转换很有用,但是如果您想将多个 ItemProcessor 实现“链接”在一起该怎么办?您可以使用前面提到的组合模式来做到这一点。为了更新之前的单个转换示例,Foo 转换为 Bar,然后转换为 Foobar 并写入,如下例所示

public class Foo {}

public class Bar {
    public Bar(Foo foo) {}
}

public class Foobar {
    public Foobar(Bar bar) {}
}

public class FooProcessor implements ItemProcessor<Foo, Bar> {
    public Bar process(Foo foo) throws Exception {
        //Perform simple transformation, convert a Foo to a Bar
        return new Bar(foo);
    }
}

public class BarProcessor implements ItemProcessor<Bar, Foobar> {
    public Foobar process(Bar bar) throws Exception {
        return new Foobar(bar);
    }
}

public class FoobarWriter implements ItemWriter<Foobar>{
    public void write(Chunk<? extends Foobar> items) throws Exception {
        //write items
    }
}

可以将 FooProcessorBarProcessor “链接”在一起以得到最终的 Foobar,如下例所示

CompositeItemProcessor<Foo,Foobar> compositeProcessor =
                                      new CompositeItemProcessor<Foo,Foobar>();
List itemProcessors = new ArrayList();
itemProcessors.add(new FooProcessor());
itemProcessors.add(new BarProcessor());
compositeProcessor.setDelegates(itemProcessors);

与前面的示例一样,您可以将组合处理器配置到 Step

  • Java

  • XML

Java 配置
@Bean
public Job ioSampleJob(JobRepository jobRepository, Step step1) {
	return new JobBuilder("ioSampleJob", jobRepository)
				.start(step1)
				.build();
}

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<Foo, Foobar>chunk(2, transactionManager)
				.reader(fooReader())
				.processor(compositeProcessor())
				.writer(foobarWriter())
				.build();
}

@Bean
public CompositeItemProcessor compositeProcessor() {
	List<ItemProcessor> delegates = new ArrayList<>(2);
	delegates.add(new FooProcessor());
	delegates.add(new BarProcessor());

	CompositeItemProcessor processor = new CompositeItemProcessor();

	processor.setDelegates(delegates);

	return processor;
}
XML 配置
<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="compositeItemProcessor" writer="foobarWriter"
                   commit-interval="2"/>
        </tasklet>
    </step>
</job>

<bean id="compositeItemProcessor"
      class="org.springframework.batch.item.support.CompositeItemProcessor">
    <property name="delegates">
        <list>
            <bean class="..FooProcessor" />
            <bean class="..BarProcessor" />
        </list>
    </property>
</bean>

过滤记录

项目处理器的典型用途之一是在将记录传递给 ItemWriter 之前将其过滤掉。过滤是一种与跳过不同的操作。跳过表示记录无效,而过滤表示不应写入记录。

例如,考虑一个批处理作业,该作业读取包含三种不同类型记录的文件:要插入的记录、要更新的记录和要删除的记录。如果系统不支持记录删除,则我们不希望将任何可删除的记录发送到 ItemWriter。但是,由于这些记录实际上不是错误的记录,因此我们希望将其过滤掉而不是跳过它们。因此,ItemWriter 将仅接收可插入和可更新的记录。

要过滤记录,您可以从 ItemProcessor 返回 null。框架检测到结果为 null 并避免将该项目添加到传递到 ItemWriter 的记录列表中。从 ItemProcessor 抛出的异常会导致跳过。

验证输入

ItemReaders 和 ItemWriters》章节讨论了多种解析输入的方法。每个主要的实现如果格式不“正确”,都会抛出一个异常。如果缺少数据范围,则FixedLengthTokenizer会抛出异常。类似地,尝试访问RowMapperFieldSetMapper中不存在的索引,或者索引格式与预期格式不同,也会导致抛出异常。所有这些类型的异常都在read返回之前抛出。但是,它们并没有解决返回的项是否有效的问题。例如,如果某个字段是年龄,则它不能为负数。它可能会被正确解析,因为它存在并且是一个数字,但不会导致异常。由于已经存在大量的验证框架,Spring Batch 并没有试图提供另一个验证框架。相反,它提供了一个简单的接口,称为Validator,您可以使用任意数量的框架来实现它,如下面的接口定义所示。

public interface Validator<T> {

    void validate(T value) throws ValidationException;

}

约定是,如果对象无效,则validate方法会抛出异常;如果对象有效,则正常返回。Spring Batch 提供了一个ValidatingItemProcessor,如下面的 Bean 定义所示。

  • Java

  • XML

Java 配置
@Bean
public ValidatingItemProcessor itemProcessor() {
	ValidatingItemProcessor processor = new ValidatingItemProcessor();

	processor.setValidator(validator());

	return processor;
}

@Bean
public SpringValidator validator() {
	SpringValidator validator = new SpringValidator();

	validator.setValidator(new TradeValidator());

	return validator;
}
XML 配置
<bean class="org.springframework.batch.item.validator.ValidatingItemProcessor">
    <property name="validator" ref="validator" />
</bean>

<bean id="validator" class="org.springframework.batch.item.validator.SpringValidator">
	<property name="validator">
		<bean class="org.springframework.batch.samples.domain.trade.internal.validator.TradeValidator"/>
	</property>
</bean>

您还可以使用BeanValidatingItemProcessor来验证使用 Bean Validation API (JSR-303) 注解注释的项。例如,考虑以下类型Person

class Person {

    @NotEmpty
    private String name;

    public Person(String name) {
     this.name = name;
    }

    public String getName() {
     return name;
    }

    public void setName(String name) {
     this.name = name;
    }

}

您可以通过在应用程序上下文中声明一个BeanValidatingItemProcessor Bean 并将其注册为分块导向步骤中的处理器来验证项。

@Bean
public BeanValidatingItemProcessor<Person> beanValidatingItemProcessor() throws Exception {
    BeanValidatingItemProcessor<Person> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
    beanValidatingItemProcessor.setFilter(true);

    return beanValidatingItemProcessor;
}

容错

当一个分块回滚时,在读取期间缓存的项可能会被重新处理。如果一个步骤被配置为容错(通常是通过使用跳过或重试处理),则使用的任何ItemProcessor都应该以幂等的方式实现。通常,这包括对ItemProcessor的输入项不执行任何更改,并且只更新作为结果的实例。