Я хочу иметь возможность установить статус шага и задания в состояние сбоя, когда писатель выдает исключения. После некоторой отладки и изучения исходного исходного кода весны я заметил, что RepeatTemplate
сконфигурирован с SimpleRetryExceptionHandler
, который считает BatchRuntimeException
фатальным исключением и, следовательно, устанавливает статус задания в FAILED, поэтому я завернул код в своем авторе в попытке -catch, который обертывает RuntimeException
в BatchRuntimeException
, и теперь статусы задания и шага устанавливаются как FAILED, как я и хотел. Я не уверен, что это правильный способ сделать это, хотя я не мог найти его документированным где-либо, и в документации для BatchRuntimeException
ничего не говорится об этом. Итак, вопрос был бы: это правильный способ сделать это?Конфигурация статуса весеннего пакетного задания
Еще одна вещь, о которой я подумал, - это иметь смысл ОТКАЗАТЬ работу, если запись не удалась, но я думаю, что это имеет смысл при использовании прецедента, который у меня есть, что-то вроде этого: чтение записей из базы данных с использованием потока читатель (который настраивает запрос для работы с базой данных), затем используйте создатель потока для отправки этих записей по электронной почте или http (извлекайте конфигурацию, где отправлять элементы в методе открытого потока). Если все будет успешным, обновите записи базы данных со статусом SENT, если во время doOpen/open/write возникнет ошибка, вызовите StepExecutionListener
, чтобы отправить уведомление, в котором задание не выполнено. Это одна из причин, по которым мне нужно, чтобы статус работы был FAILED, так что StepExecutionListener
(который проверяет ExitCode
, который должен быть FAILED) выполняется правильно. Вторая причина заключается в том, что я хочу использовать приложение Spring Batch Admin для управления заданиями, и если задание отображается как ЗАВЕРШЕНО, хотя запись не удалась, это кажется ошибочным, поскольку вся точка задания заключается в отправке элементов. Таким образом, задание может быть перезапущено, если оно не удалось, после изменения конфигурации (например, правильно настроен адрес электронной почты).
Кроме того, если вызовы записи не выполняются, то запись в базе данных должна обновляться до FAILED, которую я планирую сделать в onWriteError ItemWriteListener
внутри новой транзакции, поскольку текущий откат.
Я разместил все это длинное описание, чтобы быть уверенным, что я не работаю против намерения структуры здесь, пытаясь установить статус работы FAILED от автора.
Ждем ваших размышлений об этом.
Привет, Cristi
PS: Работа настраивается так:
<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>
СПУСТЯ 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) {
}
}
Это не совсем верно для всех случаев: например, если считыватель настроен с помощью reader-transactional-queue = "true", то StepParserStepFactoryBean вызывает метод createFaultTolerantStep, который, в свою очередь, настраивает RepeatTemplate с помощью SimpleRetryExceptionHandler (в createRetryOperations ()), который проглатывает исключение. –
Можете ли вы поделиться конфигурацией для вашего читателя и писателя? –
Конечно, я добавил конфигурацию для них в описании. –