-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Closed as not planned
Closed as not planned
Copy link
Labels
for: stackoverflowA question that's better suited to stackoverflow.comA question that's better suited to stackoverflow.com
Description
When executing multiple calls to EntityManager#merge() inside a @transactional(REQUIRES_NEW) block, which itself runs inside a @transactional block, the merge hangs indefinitely.
The setup looks like this:
@Service
public class OuterService {
@Autowired
private InnerService innerService;
@Transactional
public void runWithTransaction() {
String callId = UUID.randomUUID().toString();
String externalCallId = UUID.randomUUID().toString();
innerService.save(new ExternalServiceCall(callId, externalCallId));
innerService.load(callId)
.ifPresent(externalServiceCall -> {
externalServiceCall.setExternalCallId(UUID.randomUUID().toString());
innerService.update(externalServiceCall);
});
innerService.load(callId)
.ifPresent(externalServiceCall -> {
externalServiceCall.setExternalCallId(UUID.randomUUID().toString());
innerService.update(externalServiceCall);
});
}
}
@Repository
public class InnerService {
private static final String QUERY_WITH_CALLID = "ExternalServiceCalls.findByCallId";
@PersistenceContext
private EntityManager em;
@Transactional(REQUIRES_NEW)
public ExternalServiceCall save(ExternalServiceCall externalServiceCall) {
em.persist(externalServiceCall);
return externalServiceCall;
}
@Transactional(REQUIRES_NEW)
public ExternalServiceCall update(ExternalServiceCall externalServiceCall) {
em.merge(externalServiceCall);
return externalServiceCall;
}
public Optional<ExternalServiceCall> load(String callId) {
List<ExternalServiceCall> externalServiceCalls = em.createNamedQuery(QUERY_WITH_CALLID, ExternalServiceCall.class)
.setParameter("callId", callId)
.getResultList();
if (externalServiceCalls.isEmpty()) {
return Optional.empty();
}
return Optional.of(externalServiceCalls.get(0));
}
}
@Entity(name = "externalservicecalls")
@Table(name = "externalservicecalls")
@Inheritance(strategy = InheritanceType.JOINED)
@NamedQueries({
@NamedQuery(name = "ExternalServiceCalls.findByCallId",
query = "SELECT e FROM externalservicecalls e WHERE e.callId = :callId")
})
public class ExternalServiceCall {
@Id
private String callId;
@Basic
@Column(nullable = false)
private String externalCallId;
public ExternalServiceCall() {
}
public ExternalServiceCall(String callId, String externalCallId) {
this.callId = callId;
this.externalCallId = externalCallId;
}
public void setExternalCallId(String externalCallId) {
this.externalCallId = externalCallId;
}
}
Calling OuterService#runWithTransactions hangs on the 2nd InnerService#update(), the ThreadDump looks like this:
"main" #1 [5123] prio=5 os_prio=31 cpu=3397.67ms elapsed=12.11s tid=0x000000010152f460 nid=5123 runnable [0x000000016f43d000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.Net.poll(java.base@21.0.7/Native Method)
at sun.nio.ch.NioSocketImpl.park(java.base@21.0.7/NioSocketImpl.java:191)
at sun.nio.ch.NioSocketImpl.park(java.base@21.0.7/NioSocketImpl.java:201)
at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.7/NioSocketImpl.java:309)
at sun.nio.ch.NioSocketImpl.read(java.base@21.0.7/NioSocketImpl.java:346)
at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.7/NioSocketImpl.java:796)
at java.net.Socket$SocketInputStream.read(java.base@21.0.7/Socket.java:1099)
at org.postgresql.core.VisibleBufferedInputStream.readMore(VisibleBufferedInputStream.java:192)
at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:159)
at org.postgresql.core.VisibleBufferedInputStream.ensureBytes(VisibleBufferedInputStream.java:144)
at org.postgresql.core.VisibleBufferedInputStream.read(VisibleBufferedInputStream.java:76)
at org.postgresql.core.PGStream.receiveChar(PGStream.java:477)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2175)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:372)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:525)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:435)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:196)
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:157)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:194)
at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:134)
at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:55)
at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.doStaticUpdate(UpdateCoordinatorStandard.java:781)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.performUpdate(UpdateCoordinatorStandard.java:328)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.update(UpdateCoordinatorStandard.java:245)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:169)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:644)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:511)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:414)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:41)
at org.hibernate.internal.SessionImpl$$Lambda/0x000000a0019aca28.accept(Unknown Source)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1429)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:491)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2354)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1978)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:563)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:795)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:758)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:698)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:416)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728)
at de.woezelmann.nested.transaction.InnerService$$SpringCGLIB$$0.update(<generated>)
at de.woezelmann.nested.transaction.OuterService.lambda$runWithTransaction$1(OuterService.java:31)
at de.woezelmann.nested.transaction.OuterService$$Lambda/0x000000a0019fc1f8.accept(Unknown Source)
at java.util.Optional.ifPresent(java.base@21.0.7/Optional.java:178)
at de.woezelmann.nested.transaction.OuterService.runWithTransaction(OuterService.java:29)
I've created a small test project, that reproduces this behavior with Postgresql and Mysql: https://github.com/woezelmann/spring-nested-transactions
Metadata
Metadata
Assignees
Labels
for: stackoverflowA question that's better suited to stackoverflow.comA question that's better suited to stackoverflow.com