Spring异常事务回滚抛出后继续执行代码
Spring transaction rollback on exception continues to execute code after throw
我有一个安排会议和发送确认的服务e-mail,我注意到如果我多次点击提交按钮,将发送多封电子邮件。
服务是这样的:
@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.
}
}
当(在浏览器中)多次点击“提交”时,我希望第一个测试(状态!= 草稿)足以评估这次会议是否已安排。在这种情况下,异常被抛出,被 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.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)
我无法理解这到底是怎么发送多封邮件的。我在这里读到 spring 继续执行直到方法结束,但为什么呢?!抛出异常后,为什么要在 throw() 语句之后继续执行?
我知道这个问题可以通过包装 sendEmail 调用来解决:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
sendInvitations(meeting);
}
});
不过话又说回来了。为什么?
非常感谢!
Spring 抛出异常后未执行代码。这根本不可能,这就是 java 的工作原理。
在我看来,这是因为通过多次推送提交,您向服务器发送了多次并行执行的请求。在某些时候,不同的线程会互相妨碍。
如果你想正确解决这个问题,你需要同步这段代码,或者采取数据库锁定来阻止其他交易通过。 (使用 SELECT .... FOR UPDATE 或其他方式)
我有一个安排会议和发送确认的服务e-mail,我注意到如果我多次点击提交按钮,将发送多封电子邮件。
服务是这样的:
@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.
}
}
当(在浏览器中)多次点击“提交”时,我希望第一个测试(状态!= 草稿)足以评估这次会议是否已安排。在这种情况下,异常被抛出,被 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.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)
我无法理解这到底是怎么发送多封邮件的。我在这里读到 spring 继续执行直到方法结束,但为什么呢?!抛出异常后,为什么要在 throw() 语句之后继续执行?
我知道这个问题可以通过包装 sendEmail 调用来解决:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
sendInvitations(meeting);
}
});
不过话又说回来了。为什么?
非常感谢!
Spring 抛出异常后未执行代码。这根本不可能,这就是 java 的工作原理。
在我看来,这是因为通过多次推送提交,您向服务器发送了多次并行执行的请求。在某些时候,不同的线程会互相妨碍。
如果你想正确解决这个问题,你需要同步这段代码,或者采取数据库锁定来阻止其他交易通过。 (使用 SELECT .... FOR UPDATE 或其他方式)