抛出异常后的Spring事务回滚继续执行抛出后的代码

时间:2017-02-21 13:26:17

标签: java spring hibernate spring-transactions

我有一项服务来安排会议并发送确认电子邮件,我注意到如果我多次点击提交按钮,将会发送多封电子邮件。

服务是这样的:

@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$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);
        }
    });

但话又说回来。为什么?

非常感谢!

1 个答案:

答案 0 :(得分:0)

Spring抛出异常后没有执行代码。甚至没有办法,这就是java的工作方式。

在我看来,这是因为通过多次提交提交,您将多次向服务器发送请求并行执行。在某些时候,不同的线程将相互进入。

如果要正确解决此问题,则需要同步此代码,或者采取数据库锁定以防止其他事务通过。 (使用SELECT .... FOR UPDATE或其他方法)