Spring @Transactional and @Async

Viewed 919

In my application, on the creation of a task, I need to make an API call to Google to create a google calendar event.

I decided to make that API call on a separate thread so that our client doesn't have to wait longer for the response.

@Override
@Transactional( rollbackFor = DataException.class )
public TaskResponseBean createTask( TaskCreationBean taskCreationBean, UserAccessDetails accessDetails )
        throws DataException
{
    String googleEventId = "";
    try
    {
        TaskServiceUtil.validateInputBeforeCreatingTask(taskCreationBean, accessDetails);

        MatterModel matterModel = matterService.giveMatterIfExistElseThrowException(taskCreationBean.getMatterId(),
                owner);

        //A task is unique for a user for a matter
        taskCommons.throwExceptionIfTaskNameAlreadyExistForTheMatter(taskCreationBean.getTaskName().trim(), owner,
                matterModel);

        TaskModel savedTask = taskModelRepository.save(savableTask);

        if( !NullEmptyUtils.isNull(savableTask.getDueDate()) )
        {
            final CreateEventBean createEventBean = getCreateEventBean(getEventParticipants(savedTask), savedTask);
            calendarTrigerer.triggerEventCreation(createEventBean, savedTask.getId(), null,
                    GoogleCalendarTrigerer.EVENT_TYPE_CREATE);
        }
        // Keep track of the list of assignees of a task
        if( taskCreationBean.getHaveAssignee() || taskCreationBean.getIsSelfAssigned() )
        {
            saveTaskAssignedHistory(savedTask, owner, savedTask.getAssignedTo(), false);
        }

    }
    catch( DataException e )
    {
        LOGGER.error(GeneralConstants.ERROR, e);
        if( !NullEmptyUtils.isNullOrEmpty(googleEventId) )
        {
            LOGGER.info("Deleting google event id {}", googleEventId);
            googleCalendarService.deleteGoogleCalendarEvent(googleEventId);
        }
        throw e;
    }
    catch( Exception e )
    {
        LOGGER.error(GeneralConstants.ERROR, e);
        if( !NullEmptyUtils.isNullOrEmpty(googleEventId) )
        {
            LOGGER.info("Deleting google event id {}", googleEventId);
            googleCalendarService.deleteGoogleCalendarEvent(googleEventId);
        }
        throw new DataException(GeneralConstants.EXCEPTION, GeneralConstants.SOMETHING_WENT_WRONG,
                HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

@Async
void triggerEventCreation( CreateEventBean createEventBean, Long taskId, String eventId, String eventType )
        throws DataException
{
    try
    {

        TaskModel taskModel = null;

        if( !NullEmptyUtils.isNullOrEmpty(taskId) )
        {
            int retryCount = 0;
            Optional<TaskModel> taskModelOptional = taskModelRepository.findByIdAndIsActiveTrue(taskId);
            while( !taskModelOptional.isPresent() )
            {
                System.out.println("NOT PRESENT***********************************************");
                taskModelOptional = taskModelRepository.findByIdAndIsActiveTrue(taskId);
                if( retryCount++ > 50 )
                {
                    throw new DataException(GeneralConstants.EXCEPTION, "Transaction is unable to commit",
                            HttpStatus.INTERNAL_SERVER_ERROR);
                }
            }

            taskModel = taskModelOptional.get();
        }

        switch ( eventType )
        {
            case EVENT_TYPE_CREATE :

                eventId = googleCalendarService.addGoogleCalendarEvent(createEventBean);
                System.out.println("ADDED EVENT***********************************************" + eventId);

                System.out.println("PRESENT***********************************************");
                taskModel.setGoogleEventId(eventId);
                taskModelRepository.save(taskModel);
                break;
            case EVENT_TYPE_DELETE :
                NullEmptyUtils.throwExceptionIfInputIsNullOrEmpty(eventId);
                googleCalendarService.deleteGoogleCalendarEvent(eventId);
                taskModel.setGoogleEventId(null);
                taskModel.setIsActive(false);
                taskModelRepository.save(taskModel);
                break;
            case EVENT_TYPE_UPDATE :
                NullEmptyUtils.throwExceptionIfInputIsNullOrEmpty(eventId);
                NullEmptyUtils.throwExceptionIfInputIsNullOrEmpty(createEventBean);

                taskModel.setGoogleEventId(
                        googleCalendarService.updateGoogleCalendarEvent(eventId, createEventBean));
                taskModelRepository.save(taskModel);
                break;
            default :
                throw new DataException(GeneralConstants.EXCEPTION, "Invalid eventType", HttpStatus.BAD_REQUEST);
        }

    }
    catch( DataException e )
    {
        log.error(GeneralConstants.ERROR, e);
        throw e;
    }
    catch( Exception e )
    {
        log.error(GeneralConstants.ERROR, e);
        throw new DataException(GeneralConstants.EXCEPTION,
                "Something went wrong while trigering create event action", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

How I solved (make sure it works) while creating an event In the separate thread, I will iterate and wait till the task gets created and then update it with the event id and save it.

But, a new problem arises, when updating the task, I already have its details in the database. In the update method, I will set updated values to TaskModel and do taskModelRepo.save() and in a separate thread I am calling google calendar update API and after successful API call, I have to update the corresponding TaskModel and save it.

The issue here is, sometimes when I fetch task by id after google API call is successful, I will get the TaskModel with non-updated values as the previous transaction is not committed yet.

So How to ensure the new thread runs only after the transaction of the method from which it is called is committed?

1 Answers

you can use the Google Guava Event Bus to solve this problem. It's a publish-subscribe model in which the producer is responsible for emitting the events, these events are then passed on to the event bus and are sent to all listeners that are subscribed to that event.

The listener, subscribes to an event and it is triggered when that event is posted from the producer, you could have a listener method run Synchronously or Asynchronously depending on the kind of event bus you use.

Here is the link : https://github.com/google/guava/wiki/EventBusExplained

Related