Spring Batch(6) : ItemReader & ItemWriter

针对批处理,Spring Batch提供ItemReader、ItemProcessor、ItemWriter三个核心组件,类似ETL的三个步骤。

5.1 ItemReader

ItemReader支持从多种数据源读入数据,接口定义如下:

public interface ItemReader<T> {
    T read() throws Exception, UnexpectedInputException, ParseException;
}

泛型T表示读入的Item类型,一般是有明确意义的领域模型。通常情况下,read方法读入数据,返回item,如果没有更多数据,返回null。 在特殊的场景下,可以支持事务操作,例如对于JMS数据源,可以在事务回滚你的时候重新放回地队列中。

5.1.1 基于游标的Reader

在数据库解决方案中,游标是一个核心概念,JDBS的ResultSet也使用了Cursor的概念,如:

Spring Batch(6) : ItemReader & ItemWriter_第1张图片

基于Cursor的Reader将游标的概念转化为流的概念,例如配置一个CursorReader:

<bean id="itemReader" class="org.spr...JdbcCursorItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="sql" value="select ID, NAME, CREDIT from CUSTOMER"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.sample.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

调用该Reader的read方法将逐条返回结果中的item,而无需关心底层细节

5.1.2 基于存储过程的Reader

如果使用存储过程查询Item,则可以通过StoreProcedureItemReader完成,下面是一个简单例子:

<bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="procedureName" value="sp_customer_credit"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.sample.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

如果使用的是函数调用:

<bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="procedureName" value="sp_customer_credit"/>
    <property name="function" value="true"/>
    <property name="rowMapper">
        <bean class="org.springframework.batch.sample.domain.CustomerCreditRowMapper"/>
    </property>
</bean>

如果带有参数:

<bean id="reader" class="o.s.batch.item.database.StoredProcedureItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="procedureName" value="spring.cursor_func"/>
    <property name="parameters">
        <list>
            <bean class="org.springframework.jdbc.core.SqlOutParameter">
                <constructor-arg index="0" value="newid"/>
                <constructor-arg index="1">
                    <util:constant static-field="oracle.jdbc.OracleTypes.CURSOR"/>
                </constructor-arg>
            </bean>
            <bean class="org.springframework.jdbc.core.SqlParameter">
                <constructor-arg index="0" value="amount"/>
                <constructor-arg index="1">
                    <util:constant static-field="java.sql.Types.INTEGER"/>
                </constructor-arg>
            </bean>
            <bean class="org.springframework.jdbc.core.SqlParameter">
                <constructor-arg index="0" value="custid"/>
                <constructor-arg index="1">
                    <util:constant static-field="java.sql.Types.INTEGER"/>
                </constructor-arg>
            </bean>
        </list>
    </property>
    <property name="refCursorPosition" value="1"/>
    <property name="rowMapper" ref="rowMapper"/>
    <property name="preparedStatementSetter" ref="parameterSetter"/>
</bean>
5.1.3 分页Reader

JdbcPagingItemReader:

<bean id="itemReader" class="org.spr...JdbcPagingItemReader">
    <property name="dataSource" ref="dataSource"/>
    <property name="queryProvider">
        <bean class="org.spr...SqlPagingQueryProviderFactoryBean">
            <property name="selectClause" value="select id, name, credit"/>
            <property name="fromClause" value="from customer"/>
            <property name="whereClause" value="where status=:status"/>
            <property name="sortKey" value="id"/>
        </bean>
    </property>
    <property name="parameterValues">
        <map>
            <entry key="status" value="NEW"/>
        </map>
    </property>
    <property name="pageSize" value="1000"/>
    <property name="rowMapper" ref="customerMapper"/>
</bean>

IbatisPagingItemReader:

<bean id="itemReader" class="org.spr...IbatisPagingItemReader">
    <property name="sqlMapClient" ref="sqlMapClient"/>
    <property name="queryId" value="getPagedCustomerCredits"/>
    <property name="pageSize" value="1000"/>
</bean>
<select id="getPagedCustomerCredits" resultMap="customerCreditResult">
    select id, name, credit from customer order by id asc LIMIT #_skiprows#, #_pagesize#
</select>

_skiprows和_pagesize参数由Reader提供。

5.1.4 通过Adapter复用现有服务:

当Reader操作的实现已经有现成的代码时,可以通过适配器将其转化为Reader:

<bean id="itemReader" class="org.springframework.batch.item.adapter.ItemReaderAdapter">
    <property name="targetObject" ref="fooService" />
    <property name="targetMethod" value="generateFoo" />
</bean>

<bean id="fooService" class="org.springframework.batch.item.sample.FooService" />

但是有一点,也就是上述的generateFoo方法必须和read方法的协议保持一致,及返回值等其他约定必须符合read规范。

如果一条记录是否已经被处理,是通过数据源的某个字段来表示的,那么此时Reader将没有必要保存状态,例如当前行数,不然将导致重启的时候出错,因此将Reader的saveState置为false。

