Spring+Hibernate+PostgreSQL定时任务事务回滚异常排查求助
定时任务抛出Transaction was marked for rollback only异常的根本原因分析
使用Spring、Hibernate、PostgreSQL技术栈,执行定时任务时出现如下异常:
2024-04-02 11:05:34.035 [acquiring_push_notify_scheduler-10] ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task. org.springframework.orm.jpa.JpaSystemException: Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:333) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521) 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:504) 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.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) at com.sun.proxy.$Proxy868.validateExpiredProfiles(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:217) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ... 22 common frames omitted
对应的业务方法如下:
@Override @Transactional("mainTx") public void validateExpiredProfiles() { List<ProfileData> profiles = verifiedCashierDao.getVerifiedCashiersWithExpiredProfile(LocalDateTime.now().plusDays(NOTIFY_DAY_BEFORE)); LOGGER.info("validateExpiredProfiles: Size of expired profiles {}", profiles.size()); for (ProfileData profile : profiles) { try{ agentDao.changeCashierIdentification(profile.getCashierId(), IdentificationStatusEnum.PROFILE_DATA_EXPIRED); } catch (Exception e){ LOGGER.error("Error on validateExpiredProfiles {} cause {}", profile.getPhone(), e); } } }
Spring XML上下文配置中的定时任务定义为:
<task:scheduled ref="verifiedCashierService" method="validateExpiredProfiles" cron="0 0/5 * * * *" />
根本原因分析
- 子方法抛出RuntimeException触发事务回滚标记:
agentDao.changeCashierIdentification内部可能抛出了RuntimeException(比如数据库约束冲突、Hibernate持久化异常等),尽管你在循环里捕获了Exception,但Spring事务拦截器会监控整个事务上下文——只要事务执行过程中出现未被事务边界捕获的RuntimeException,就会将当前事务标记为rollback-only。当外层事务尝试提交时,就会触发该异常。 - 事务传播特性导致的上下文污染:如果
agentDao.changeCashierIdentification自身也标注了@Transactional,且传播特性为默认的REQUIRED,那么它会加入外层的定时任务事务。当该方法内部抛出异常,即使被外层try-catch捕获,事务上下文已经被标记为回滚,最终外层事务提交必然失败。 - 数据库隐性异常触发回滚:比如PostgreSQL的死锁、连接超时、数据唯一性约束违反等,这类异常会被Hibernate包装成RuntimeException抛出,触发事务的回滚标记,导致后续提交失败。
内容的提问来源于stack exchange,提问作者Nurzamat
相关产品推荐
相关产品推荐

