我希望能够在编写器抛出异常时将步骤和作业状态设置为失败。在做了一些调试并检查spring批处理源代码之后,我注意到RepeatTemplate
配置了SimpleRetryExceptionHandler
,将BatchRuntimeException
视为致命异常,因此将作业状态设置为FAILED ,所以我将代码包装在我的编写器中的try-catch中,将RuntimeException
包装在BatchRuntimeException
中,现在作业和步骤状态设置为FAILED,如我所愿。我不确定这是否是正确的方法,因为我无法在任何地方找到它并且BatchRuntimeException
的文档也没有说明任何内容。所以,问题是:这是正确的方法吗?
我想到的另一件事是,如果写入失败,如果FAIL工作是有意义的,但我认为这是有意义的,因为我有这样的用例:使用流阅读器从数据库读取条目(它将查询配置为针对数据库运行),然后使用流编写器通过电子邮件或http发送这些条目(检索配置在流开放方法中发送项目的位置)。如果一切都成功,则使用状态SENT更新数据库条目,如果在doOpen / open / write方法期间发生错误,则调用StepExecutionListener
以发送作业失败的通知电子邮件。这是我需要将作业状态设置为FAILED的原因之一,以便StepExecutionListener
(检查ExitCode
为FAILED)正确执行。第二个原因是我想使用spring Batch Admin应用程序来管理作业,如果作业显示为COMPLETED,虽然写入失败,但这似乎有误导性,因为作业的重点是发送项目。这样,更改配置后,如果失败,则可以重新启动作业(例如,正确配置了电子邮件地址)。
此外,如果写入调用失败,那么数据库中的条目应将其状态更新为FAILED,我计划在新事务中的ItemWriteListener
的onWriteError中执行此操作,因为当前的一个将要回滚。
我发布了所有这些长篇描述只是为了确保我不会在这里违背框架的意图,尝试将作业状态设置为来自作者的FAILED。
期待您对此的看法。
问候, 克里斯提
P.S。:作业配置如下:
<batch:job id="job">
<batch:step id="step">
<batch:tasklet>
<batch:chunk reader="reader" writer="writer" reader-transactional-queue="true" commit-interval="#{properties['export.page.size']}"/>
</batch:tasklet>
<batch:listeners>
<batch:listener ref="failedStepListener"/>
</batch:listeners>
</batch:step>
</batch:job>
LATER EDIT:读者和作者配置如下:
<bean name="reader" class="...LeadsReader" scope="step">
<property name="campaignId" value="#{jobParameters[campaignId]}" />
<property name="partnerId" value="#{jobParameters[partnerId]}" />
<property name="from" value="#{jobParameters[from]}" />
<property name="to" value="#{jobParameters[to]}" />
<property name="saveState" value="false" /> <!-- we use a database flag to indicate processed records -->
</bean>
<bean name="writer" class="...LeadsItemWriter" scope="step">
<property name="campaignId" value="#{jobParameters[campaignId]}" />
</bean>
作者的代码是:
public class LeadsItemWriter extends AbstractItemStreamItemWriter<Object[]> {
//fields and setters omitted
public LeadsItemWriter() {
setName(ClassUtils.getShortName(LeadsItemWriter.class));
}
@Override
public void open(ExecutionContext executionContext) {
super.open(executionContext);
PartnerCommunicationDTO partnerCommunicationDTO = this.leadableService.getByCampaignId(this.campaignId)
.getPartnerCommDTO();
this.transportConfig = partnerCommunicationDTO != null ? partnerCommunicationDTO.getTransportConfig() : null;
this.encoding = partnerCommunicationDTO != null ? partnerCommunicationDTO.getEnconding() : null;
if (this.transportConfig == null) {
throw new ItemStreamException ("Failed to retrieve the transport configuration for campaign id: "
+ this.campaignId);
}
PageRequestDTO pageRequestDTO = this.pageRequestMapper.map(partnerCommunicationDTO);
if (pageRequestDTO == null) {
throw new ItemStreamException("Wrong transport mapping configured for campaign id: " + this.campaignId);
}
this.columnNames = new ArrayList<>();
for (LeadColumnDTO leadColumnDTO : pageRequestDTO.getColumns()) {
this.columnNames.add(leadColumnDTO.getName());
}
}
@Override
public void write(List<? extends Object[]> items) throws Exception {
try {
if (this.transportConfig.getTransportType() == TransportConfigEnum.EMAIL) {
this.leadExporterService.sendLeads(items, this.columnNames, this.transportConfig, this.encoding);
} else {
for (Object[] lead : items) {
this.leadExporterService.sendLead(lead, this.columnNames, this.transportConfig, this.encoding);
}
}
} catch (RuntimeException e) {
LOGGER.error("Encountered exception while sending leads.", e);
//wrap exception so that the job fails and the notification listener gets called
throw new BatchRuntimeException(e);
}
}
}
读者的代码:
public class LeadsReader extends AbstractPagingItemReader<Object[]> {
//fields and setters omitted
public LeadsReader() {
setName(ClassUtils.getShortName(LeadsReader.class));
}
@Override
protected void doOpen() throws Exception {
this.pageRequestDTO = this.pageRequestMapper.map(this.leadableService.getByCampaignId(this.campaignId)
.getPartnerCommDTO());
if (pageRequestDTO == null) {
throw new ItemStreamException("Wrong transport mapping configured for campaign id: " + this.campaignId);
}
this.timeInterval = new LeadTimeIntervalDTO(this.from != null ? of(this.from,
LeadQueryFilterParam.Comparison.GT) : null,
this.to != null ? of(this.to, LeadQueryFilterParam.Comparison.LE) : null);
super.doOpen();
}
private LeadFilterDTO of(Date date, LeadQueryFilterParam.Comparison comparison) {
LeadFilterDTO filterDTO = new LeadFilterDTO();
filterDTO.setColumn(CREATION_DATE);
filterDTO.setSqlType(DATE);
filterDTO.setComparison(comparison.name());
filterDTO.setValue(DateUtil.format(date, Validator.DATE_FORMAT));
return filterDTO;
}
@Override
protected void doReadPage() {
if (results == null) {
results = new CopyOnWriteArrayList<>();
} else {
results.clear();
}
if (this.pageRequestDTO != null) {
results.addAll(LeadsReader.this.leadStorageService.listLeads(
LeadsReader.this.pageRequestDTO.getColumns(),
LeadsReader.this.getFilters(),
LeadsReader.this.pageRequestDTO.getQueryOrderByParams(),
LeadsReader.this.pageRequestDTO.isUniqueByEmail(), LeadsReader.this.timeInterval,
(long) getPage() + 1, (long) getPageSize()).getExportedLeadsRows());
}
}
private List<LeadFilterDTO> getFilters() {
List<LeadFilterDTO> filtersList = new ArrayList<>();
LeadFilterDTO campaignFilter = new LeadFilterDTO();
campaignFilter.setColumn(CAMPAIGN_ID);
campaignFilter.setValue(Long.toString(campaignId));
campaignFilter.setSqlType(BIGINTEGER);
filtersList.add(campaignFilter);
LeadFilterDTO partnerFilter = new LeadFilterDTO();
partnerFilter.setColumn(PARTNER_ID);
partnerFilter.setValue(Long.toString(partnerId));
partnerFilter.setSqlType(BIGINTEGER);
filtersList.add(partnerFilter);
LeadFilterDTO statusFilter = new LeadFilterDTO();
statusFilter.setColumn(STATUS);
statusFilter.setValue("VALID");
statusFilter.setSqlType(CHAR);
filtersList.add(statusFilter);
return filtersList;
}
@Override
protected void doJumpToPage(int itemIndex) {
}
}
答案 0 :(得分:0)
如果框架内的组件(特别是ItemReader
,ItemProcessor
,ItemWriter
或Tasklet
)抛出异常并且未被捕获,则该步骤执行组件将被标记为失败,而无需您执行任何其他操作。如果步骤失败,则作业也会被标记为失败(这是允许重新启动的步骤)。
简而言之,在抛出异常时,您不需要做任何额外的工作来使作业失败。