<bean id="playerSummarizationSource" class="org.spr...JdbcCursorItemReader">
    <property name="dataSource" ref="dataSource" />
    <property name="rowMapper">
        <bean class="org.springframework.batch.sample.PlayerSummaryMapper" />
    </property>
    <property name="saveState" value="false" />
    <property name="sql">
        <value>
            SELECT games.player_id, games.year_no, SUM(COMPLETES),
            SUM(ATTEMPTS), SUM(PASSING_YARDS), SUM(PASSING_TD),
            SUM(INTERCEPTIONS), SUM(RUSHES), SUM(RUSH_YARDS),
            SUM(RECEPTIONS), SUM(RECEPTIONS_YARDS), SUM(TOTAL_TD)
            from games, players where players.player_id =
            games.player_id group by games.player_id, games.year_no
        </value>
    </property>
</bean>


5.2 ItemWriter

IteamWriter用于将处理后的数据写入到目的地。支持批量写出,接口如下:

public interface ItemWriter<T> {
    void write(List<? extends T> items) throws Exception;
}

写入Item List后,需要进行的刷新、收尾操作都可以在这里完成,例如关闭Session等。

对于Reader和Writer,一般都只完成特定目的的读写操作,如果需要更复杂的读写操作,可以使用组合模式,创建更负责的综合的Reader/Writer,下面是一个例子:

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

    ItemWriter<T> itemWriter;

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

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

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

Reader也一样,例如业务的一次处理需要一个复杂的Item,这些数据可能来自不同的数据源,或者来自同一数据源的不同表,可以通过组合多个Reader来完成。

需要注意的是,如果Step中Reader、Writer、Processor实现了ItemStream或者StepListener,则会自动地被注册到Step中,但是上述的例子中使用了代理的Writer,对于代理使用的itemWriter,Step是不知道的,因此需要显示注册,以便Step管理该Steam。

<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="fooProcessor" writer="compositeItemWriter" commit-interval="2">
                <streams>
                    <stream ref="barWriter" />
                </streams>
            </chunk>
        </tasklet>
    </step>
</job>

<bean id="compositeItemWriter" class="...CustomCompositeItemWriter">
    <property name="delegate" ref="barWriter" />
</bean>

<bean id="barWriter" class="...BarWriter" />

文件的事务性是通过特殊的ItemWriter来保障的,但是在JDBC中,事务性由数据库本身来提供保障,因此没有必要提供特殊的Writer。数据库Wtriter执行过程如下:

但是在批处理中,批量的更新或者插入时,如果其中一条记录出错导致事务回滚,这时候无法知道是哪条具体的记录造成的。解决的唯一方案是每次都刷新:
Spring Batch(6) : ItemReader & ItemWriter_第2张图片

5.3 ItemProcessor

Processor处理自定义的业务逻辑,完成转换、过滤等操作。例如:

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(List<? extends Bar> bars) throws Exception {
        //write bars
    }
}

将这些配置到一个Step里:

<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="fooProcessor" writer="barWriter" commit-interval="2"/>
        </tasklet>
    </step>
</job>
5.3.1 组合ItemProcessor

可以将多个ItemProcessor组合为一个Processor链条,通过使用CompositeItemProcessor:

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);
    }
}

// 复合Processor
CompositeItemProcessor<Foo,Foobar> compositeProcessor =
                                      new CompositeItemProcessor<Foo,Foobar>();
List itemProcessors = new ArrayList();
itemProcessors.add(new FooTransformer());
itemProcessors.add(new BarTransformer());
compositeProcessor.setDelegates(itemProcessors);
<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="compositeProcessor" 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>
5.3.2 过滤Item

Skipping表示一个Item是非法的(invalid),需要跳过。而Filtering只是表示这个Item不应该被写到目标数据中。
可以通过返回null来表示被过滤。

5.3.3 ItemStream

Item流是对Reader和Writer的统一抽象,接口如下:

public interface ItemStream {

    void open(ExecutionContext executionContext) throws ItemStreamException;

    void update(ExecutionContext executionContext) throws ItemStreamException;

    void close() throws ItemStreamException;
}

其中open和close都比较容易理解,update主要用于将状态信息更新到JobRepository,一般在提交事务的时候调用,确保框架任务状态的准确性。

5.4 输入校验

Spring Batch的Validator组件用于在处理Item之前对Item进行校验,接口定义如下:

public interface Validator {
    void validate(Object value) throws ValidationException;
}

将验证器配置到Processor:

<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 id="orderValidator" class="org.springmodules.validation.valang.ValangValidator">
            <property name="valang">
                <value>
                    <![CDATA[ { orderId : ? > 0 AND ? <= 9999999999 : 'Incorrect order ID' : 'error.order.id' } { totalLines : ? = size(lineItems) : 'Bad count of order lines' : 'error.order.lines.badcount'} { customer.registered : customer.businessCustomer = FALSE OR ? = TRUE : 'Business customer must be registered' : 'error.customer.registration'} { customer.companyName : customer.businessCustomer = FALSE OR ? HAS TEXT : 'Company name for business customer is mandatory' :'error.customer.companyname'} ]]>
                </value>
            </property>
        </bean>
    </property>
</bean>

如果Item合法,则正常返回,否则抛出异常。

你可能感兴趣的:(Spring Batch(6) : ItemReader & ItemWriter)