什么是Spring Batch
Spring Batch 作为 Spring的子项目,是一款基于 Spring的企业批处理框架。通过它可以构建出健壮的企业批处理应用。Spring Batch不仅提供了统一的读写接口、丰富的任务处理方式、灵活的事务管理及并发处理,同时还支持日志、监控、任务重启与跳过等特性,大大简化了批处理应用开发,将开发人员从复杂的任务配置管理过程中解放出来,使他们可以更多地去关注核心的业务处理过程。
另外我们还需要知道,Spring Batch是一款批处理应用框架,不是调度框架。它只关注批处理任务相关的问题,如事务、并发、监控、执行等,并不提供相应的调度功能。因此,如果我们希望批处理任务定期执行,可结合 Quartz等成熟的调度框架实现。
Spring Batch 还针对读、写操作提供了多种实现,如消息、文件、数据库。对于数据库,还提供了 Hibernate、iBatis、JPA等常见 ORM框架的读、写接口支持。
所有 Spring Batch的读操作均需要实现 ItemReader接口,而且 Spring Batch为我们提供了多种默认实现,尤其是基于 ORM框架的读接口,同时支持基于游标和分页两类操作。因此,多数情况下我们并不需要手动编写ItemReader类,而是直接使用相应实现类即可。
上图描绘了Spring Batch的执行过程。说明如下:
每个Batch都会包含一个Job。Job就像一个容器,这个容器里装了若干Step,Batch中实际干活的也就是这些Step,至于Step干什么活,无外乎读取数据,处理数据,然后将这些数据存储起来(ItemReader用来读取数据,ItemProcessor用来处理数据,ItemWriter用来写数据)。JobLauncher用来启动Job,JobRepository是上述处理提供的一种持久化机制,它为JobLauncher,Job,和Step实例提供CRUD操作。
外部控制器调用JobLauncher启动一个Job,Job调用自己的Step去实现对数据的操作,Step处理完成后,再将处理结果一步步返回给上一层,这就是Batch处理实现的一个简单流程。
从DB或是文件中取出数据的时候,read()操作每次只读取一条记录,之后将读取的这条数据传递给processor(item)处理,框架将重复做这两步操作,直到读取记录的件数达到batch配置信息中”commin-interval”设定值的时候,就会调用一次write操作。然后再重复上图的处理,直到处理完所有的数据。当这个Step的工作完成以后,或是跳到其他Step,或是结束处理。
这就是一个SpringBatch的基本工作流程。
HelloWorld
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd">
<!-- <import resource="applicationContext.xml"/>-->
<batch:job id="batchhelloWorldJob" >
<batch:step id="step_hello" next="step_world">
<tasklet ref="hello" transaction-manager="transactionManagerBatch"></tasklet>
</batch:step>
<batch:step id="step_world">
<tasklet ref="world" transaction-manager="transactionManagerBatch"></tasklet>
</batch:step>
</batch:job>
<bean id="hello" class="com.sds.job.SayHello">
<property name="message" value="Hello "></property>
</bean>
<bean id="world" class="com.sds.job.SayHello">
<property name="message" value=" World!"></property>
</bean>
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean" />
<bean id="transactionManagerBatch"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry"/>
<bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry"/>
</bean>
</beans>
多个processor
多个processor
<batch:job id="csvJob">
<batch:step id="csvStep">
<tasklet transaction-manager="transactionManagerBatch">
<chunk reader="csvItemReader" writer="csvItemWriter" processor="CompositeProcessor" commit-interval="1">
</chunk>
</tasklet>
</batch:step>
</batch:job>
<!-- 多个 processor -->
<bean id="CompositeProcessor" class="org.springframework.batch.item.support.CompositeItemProcessor">
<property name="delegates">
<list>
<ref bean="csvItemProcessor" />
<ref bean="WriteIntoDB" />
</list>
</property>
</bean>
文件读写
CsvItemProcessor 实现了
ItemProcessor<I,O>
接口,其中类型 I 表示传递给处理器的对象类型,而 O 则表示处理器返回的对象类型。
<batch:job id="csvJob">
<batch:step id="csvStep">
<tasklet transaction-manager="transactionManagerBatch">
<chunk reader="csvItemReader" writer="csvItemWriter" processor="CompositeProcessor" commit-interval="1">
</chunk>
</tasklet>
</batch:step>
</batch:job>
<!-- 多个 processor -->
<bean id="CompositeProcessor" class="org.springframework.batch.item.support.CompositeItemProcessor">
<property name="delegates">
<list>
<ref bean="csvItemProcessor" />
<ref bean="WriteIntoDB" />
</list>
</property>
</bean>
<bean id = 'csvItemProcessor' class="com.sds.processor.CsvItemProcessor"></bean>
<!-- 读取csv文件 -->
<bean id="csvItemReader"
class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource" value="file:src/inputFile.txt"/>
<!-- <property name="linesToSkip" value="1" /> 告诉 file reader 有多少标题行需要跳过。
通常CSV文件的第一行包含标题信息,如列名称,所以本例中让 reader 跳过文件的第一行-->
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer" ref="lineTokenizer"/>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="person"></property>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="person" class="com.sds.bean.Person"></bean>
<!-- lineTokenizer -->
<bean id="lineTokenizer" class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="delimiter" value=","/>
<property name="names">
<list>
<value>pname</value>
<value>pid</value>
<value>age</value>
<value>birthday</value>
</list>
</property>
</bean>
<!-- 写CSV文件 -->
<bean id="csvItemWriter"
class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
<property name="resource" value="file:src/outputFile.txt"/>
<property name="lineAggregator">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value=","></property>
<property name="fieldExtractor">
<bean
class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="names" value="pname,pid,age,birthday"></property>
</bean>
</property>
</bean>
</property>
</bean>
处理类CsvItemProcessor
public class CsvItemProcessor implements ItemProcessor<Person,Person> {
@Override
public Person process(Person person) throws Exception {
System.out.println("------------------------");
System.out.println(person.getPname());
person.setPname(person.getPname() + "~");
return person;
}
}
多个processor
<batch:job id="csvJob">
<batch:step id="csvStep">
<tasklet transaction-manager="transactionManagerBatch">
<chunk reader="csvItemReader" writer="csvItemWriter" processor="CompositeProcessor" commit-interval="1">
</chunk>
</tasklet>
</batch:step>
</batch:job>
<!-- 多个 processor -->
<bean id="CompositeProcessor" class="org.springframework.batch.item.support.CompositeItemProcessor">
<property name="delegates">
<list>
<ref bean="csvItemProcessor" />
<ref bean="WriteIntoDB" />
</list>
</property>
</bean>
操作数据库
@Component("WriteIntoDB")
public class WriteIntoDB implements ItemProcessor<Person,Person>{
@Resource(name="personDao")
public IPersonDao dao ;
@Override
public Person process(Person item) throws Exception {
System.out.println("******************");
System.out.println(item.getPname() + item.getBirthday());
dao.insertPerson(item);
System.out.println("insert success");
return item;
}
}
skip的介绍
在实际的项目开发中,我们常常要将几十万甚至上百万的数据从文件导入到DB中,如果其中某条数据导入时发生例外,我们并不想整个Job以失败而结束,而是希望能将错误的数据经过处理后保存起来,其余正确的数据继续做导入处理。如果遇到这样的场景,SpringBatch的skip机制就可以派上用场了。顾名思义,skip的作用就是跳过某些数据(例如错误数据)。
<job id="csvJob">
2 <step id="csvStep">
3 <tasklet transaction-manager="transactionManager">
4 <chunk reader="itemReaders" writer="itemWriter" processor="itemProcessor"
5 commit-interval="1" skip-limit="1000">
6 <skippable-exception-classes>
7 <include class="org.springframework.batch.item.file.FlatFileParseException" />
8 </skippable-exception-classes>
9 </chunk>
10 </tasklet>
11 </step>
12 </job>