本文的目的是让未使用过spring batch的朋友快速上手应用spring batch,以及避免掉一下可能遇到的雷坑。
先推荐一篇关于spring batch学习的文章,这里介绍一下基础知识https://www.jianshu.com/p/6f038c1f6037。
好接下来不多废话
为什么要用spring batch?
spring batch是一个轻量级的,完全面向spring的批处理框架,可以应用于企业级大量的数据处理系统。与spring boot脚手架一同使用,省去了复杂的配置和麻烦的部署,注解化调用方式轻松上手。spring batch相较于其他几种批处理框架,优势就在于轻量级,面向spring,以对象的形式来完成数据的读解写三步操作。
这里我们说一下 job、step、reader、processor、writer在实际使用中的表现以及方式。
up测试了一下spring batch在实际应用中的表现如何,经过完整的验证,db类操作支撑几百万级的数据批处理操作轻轻松松,文件io操作,也足以完成4g左右大小的excel数据导入解析。因此在应对中小型项目的数据批处理工作上,spring batch的表现足以让你眼前一亮。
1.job
spring batch最核心的就是job,job内可以分为很多个step,比如我要对订单商品数据进行导入解析,我的step1导入商品数据,step2导入订单数据,step3结合比对统计分析,step4完成输入。
package com.winhc.flysky.batch;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.winhc.flysky.batch.listener.RadarAfterDayJobListener;
import com.winhc.flysky.batch.listener.RadarAfterLimitJobListener;
import com.winhc.flysky.batch.processer.EciDetailProcessor;
/**
* 任务builder类,统一管理任务
* @author donnie
*
*/
@Configuration
@EnableBatchProcessing
public class RadarBatchBuilder {
Log log = LogFactory.getLog(RadarBatchBuilder.class);
@Autowired
public JobLauncher jobLauncher;
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Autowired
public RadarBatchStep radarBatchStep;
@Autowired
public EciDetailProcessor eciDetailProcessor;
@Autowired
public RadarAfterDayJobListener radarAfterDayJobListener;
@Autowired
public RadarAfterLimitJobListener radarAfterLimitJobListener;
public void dayRun() {
try {
//这里需要注意的是dateParam参数必须传入到JobParameters内,这是spring batch的一个后绑定技术,
//就是在生成Step的时候,才去创建bean,因 为这个时候jobparameter才传过来。
//如果加载配置信息的时候就创建bean,这个时候jobparameter的值还没有产生,会抛出异常。
String dateParam = new Date().toString();
JobParameters param =new JobParametersBuilder()
.addString("jobName", "radarMessageDayJob")
.addString("date", dateParam)
.toJobParameters();
JobExecution dayExecution = jobLauncher.run(radarMessageDayJob(), param);//执行job
log.info("dayExecution dateParam : " + dateParam);
log.info("dayExecution Exit Status : " + dayExecution.getStatus());
} catch (Exception e) {
log.error("dayRun :", e );
}
}
public void limitRun() {
try {
String dateParam = new Date().toString();
JobParameters param =new JobParametersBuilder()
.addString("jobName", "radarMessageLimitJob")
.addString("date", dateParam)
.toJobParameters();
JobExecution limitExecution = jobLauncher.run(radarMessageLimitJob(), param);//执行job
log.info("limitExecution dateParam : " + dateParam);
log.info("limitExecution Exit Status : " + limitExecution.getStatus());
} catch (Exception e) {
log.error("limitRun :", e );
}
}
@Bean
public Job radarMessageLimitJob() {
return jobBuilderFactory.get("radarMessageLimitJob")
.incrementer(new RunIdIncrementer())
.start(radarBatchStep.eciChattelProcessorStep())//启动第一个执行的step,这里是逐步运行每个step
.next(radarBatchStep.eciDetailProcessorStep())//next 下一个执行的step
.next(radarBatchStep.eciExceptionProcessorStep())
.next(radarBatchStep.eciInvestProcessorStep())
.next(radarBatchStep.eciShixinZhixingProcessorStep())
.next(radarBatchStep.eciZSCQProcessorStep())
.next(radarBatchStep.gudongShixinProcessorStep())
.next(radarBatchStep.judicialProcessorStep())
.next(radarBatchStep.recruitProcessorStep())
.next(radarBatchStep.tenderProcessorStep())
.listener(radarAfterLimitJobListener)
.build();
}
@Bean
public Job radarMessageDayJob() {
return jobBuilderFactory.get("radarMessageDayJob")
.incrementer(new RunIdIncrementer())
.flow(radarBatchStep.landInfoProcessorStep())//使用flow,这个step可以与其他step同时执行
.next(radarBatchStep.courtAnnouncementProcessorStep())//next 下一个执行的step
.end()
.listener(radarAfterDayJobListener)//job运行结束后的监听器,监听器内可以完成一些消息发送任务完结的代码
.build();
}
}
以上相关重要参数的说明都加好注释。
2.step
每个job分一个或多个step,step内分reader、processor、writer。我们一般的运行机制是先读取reader数据,在解析processor数据,这里可以完成你对数据的业务变更,最后将解析processor的数据根据具体需求可以写入writer到db或者file等
每个step内可以有多个reader、可以有多个processor、多个writer
需要注意的是,假设我定义了3个processor,分别是A,B,C,调用的顺序是A---->B---->C,那么A的返回参数必须是B的输入参数,B的返回参数必须是C的输入参数。
同时,reader的返回参数必须是A的输入参数,C的返回参数必须是writer的输入参数。
可能有点绕,没关系,仔细看一眼再看下面的图,就一目了然了。
画图画的有点潦草了,望大家见谅。
package com.winhc.flysky.batch.reader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.winhc.flysky.db.mybatis.dataobject.RadarRta;
import com.winhc.flysky.service.radar.RadarRtaService;
import com.winhc.flysky.util.DateUtil;
/**
* 读取所有公司,进行每日任务
* @author donnie
*
*/
@Service
public class RadarDayJobReader {
Log log = LogFactory.getLog(RadarDayJobReader.class);
private static Map<String, List<RadarRta>> finalMap=new HashMap<String, List<RadarRta>>();
@Autowired
private RadarRtaService radarRtaService;
public ListItemReader<RadarRta> geListRaderReader() {
log.info("雷达监控开始初始化dayjob reader......");
String now=DateUtil.getCurrDate_YYYYMMDD();
if(finalMap.get(now) == null){
finalMap.clear();
List<RadarRta> list =radarRtaService.queryDayJob();
finalMap.put(now, list);
}
List<RadarRta> list =finalMap.get(now);
ListItemReader<RadarRta> reader = new ListItemReader<RadarRta>(list);
log.info("雷达监控初始化dayjob reader完成......");
log.info("dayjob reader :"+JSON.toJSONString(list));
return reader;
}
}
package com.winhc.flysky.batch.processer;
import java.util.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import com.winhc.flysky.batch.ParserHelper;
import com.winhc.flysky.batch.bean.CourtInfoAnalysisBean;
import com.winhc.flysky.batch.parser.CourtInfoParser;
import com.winhc.flysky.common.exception.UnresolveRadarException;
import com.winhc.flysky.config.dict.Dict.InfoType;
import com.winhc.flysky.db.mybatis.dataobject.RadarRta;
import com.winhc.flysky.db.mybatis.dataobject.RadarRtaResultWithBLOBs;
import com.winhc.flysky.db.prism.mybatis.dataobject.CourtAnnouncementWithBLOBs;
import com.winhc.flysky.service.enterprise.EnterpriseDBService;
/**
* 法院公告
* @author donnie
*
*/
@Service
public class CourtAnnouncementProcessor implements ItemProcessor<RadarRta, RadarRtaResultWithBLOBs> {
Log log = LogFactory.getLog(CourtAnnouncementProcessor.class);
@Autowired
private EnterpriseDBService enterpriseDBService;
@Override
public RadarRtaResultWithBLOBs process(RadarRta radarRta) throws UnresolveRadarException {
String compName=radarRta.getCompName();
log.info("开始执行法院公告:"+compName);
try {
Date now=new Date();
List<CourtAnnouncementWithBLOBs> courtInfoList = enterpriseDBService.queryCourtAnnouncement(compName, now);
CourtInfoAnalysisBean courtInfoParser =new CourtInfoParser()
.setCourtInfoList(courtInfoList)
.parser();
if(ParserHelper.isEmpty(courtInfoParser.getMessage())){
return null;
}
RadarRtaResultWithBLOBs radarRtaResult = new RadarRtaResultWithBLOBs();
radarRtaResult.setRtaId(radarRta.getRtaId());
radarRtaResult.setCompName(compName);
radarRtaResult.setInfoType(InfoType.法院公告.getCode());
radarRtaResult.setChangeContent(JSON.toJSONString(courtInfoParser.getCourtInfoList()));
radarRtaResult.setRtaDesc(JSON.toJSONString(courtInfoParser.getMessage()));
} catch (Exception e) {
// TODO: handle exception
log.error("" ,e);
}
return null;
}
}
package com.winhc.flysky.batch.writer;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.winhc.flysky.config.dict.Dict.StatusEnum;
import com.winhc.flysky.db.mybatis.dataobject.RadarRta;
import com.winhc.flysky.db.mybatis.dataobject.RadarRtaResultWithBLOBs;
import com.winhc.flysky.service.radar.dao.RadarRtaDao;
import com.winhc.flysky.service.radar.dao.RadarRtaResultDao;
import com.winhc.flysky.service.radar.dto.RadarRtaDTO;
/**
* 数据写入类,判断changeContent以及rtaDesc是否为空
* @author donnie
*
*/
@Service
public class RadarDBWriter implements ItemWriter<RadarRtaResultWithBLOBs> {
@Autowired
private RadarRtaResultDao radarRtaResultDao;
@Autowired
private RadarRtaDao radarRtaDao;
@Override
public void write(List<? extends RadarRtaResultWithBLOBs> list) throws Exception {
Date now=new Date();
RadarRtaDTO radarRtaDTO =null;
for(RadarRtaResultWithBLOBs radar : list){
}
}
}
@Bean
public Step eciChattelProcessorStep() {
return stepBuilderFactory.get("eciChattelProcessorStep")
.<RadarRta, RadarRtaResultWithBLOBs> chunk(10)
.reader(radarLimitJobReader.geListRaderReader())//读取
.processor(eciChattelProcessor)//解析
.writer(radarDBWriter)//写入
.listener(radarSkipListener)//读取异常监听器,用来监听读写解析过程中的异常记录
.faultTolerant()//异常重试
.skipLimit(10)//跳过
.skip(UnresolveRadarException.class)
.retryLimit(3)//重试次数
.retry(RadarRuntimeException.class)//重试异常控制
.backOffPolicy(new FixedBackOffPolicy())
.taskExecutor(taskExecutor())//多线程同时完成步骤
.throttleLimit(5)
.build();
}
package com.winhc.flysky.batch.listener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.SkipListener;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import com.winhc.flysky.common.AssetsException;
import com.winhc.flysky.config.dict.Dict;
import com.winhc.flysky.db.mybatis.dataobject.RadarRta;
/**
* 跳过监听器
* @author donnie
*
*/
@Service
public class RadarSkipListener implements SkipListener<RadarRta, RadarRta> {
Log log = LogFactory.getLog(RadarSkipListener.class);
@Override
public void onSkipInProcess(RadarRta radar, Throwable throwable) {
if (throwable instanceof NullPointerException) {
throw new NullPointerException("异常状态提醒 :雷达监控任务在process过程中跳过: "+radar);
}
}
@Override
public void onSkipInRead(Throwable throwable) {
log.info("异常状态提醒 :雷达监控任务在Read过程中跳过 : "+throwable.getMessage());
}
@Override
public void onSkipInWrite(RadarRta radar, Throwable throwable) {
log.info("异常状态提醒 :雷达监控任务在Write过程中跳过 : "+radar);
}
}