Spring事务对Oracle的SQLException不回滚,因默认仅回滚RuntimeException和Error;需显式配置rollbackFor = SQLException.class,且须避免try-catch吞异常、同类调用绕过代理等导致事务失效的情况。

Spring事务连接Oracle后不回滚,绝大多数情况不是Oracle的问题,而是@Transactional没按规则“说话”——它默认不理SQLException,而Oracle JDBC抛的全是SQLException。
为什么@Transactional对ORA-00001这类错误不回滚
Oracle驱动在违反唯一约束、主键冲突时抛出SQLException(比如ORA-00001: unique constraint violated),但Spring默认只对RuntimeException和Error自动回滚。因为SQLException是checked exception,继承自Exception,不在默认回滚列表里。
- 日志能看到错误,但数据已提交——这不是bug,是Spring的明确设计
- MySQL/PostgreSQL同样如此,跟数据库无关,是JDBC规范决定的异常分类
- 哪怕你用的是最新版ojdbc8,行为也一样
@Transactional(rollbackFor = SQLException.class)必须显式写
不能靠“父类写了我就不用写”或“全局配置”,每个需要响应SQLException的方法都得单独声明。
- 正确写法:
@Transactional(rollbackFor = SQLException.class) - 要同时覆盖业务异常:
@Transactional(rollbackFor = {SQLException.class, BusinessException.class}) - 慎用
rollbackFor = Exception.class:会把IOException、ClassNotFoundException也拉进回滚,可能掩盖真实故障 -
SQLException是抽象类,它的子类(如SQLTimeoutException)也会被匹配到
ORA-01403(no data found)要不要回滚?别一刀切
这个错误来自SELECT ... INTO没查到数据,JDBC包装成SQLException,但它常代表正常业务分支(比如“用户不存在”),不该触发回滚。
- 不要用
@Transactional(noRollbackFor = SQLException.class)——太粗暴,会把所有SQL错误都放过 - 推荐做法:在DAO层捕获并转义:
if (e.getSQLState().equals("02000")) { return null; } else { throw e; } - 或者封装为
NoDataFoundException,再用noRollbackFor = NoDataFoundException.class精准控制
事务不回滚的其他高频原因(和Oracle无关但常一起出现)
即使加了rollbackFor,还可能因这些操作让事务“静默提交”:
- 在
@Service方法里写了try-catch却没throw或没调TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() - 调用本类另一个
@Transactional方法——走的是this.methodB(),绕过代理,事务失效 - 方法不是
public:Spring AOP代理不生效,注解直接被忽略 - 事务方法被同一类内非事务方法调用,传播机制没生效(
PROPAGATION_REQUIRED要求调用方也在事务中)
最麻烦的其实是混合场景:比如你修好了rollbackFor,但又在service里catch了异常+打日志+沉默返回,那Spring根本收不到异常信号——事务切面就当一切顺利,照常提交。


















