Spring Batch:一个阅读器,多个处理器和编写器

时间:2013-09-25 08:20:31

标签: spring spring-batch itemprocessor itemwriter

在Spring批处理中,我需要将ItemReader读取的项目传递给两个不同的处理器和编写器。我想要实现的是......

                        +---> ItemProcessor#1 ---> ItemWriter#1
                        |
ItemReader ---> item ---+
                        |
                        +---> ItemProcessor#2 ---> ItemWriter#2

这是必需的,因为与ItemWriter#2编写的项目相比,ItemWriter#1编写的项目应该以完全不同的方式处理。 此外,ItemReader从数据库中读取项目,并且它执行的查询计算成本非常高,因此应该丢弃两次执行相同的查询。

有关如何实现此类设置的任何提示?或者,至少,逻辑上等同的设置?

5 个答案:

答案 0 :(得分:9)

如果您的项目应由处理器#1和处理器#2

处理,则此解决方案有效

您必须使用此签名创建处理器#0:

class Processor0<Item, CompositeResultBean>

其中CompositeResultBean是定义为

的bean
class CompositeResultBean {
  Processor1ResultBean result1;
  Processor2ResultBean result2;
}

在您的Processor#0中,只需将工作委托给处理器#1和#2,并将结果放入CompositeResultBean

CompositeResultBean Processor0.process(Item item) {
  final CompositeResultBean r = new CompositeResultBean();
  r.setResult1(processor1.process(item));
  r.setResult2(processor2.process(item));
  return r;
}

您自己的作家是CompositeItemWriter代表作家CompositeResultBean.result1CompositeResultBean.result2(查看PropertyExtractingDelegatingItemWriter,也许可以提供帮助)

答案 1 :(得分:3)

我遵循Luca的建议,使用PropertyExtractingDelegatingItemWriter作为作家,我能够在一个步骤中与两个不同的实体合作。

首先,我所做的是定义一个存储来自处理器的两个实体/结果的DTO

public class DatabaseEntry {
    private AccessLogEntry accessLogEntry;
    private BlockedIp blockedIp;

    public AccessLogEntry getAccessLogEntry() {
        return accessLogEntry;
    }

    public void setAccessLogEntry(AccessLogEntry accessLogEntry) {
        this.accessLogEntry = accessLogEntry;
    }

    public BlockedIp getBlockedIp() {
        return blockedIp;
    }

    public void setBlockedIp(BlockedIp blockedIp) {
        this.blockedIp = blockedIp;
    }
}

然后我将这个DTO传递给了一个PropertyExtractingDelegatingItemWriter类的编写器,在那里我定义了两个自定义方法来将实体写入数据库,请参阅下面的编写器代码:

@Configuration
public class LogWriter extends LogAbstract {
    @Autowired
    private DataSource dataSource;

    @Bean()
    public PropertyExtractingDelegatingItemWriter<DatabaseEntry> itemWriterAccessLogEntry() {
        PropertyExtractingDelegatingItemWriter<DatabaseEntry> propertyExtractingDelegatingItemWriter = new PropertyExtractingDelegatingItemWriter<DatabaseEntry>();
        propertyExtractingDelegatingItemWriter.setFieldsUsedAsTargetMethodArguments(new String[]{"accessLogEntry", "blockedIp"});
        propertyExtractingDelegatingItemWriter.setTargetObject(this);
        propertyExtractingDelegatingItemWriter.setTargetMethod("saveTransaction");
        return propertyExtractingDelegatingItemWriter;
    }

    public void saveTransaction(AccessLogEntry accessLogEntry, BlockedIp blockedIp) throws SQLException {
        writeAccessLogTable(accessLogEntry);
        if (blockedIp != null) {
            writeBlockedIp(blockedIp);
        }

    }

    private void writeBlockedIp(BlockedIp entry) throws SQLException {
        PreparedStatement statement = dataSource.getConnection().prepareStatement("INSERT INTO blocked_ips (ip,threshold,startDate,endDate,comment) VALUES (?,?,?,?,?)");
        statement.setString(1, entry.getIp());
        statement.setInt(2, threshold);
        statement.setTimestamp(3, Timestamp.valueOf(startDate));
        statement.setTimestamp(4, Timestamp.valueOf(endDate));
        statement.setString(5, entry.getComment());
        statement.execute();
    }

    private void writeAccessLogTable(AccessLogEntry entry) throws SQLException {
        PreparedStatement statement = dataSource.getConnection().prepareStatement("INSERT INTO log_entries (date,ip,request,status,userAgent) VALUES (?,?,?,?,?)");
        statement.setTimestamp(1, Timestamp.valueOf(entry.getDate()));
        statement.setString(2, entry.getIp());
        statement.setString(3, entry.getRequest());
        statement.setString(4, entry.getStatus());
        statement.setString(5, entry.getUserAgent());
        statement.execute();
    }
}

通过这种方法,您可以从单个读取器获取所需的初始行为,以处理多个实体并将其保存在一个步骤中。

答案 2 :(得分:2)

您可以使用CompositeItemProcessorCompositeItemWriter

它看起来不像你的架构,它将是顺序的,但它会完成这项工作。

答案 3 :(得分:0)

如果您有合理数量的项目(例如少于1 Go),还有另一种解决方案:您可以将select的结果缓存到包含在Spring bean中的集合中。

然后你可以免费阅读该集合两次。

答案 4 :(得分:-1)

这是我提出的解决方案。

因此,我们的想法是编写一个“包含”ItemProcessor和ItemWriter的新Writer。为了给你一个想法,我们称它为PreprocessoWriter,这就是核心代码。

private ItemWriter<O> writer;
private ItemProcessor<I, O> processor;

@Override
public void write(List<? extends I> items) throws Exception {
    List<O> toWrite = new ArrayList<O>();
    for (I item : items) {
        toWrite.add(processor.process(item));
    }
    writer.write(toWrite);
}

有很多事情被抛在一边。例如,管理ItemStream。但在我们的特殊情况下,这已经足够了。

因此,您可以将多个PreprocessorWriter与CompositeWriter结合使用。