Spring事务对SQLException默认不回滚,因其属Checked Exception;需显式配置rollbackFor = SQLException.class,且须避免同类内调用导致代理失效。
spring事务在oracle异常时不回滚,绝大多数情况是因为抛出了sqlexception(checked exception),而它默认不触发回滚。这不是oracle驱动或数据库的问题,是spring事务机制的明确设计——你得主动告诉它“这个异常也要回滚”。
为什么SQLException抛出后事务没回滚?
Oracle JDBC驱动抛出的SQLException属于Checked Exception,继承自Exception而非RuntimeException。Spring的@Transactional默认只对RuntimeException和Error自动回滚,对SQLException完全无视。
- 哪怕SQL执行失败(如违反唯一约束、主键冲突、外键约束),只要抛的是
SQLException,事务就继续走提交流程 - 日志里能看到
ORA-00001: unique constraint violated,但数据却留在了库中 - 这和MySQL/PostgreSQL行为一致——不是Oracle特有,而是JDBC规范决定的异常类型
@Transactional(rollbackFor = SQLException.class)必须显式写
不能依赖“全局配置”或“父类继承”,每个需要回滚SQLException的方法都得单独声明。Spring不会向上查找父类或接口的注解来合并rollbackFor。
- 正确写法:
@Transactional(rollbackFor = SQLException.class) - 如果还想覆盖其他业务异常,可叠加:
@Transactional(rollbackFor = {SQLException.class, BusinessException.class}) - 慎用
rollbackFor = Exception.class:会把IOException、ClassNotFoundException等无关异常也纳入回滚,可能掩盖真实问题 - 注意:
SQLException是抽象类,子类如SQLTimeoutException、SQLNonTransientException也会被匹配到
Oracle特定场景:ORA-01403: no data found要不要回滚?
这个错误由SELECT ... INTO未查到数据时抛出,JDBC驱动将其包装为SQLException。但它常代表“正常业务分支”,比如查用户不存在——此时你**不该回滚**,否则会误杀上游已成功的操作。
- 若想让
ORA-01403不回滚,需用noRollbackFor排除:@Transactional(noRollbackFor = SQLException.class)(不推荐) - 更稳妥做法:捕获该异常并转为自定义
BusinessException,再用noRollbackFor = BusinessException.class精准控制 - 直接
catch (SQLException e) { if (e.getSQLState().equals("02000")) { return null; } throw e; },避免事务切面介入
代理失效导致rollbackFor形同虚设
即使写了rollbackFor = SQLException.class,事务仍不回滚,大概率是调用发生在同一个类内部——Spring AOP代理无法拦截私有方法或this调用。
- 错误示例:
public void doWork() { saveOrder(); throw new SQLException(); }→saveOrder()上的@Transactional根本没生效 - 验证方式:在方法入口打日志,看是否出现
Creating new transaction字样;没出现=代理未织入 - 修复路径:拆到另一个
@Service类中调用,或用ApplicationContext.getBean(ThisClass.class)绕过代理(不推荐)
最易被忽略的一点:事务回滚只作用于当前事务上下文,而Oracle的自治事务(PRAGMA AUTONOMOUS_TRANSACTION)、存储过程中的COMMIT或SAVEPOINT会彻底破坏Spring的控制边界——这种情况下,rollbackFor再怎么配都没用。











