모임 일정을 잡고 확인 전자 메일을 보내는 서비스가 있으며 제출 단추를 여러 번 클릭하면 여러 개의 전자 메일이 전송됩니다.스프링 트랜잭션 롤백 예외가 발생하면 코드를 계속 실행 함
서비스는 이것이다 :
@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.
}
}
내가 첫 번째 테스트를 기다리고 있었다 (브라우저에서) 제출에 여러 번 클릭 할 때 (상태 = 초안!)이 회의가 이미 예약되어 있음을 평가하기에 충분하기 . 이 경우 예외가 throw되고 catch 블록에서 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 된 후 왜 throw() 문을 넘어 계속 실행해야합니까?
나는이 문제에 sendEmail 호출을 래핑에 의해 해결 될 수 있다는 사실을 알고 :
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
sendInvitations(meeting);
}
});
하지만 다시. 왜 ?
감사합니다.
예외 이후에 Spring은 확실히 실행을 계속하지 않습니다. 여러 이메일을 보내는 이유는 경쟁 조건이 있기 때문입니다. submit을 여러 번 누르면 같은 요청이 병렬로 실행되므로,'meetingDao.loadById'는 업데이트가 끝난 코드의 한 지점에 도달하기 전에 여러 번 실행될 수 있습니다. – Leon
나는 본다. 그것은 커밋이 트랜잭션의 끝에서만 수행되기 때문입니까? 그렇다면 업데이트 이후에 imeddiately 명령을 내릴 수있는 방법이 있습니까? 또는 TransactionSynchronizationManager를 사용하는 것이 더 나은 방법입니까? – tggm
커밋을 강제 할 수 있지만 트랜잭션이 커밋되기 전에 여러 참가자가 읽기 섹션에 도달 할 수 있기 때문에 여전히 경쟁 상태가됩니다. "간단한"해결책은 요청이 제출되면 제출 단추를 사용 불가능하게하는 것입니다. 이것은 누군가가 종점을 직접 호출하는 것을 막지는 못합니다. 이와 같은 시스템 문제로 인해 일반적으로 요청에 대한 idem-potency를 제안합니다. 그러나, 그것은 당신이하고있는 것에 대한 과잉 공격일지도 모릅니다. – Leon