2017-02-21 44 views
0

У меня есть служба, чтобы запланировать встречу и отправить электронное письмо с подтверждением, и я заметил, что если я нажму кнопку отправки много раз, будет отправлено несколько писем.Срочный откат транзакций по исключению продолжает выполнять код после броска

Услуга заключается в следующем:

@Service 
@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = { Exception.class }) 
public class MeetingService { 
    public void scheduleAndInvite(int meetingId) { 
     try { 
      Meeting meeting = meetingDao.loadById(meetingId); 

      // Validations. 
      if (meeting.getMeetingStatus() != MeetingStatus.Draft) { 
       throw new FmcUserException("not draft"); 
      } 

      // Persist entity 
      meeting.setMeetingStatus(MeetingStatus.Scheduled); 

      meetingDao.persistMyEntity(meeting); 

      // This eventually calls JavaMailSender. Uses the Meeting hibernate entity 
      sendInvitations(meeting); 
     } catch (Exception ex) { 
      logger.error(ex.getMessage(), ex); 
      throw new FmcSystemException(ex); // This class extends RuntimeException. 
     } 
    } 

При нажатии несколько раз на Submit (в браузере), я ожидал, что первый тест (Status = проект!) Будет достаточно для оценки, что эта встреча уже назначена , В этом случае исключение выбрасывается, захватывается блоком catch, тем самым пропуская вызов sendInvitations().

Он правильно генерирует тонны исключений в журнале:

12:45:01,117 ERROR [my.framework.mvc.BaseController] (default task-55) could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.LockAcquisitionException: could not execute statement: org.springframework.dao.CannotAcquireLockException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.LockAcquisitionException: could not execute statement 
    at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:232) 
    at org.springframework.orm.hibernate5.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:755) 

Caused by: org.hibernate.exception.LockAcquisitionException: could not execute statement 
    at org.hibernate.dialect.MySQLDialect$3.convert(MySQLDialect.java:522) 
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) 

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) 

Я не могу понять, как на земле, это посылает несколько писем. Я читал здесь, что весна продолжает исполнение до конца метода, но почему ?! После того, как выбрано исключение, почему выполнение должно выполняться за пределами инструкции throw()?

Я знаю, что эта проблема может быть решена путем оборачивания вызова SendEmail в:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { 
     @Override 
     public void afterCommit() { 
      sendInvitations(meeting); 
     } 
    }); 

Но потом снова. Зачем ?

Большое спасибо!

+3

Весна определенно не выполняется после исключения. Причина отправки нескольких писем связана с тем, что есть состояние гонки. если вы нажимаете кнопку отправки несколько раз, то один и тот же запрос выполняется параллельно, поэтому 'meetingDao.loadById' может выполняться много раз, прежде чем вы перейдете к точке вашего кода, где выполняется обновление. – Leon

+0

Я вижу. Это потому, что фиксация выполняется только в конце транзакции? Если есть способ принудительно выполнить фиксацию сразу после обновления? Или это лучший подход к использованию TransactionSynchronizationManager? – tggm

+1

Вы можете принудительно зафиксировать фиксацию, но у вас все еще будет условие гонки, так как несколько участников могут перейти в раздел чтения до совершения транзакции. «Простое» исправление заключается в том, чтобы отключить кнопку отправки после отправки запроса. Это никоим образом не мешает кому-либо вызывать конечную точку. С такой системной проблемой я обычно предлагаю idem-potency ваших запросов; тем не менее, это может быть полный избыток за то, что вы делаете. – Leon

ответ

0

Spring не выполняет код после исключения. Существует не так, как это возможно, это просто, как работает Java.

Мне кажется, что это связано с тем, что, нажимая кнопку submit несколько раз, вы отправляете запрос на свой сервер несколько раз, которые выполняются параллельно. в какой-то момент разные потоки будут проходить друг с другом.

Если вы хотите правильно решить эту проблему, вам необходимо либо синхронизировать этот код, либо заблокировать базу данных, предотвращающую прохождение других транзакций. (Использование SELECT .... FOR UPDATE или некоторые другие средства)