在service层的RuntimeException没有处理好导致了事务没有按照预期运行而是回滚了整个事务。
业务描述:在平台上,一位用户采取特殊手段跳过了前台的验证取款,通过浏览器的fetch取出大于自己钱包余额的amount。
- sql:
因为我们的sql定义了对于amount负数的情况,将会抛出DECLARE ERROR_NEGATIVE_BALANCE CONDITION FOR SQLSTATE 'B0014'; -- Check did all money deducted what supposed IF (amount_left > 0.00 || new_total_balance < 0.00) THEN SIGNAL ERROR_NEGATIVE_BALANCE SET MESSAGE_TEXT = 'NOT ENOUGH MONEY'; END IF;
e.SQLState == 'B0014',我们在DAO层转换这个异常:
} else if (e.SQLState == 'B0014') { log.warn('[{}] [accountId: {}] not enough balance', method, accountId) throw new NegativeBalanceException()
-
在service层中处理这个异常:
} catch (NegativeBalanceException e) { cancelWithdrawal() throw e }
这就是我最开始的处理方法,把这个异常抛到control层想要打出一个log,写到这里我都不觉得有什么问题,直到后面运行才发现cancelWithdrawal()这个方法一直被回滚,后面仔细查看才发现。
-
/** * In case of balance goes below ZERO */ class NegativeBalanceException extends RuntimeException { }
问题就出在这里了,这个NegativeBalanceException是一个RuntimeException,我在service层catch到了这个异常没什么问题,错误就是处理完之后不应该抛出这个Exception,这就导致了在整个事务结束后抛出了一个RuntimeException被Spring发现,由于没有配置Spring中的@Transactional(rollbackFor = Exception.class)属性,默认是RuntimeException回滚,最终使得整个事务回滚。