I have an async Listener that reads data from a database, parses a file and calls an external service.
The listener is Transactional, as it needs to read from a database.
@Async
@EventListener
@Transactional
public void handleUploadedFile(DocumentDto dto) {
final var docEvent = cService.findEvent(documentDto);
final var document = dRepository.findById(dto.getId()).orElseThrow();
cService.saveUploadedDate(docEvent, document);
final var data = cService.saveData(docEvent, document);
extService.execute(data.getId(), docEvent.getId());
}
Load an event from a database using parent transaction. This part works.
public DocEvent findEvent(DocumentDto document) {
Integer periodId = document.getPeriod();
deRepository.findByPeriod(reportPeriodId).get(0);
}
I think that this new transaction is not neccessary here. This part works.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveUploadedDate(DocEvent docEvent, Document document) {
docEvent.setUploadedDate(LocalDateTime.now());
docEvent.setLoanDocument(document);
docEventRepository.save(docEvent);
}
Here I parse data and save them in a database. I use a new transaction because I need to commit data as an external service will consume them. This part works.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Data saveData(DocEvent docEvent, Document loanDocument) {
verifyDocument(document);
var data = Data.builder().event(docEvent).build();
return dhRepository.save(data);
}
Here I consume an external service which is not ready yet, so I am testing a negative scenario, when feign client throws an exception. The problem is that processingStatus is not persisted. I also use a new transaction to avoid a rollback in the caller method.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void execute(long dataId, long docEventId) {
final var data = dhRepository.findById(dataId).orElseThrow();
final var docEvent = deRepository.findById(docEventId).orElseThrow();
try {
final var request = ExternalRequest.builder().dataId(data.getId()).build();
final var body = objectMapper.writeValueAsString(request);
final var response = externalFeignClient.execute(body);
docEvent.setProcessingStatus(response.isSuccess() ? PROCESSING_SUCCESS : PROCESSING_ERROR);
deRepository.save(docEvent);
} catch (Exception e) {
log.error("Failed to execute for {}", e, data);
docEvent.setProcessingStatus(PROCESSING_ERROR);
docEventRepository.save(docEvent);
}
}
I can see in the debugger that it reached catch block and the processingStatus
is updated. But when I load a row in DB, the value is null. Everytime. Here are hibernate logs:
Feign.RetryableException: Connection reset executing POST https://host/service/execute
TransactionImpl: committing
AbstractFlushingEventListener: Processing flush-time cascades
AbstractFlushingEventListener: Dirty checking collections
AbstractFlushingEventListener: Flushed: 0 insertions, 1 updates, 0 deletions to 2 objects
AbstractFlushingEventListener: Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
EntityPrinter: Listing entities:
EntityPrinter: Data{}
EntityPrinter: DocEvent{processingStatus=ERROR}
update doc_event set processing_status=? where id=?
JdbcCoordinatorImpl: HHH000420: Closing un-released batch
PS this is Spring boot application 2.7.16, Java 11, Oracle.
PS2 I tried to move that processing status update to another method with a new transaction similar to saveUploadedDate
, and I later realized this worked. But my original approach shall work as well. I dont want to create so much transactions.