From 784efee722f8dd0a452163cf9b211717e99e1976 Mon Sep 17 00:00:00 2001 From: Christian Schuster Date: Tue, 17 Sep 2024 11:31:44 +0200 Subject: [PATCH] use ReentrantLock to synchronize on shared DatabaseLogin instance --- .../orm/jpa/vendor/EclipseLinkJpaDialect.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java index c26b9bb27b2a..9c0a02503656 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java @@ -18,6 +18,8 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; @@ -29,6 +31,7 @@ import org.springframework.orm.jpa.DefaultJpaDialect; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; +import org.springframework.util.ConcurrentReferenceHashMap; /** * {@link org.springframework.orm.jpa.JpaDialect} implementation for Eclipse @@ -56,6 +59,11 @@ @SuppressWarnings("serial") public class EclipseLinkJpaDialect extends DefaultJpaDialect { + /** + * Locks for virtual thread friendly "synchronization" on the {@link DatabaseLogin} instance. + */ + private static final Map LOCKS = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); + private boolean lazyDatabaseTransaction = false; @@ -100,7 +108,9 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio DatabaseLogin databaseLogin = uow.getLogin(); // Synchronize on shared DatabaseLogin instance for consistent isolation level // set and reset in case of concurrent transactions with different isolation. - synchronized (databaseLogin) { + ReentrantLock lock = LOCKS.computeIfAbsent(databaseLogin, k -> new ReentrantLock()); + lock.lock(); + try { int originalIsolationLevel = databaseLogin.getTransactionIsolation(); // Apply current isolation level value, if necessary. if (currentIsolationLevel != originalIsolationLevel) { @@ -115,6 +125,8 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio if (currentIsolationLevel != originalIsolationLevel) { databaseLogin.setTransactionIsolation(originalIsolationLevel); } + } finally { + lock.unlock(); } } else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) { @@ -125,10 +137,14 @@ else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) { // Synchronize on shared DatabaseLogin instance for consistently picking up // the default isolation level even in case of concurrent transactions with // a custom isolation level (see above), as of 6.0.10 - synchronized (databaseLogin) { + ReentrantLock lock = LOCKS.computeIfAbsent(databaseLogin, k -> new ReentrantLock()); + lock.lock(); + try { entityManager.getTransaction().begin(); uow.beginEarlyTransaction(); entityManager.unwrap(Connection.class); + } finally { + lock.unlock(); } } else {