-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
In what version(s) of Spring Integration are you seeing this issue?
Spring boot version: 2.6.2
Spring data jpa: 2.6.0
Spring Integration version: 5.5.6
Describe the bug
JdbcLockRegistry
is unable to retry a lock when there is a serialization problem and you are using JPATransactionManager
instead of DataSourceTransactionManager
.
As I have an application that uses the JdbcLockRegistry
and JPA with Hibernate, by default, the application uses JPATransactionManager
, but seems like for some transactional errors, JdbcLockRegistry
is unable to retry the lock, as you can see in the following stack trace:
Caused by: org.springframework.dao.CannotAcquireLockException: Failed to lock mutex at 19414b49-c76b-3b77-a05d-3aca07a855aa; nested exception is org.springframework.orm.jpa.JpaSystemException: Unable to commit against JDBC Connection; nested exception is org.hibernate.TransactionException: Unable to commit against JDBC Connection
at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.rethrowAsLockException(JdbcLockRegistry.java:197)
at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.tryLock(JdbcLockRegistry.java:262)
at com.geniussports.geniuslive.ingressmanager.domain.pipeline.ingress.IngressPipelineService.create(IngressPipelineService.kt:35)
at com.geniussports.geniuslive.ingressmanager.application.adapters.pipeline.ingress.gateway.graphql.PipelineMutationResolver.createFromJSON(PipelineMutationResolver.kt:51)
at jdk.internal.reflect.GeneratedMethodAccessor254.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282)
at com.netflix.graphql.dgs.internal.DgsSchemaProvider.invokeDataFetcher(DgsSchemaProvider.kt:402)
at com.netflix.graphql.dgs.internal.DgsSchemaProvider.access$invokeDataFetcher(DgsSchemaProvider.kt:64)
at com.netflix.graphql.dgs.internal.DgsSchemaProvider$createBasicDataFetcher$1.get(DgsSchemaProvider.kt:279)
at com.netflix.graphql.dgs.metrics.micrometer.DgsGraphQLMetricsInstrumentation$instrumentDataFetcher$1.get(DgsGraphQLMetricsInstrumentation.kt:101)
at graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation.lambda$instrumentDataFetcher$0(DataLoaderDispatcherInstrumentation.java:86)
at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:270)
... 76 more
Caused by: org.springframework.orm.jpa.JpaSystemException: Unable to commit against JDBC Connection; nested exception is org.hibernate.TransactionException: Unable to commit against JDBC Connection
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:331)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:566)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
at org.springframework.cloud.sleuth.instrument.tx.TracePlatformTransactionManager.commit(TracePlatformTransactionManager.java:121)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
at org.springframework.integration.jdbc.lock.DefaultLockRepository$$EnhancerBySpringCGLIB$$29ab5ce4.acquire(<generated>)
at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.doLock(JdbcLockRegistry.java:268)
at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.tryLock(JdbcLockRegistry.java:249)
... 88 more
Caused by: org.hibernate.TransactionException: Unable to commit against JDBC Connection
3 lines skipped for [org.hibernate]
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562)
... 100 more
Caused by: org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
Detail: Reason code: Canceled on identification as a pivot, during commit attempt.
Hint: The transaction might succeed if retried.
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2674)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2364)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:354)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:314)
at org.postgresql.jdbc.PgConnection.executeTransactionCommand(PgConnection.java:853)
at org.postgresql.jdbc.PgConnection.commit(PgConnection.java:875)
at com.zaxxer.hikari.pool.ProxyConnection.commit(ProxyConnection.java:387)
at com.zaxxer.hikari.pool.HikariProxyConnection.commit(HikariProxyConnection.java)
1 line skipped for [org.hibernate]
As far as I can tell, seems like to retry a lock we expect the following exceptions to be thrown, as you can see here:
TransientDataAccessException | TransactionTimedOutException | TransactionSystemException
However, Hibernate is throwing a org.hibernate.TransactionException
, and after wrapped in the JpaSystemException
, which is not part of any of the previous exceptions hierarchy, and therefore, the retry is not applied. I created this bug on the Hibernate side as I think we might not have a lot of options in Spring side.
Now, regarding workarounds, I can tell the following:
- Override the
JPATransactionManager
to catch this particular error and throw the right exception, likeorg.springframework.dao.CannotAcquireLockException
, which is aTransientDataAccessException
. This stack overflow question shows how. This solution seems pretty fragile. - Creating a new
XXXXXLockRespository
, which inherit fromDefaultLockRepository
, and override the@Transactional
annotation to explicitly tell which transaction manager to use, and declare a newDataSourceTransactionManager
only to be used for the newXXXXXLockRepository
.
Not sure if we have more options.
To Reproduce
Use a JPATransactionManager
instead of DataSourceTransactionManager
for JdbcLockRegistry
, and generating a serialization database error.
Expected behavior
JdbcLockRegistry
should work with JPATransactionManager
and DataSourceTransactionManager