Spring/Hibernate中自定义异常转换器未触发问题求助
你已经实现了自定义的MyLocalSessionFactoryBean,重写了translateExceptionIfPossible方法来处理Oracle的事务序列化异常(ORA-08177),代码如下:
class MyLocalSessionFactoryBean extends LocalSessionFactoryBean { @Override public DataAccessException translateExceptionIfPossible(RuntimeException ex) { if (ex instanceof HibernateJdbcException) { // Oracle序列化异常的SQL状态为[72000],错误码为[8177] SQLException sqle = ((HibernateJdbcException) ex).getSQLException(); if (equal(sqle.getSQLState(), "72000") && sqle.getErrorCode() == 8177) return new CannotSerializeTransactionException("Cannot serialize transaction access", ex); } return super.translateExceptionIfPossible(ex); } }
并且已经将该Bean设为primary,DAO也标注了@Repository,但事务提交阶段抛出ORA-08177时,自定义方法从未被调用,异常直接传播,导致Spring Retry无法触发重试。
附异常栈:
Exception in thread "processRunManager-dequeue" org.springframework.orm.hibernate5.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [n/a]; SQL state [72000]; error code [8177]; could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException: could not execute statement at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:248) at org.springframework.orm.hibernate5.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:755) at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:590) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:518) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.retry.interceptor.RetryOperationsInterceptor$1.doWithRetry(RetryOperationsInterceptor.java:91) at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287) at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:164) at org.springframework.retry.interceptor.RetryOperationsInterceptor.invoke(RetryOperationsInterceptor.java:118) at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:153) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy272.pick(Unknown Source) at sun.reflect.GeneratedMethodAccessor1806.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.__invoke(DelegatingMethodAccessorImpl.java:43) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) at com.sun.proxy.$Proxy273.pick(Unknown Source) at com.acme.Dequeuer.run(ProcessEngineImpl.java:298) at java.lang.Thread.run(Thread.java:745) Caused by: org.hibernate.exception.GenericJDBCException: could not execute statement at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:207) at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3184) at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3063) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3443) at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:145) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:589) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463) at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337) at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295) at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468) at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3159) at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352) at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:491) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:582) ... 24 more Caused by: java.sql.SQLException: ORA-08177: impossibile serializzare l'accesso per questa transazione at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447) at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396) at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951) at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513) at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:227) at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531) at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:208) at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1046) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1336) at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3613) at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3694) at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1354) at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:384) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204) ... 43 more
先给你理清楚为什么原来的方法没生效:从异常栈可以看到,ORA-08177是在事务提交阶段抛出的,异常最终由HibernateTransactionManager.doCommit方法处理,它调用的是SessionFactoryUtils.convertHibernateAccessException来转换异常,而不是你重写的LocalSessionFactoryBean.translateExceptionIfPossible——这个方法仅在Spring的HibernateTemplate或者DAO直接操作Session抛出异常时才会被触发,提交阶段的异常走的是另一条处理链路。
下面给你两种可行的解决办法:
方案一:自定义HibernateTransactionManager,重写异常转换逻辑
直接在事务管理器层面处理ORA-08177的转换,这样就能覆盖提交阶段的异常处理:
public class CustomHibernateTransactionManager extends HibernateTransactionManager { @Override protected DataAccessException convertHibernateAccessException(HibernateException ex) { // 检查是否是目标异常 if (ex instanceof HibernateJdbcException) { SQLException sqle = ((HibernateJdbcException) ex).getSQLException(); if ("72000".equals(sqle.getSQLState()) && sqle.getErrorCode() == 8177) { return new CannotSerializeTransactionException("无法序列化事务访问", ex); } } // 其他异常交给父类处理 return super.convertHibernateAccessException(ex); } }
然后在你的配置中(XML或者Java配置类),把原来的HibernateTransactionManager替换成这个自定义类:
如果是XML配置:
<bean id="transactionManager" class="com.yourpackage.CustomHibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean>
如果是Java配置类:
@Bean public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) { CustomHibernateTransactionManager txManager = new CustomHibernateTransactionManager(); txManager.setSessionFactory(sessionFactory); return txManager; }
方案二:自定义Hibernate的SQLExceptionConverter
让Hibernate本身把ORA-08177转换成CannotSerializeTransactionException,这样Spring的SessionFactoryUtils会自动识别这个异常类型,无需修改事务管理器:
public class OracleSQLExceptionConverter extends StandardSQLExceptionConverter { @Override public JDBCException convert(SQLException sqlException, String message, String sql) { // 匹配Oracle序列化异常 if ("72000".equals(sqlException.getSQLState()) && sqlException.getErrorCode() == 8177) { return new CannotSerializeTransactionException(message, sqlException, sql); } // 其他异常交给父类处理 return super.convert(sqlException, message, sql); } }
然后配置Hibernate使用这个转换器,在LocalSessionFactoryBean的Hibernate属性中添加:
如果是XML配置:
<bean id="sessionFactory" class="com.yourpackage.MyLocalSessionFactoryBean"> <!-- 其他配置... --> <property name="hibernateProperties"> <props> <prop key="hibernate.jdbc.sql_exception_converter">com.yourpackage.OracleSQLExceptionConverter</prop> <!-- 其他Hibernate属性 --> </props> </property> </bean>
如果是Java配置类:
@Bean public LocalSessionFactoryBean sessionFactory() { MyLocalSessionFactoryBean sessionFactory = new MyLocalSessionFactoryBean(); // 配置数据源、实体扫描等... Properties hibernateProps = new Properties(); hibernateProps.put("hibernate.jdbc.sql_exception_converter", OracleSQLExceptionConverter.class.getName()); // 其他Hibernate属性设置 sessionFactory.setHibernateProperties(hibernateProps); return sessionFactory; }
这两种方案都能让ORA-08177被转换成CannotSerializeTransactionException,Spring Retry就能识别这个异常并触发重试逻辑了。
内容的提问来源于stack exchange,提问作者usr-local-ΕΨΗΕΛΩΝ

