У меня есть служба, чтобы запланировать встречу и отправить электронное письмо с подтверждением, и я заметил, что если я нажму кнопку отправки много раз, будет отправлено несколько писем.Срочный откат транзакций по исключению продолжает выполнять код после броска
Услуга заключается в следующем:
@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);
}
});
Но потом снова. Зачем ?
Большое спасибо!
Весна определенно не выполняется после исключения. Причина отправки нескольких писем связана с тем, что есть состояние гонки. если вы нажимаете кнопку отправки несколько раз, то один и тот же запрос выполняется параллельно, поэтому 'meetingDao.loadById' может выполняться много раз, прежде чем вы перейдете к точке вашего кода, где выполняется обновление. – Leon
Я вижу. Это потому, что фиксация выполняется только в конце транзакции? Если есть способ принудительно выполнить фиксацию сразу после обновления? Или это лучший подход к использованию TransactionSynchronizationManager? – tggm
Вы можете принудительно зафиксировать фиксацию, но у вас все еще будет условие гонки, так как несколько участников могут перейти в раздел чтения до совершения транзакции. «Простое» исправление заключается в том, чтобы отключить кнопку отправки после отправки запроса. Это никоим образом не мешает кому-либо вызывать конечную точку. С такой системной проблемой я обычно предлагаю idem-potency ваших запросов; тем не менее, это может быть полный избыток за то, что вы делаете. – Leon