Arnold Mapps
Arnold Mapps

Reputation: 210

How to SendEmail only if transaction is success with spring-transactions

I want to create a user in database and send email with AWS SES to user with this case.

  1. if user commit transaction in database success => send email
  2. if send email AWS SES failed (checked exception) => rollback user creation in database
  3. if user commit transaction in database failed => don't send email to user with AWS

Problem with my code : Transaction is commited if my sendEmail method throw a exception.

Configuration: Spring-Boot project with spring-data-jpa

class EmailServiceImpl {   

    @Transactional(rollbackFor = Exception.class)
    @Override
    public User createUserAndSendEmail(UserDto userDto) throws UserEmailException {
        try {
            //rollback userCreation if sendEmail throw a checkedException
            User user = userService.create(userDto);
            sendEmail(user);
            return user;

        } catch (Exception exception) {
            throw new UserEmailException(exception.getMessage());
        }
    }

    //don't send email if userCommit in database failed
    private void sendEmail(User user) {
        amazonEmailService.sendMail(user);
    }
}

class UserServiceImpl {    

   @Transactional(propagation = Propagation.REQUIRES_NEW)
   @Override
   public User create(UserDto userDto) throws Exception {
       User user = mapp(userDto);
       return userRepository.save(user);
   }
}

Upvotes: 7

Views: 3439

Answers (1)

Ken Chan
Ken Chan

Reputation: 90497

To do certain actions after TX commit , you can use @TransactionalEventListener with TransactionPhase.AFTER_COMMIT (Default setting). Put the action that you want to do in the TransactionalEventListener :

Use the ApplicationEventPublisher to publish UserCreatedEvent :

public class EmailServiceImpl {   

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Transactional(rollbackFor = Exception.class)
    public User createUserAndSendEmail(UserDto userDto) throws UserEmailException {
        try {
            User user = userService.create(userDto);

            //Publish UserCreatedEvent such the UserCreatedEventHandler can handled it after TX commit
            applicationContext.publishEvent(new UserCreatedEvent(user));

            return user;
        } catch (Exception exception) {
            throw new UserEmailException(exception.getMessage());
        }
    }
}

And the UserCreatedEvent will be handled by this handler after TX commit:

@Component
public class UserCreatedEventHandler {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(UserCreatedEvent event) {    
        try{
            amazonEmailService.sendMail(user);
            System.out.println("Good, can send email.");
        }catch(Exception exception){
            System.out.println("Sad, fail to send email , so remove user from DB...");
            userService.remove();
        }
    }
}

Good catch by Deinum. If you use my suggestion , you have to change userService.create() to @Transactional(propagation = Propagation.REQUIRES)

Upvotes: 9

Related Questions