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 c26b9bb27b..5b313816d1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.springframework.orm.jpa.vendor; import java.sql.Connection; import java.sql.SQLException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceException; @@ -39,14 +41,18 @@ import org.springframework.transaction.TransactionException; * JDBC and JPA operations in the same transaction, with cross visibility of * their impact. If this is not needed, set the "lazyDatabaseTransaction" flag to * {@code true} or consistently declare all affected transactions as read-only. - * As of Spring 4.1.2, this will reliably avoid early JDBC Connection retrieval - * and therefore keep EclipseLink in shared cache mode. + * This will reliably avoid early JDBC Connection retrieval and therefore keep + * EclipseLink in shared cache mode. * *
NOTE: This dialect supports custom isolation levels with limitations. - * Consistent isolation level handling is only guaranteed when all Spring transaction - * definitions specify a concrete isolation level, and as of 6.0.10 also when using - * the default isolation level with non-readOnly and non-lazy transactions. See the + * Consistent isolation level handling is only guaranteed when all Spring + * transaction definitions specify a concrete isolation level and when using the + * default isolation level with non-readOnly and non-lazy transactions; see the * {@link #setLazyDatabaseTransaction "lazyDatabaseTransaction" javadoc} for details. + * Internal locking happens for transaction isolation management in EclipseLink's + * DatabaseLogin, at the granularity of the {@code EclipseLinkJpaDialect} instance; + * for independent persistence units with different target databases, use distinct + * {@code EclipseLinkJpaDialect} instances in order to minimize the locking impact. * * @author Juergen Hoeller * @since 2.5.2 @@ -58,6 +64,8 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { private boolean lazyDatabaseTransaction = false; + private final Lock transactionIsolationLock = new ReentrantLock(); + /** * Set whether to lazily start a database resource transaction within a @@ -94,13 +102,13 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { int currentIsolationLevel = definition.getIsolationLevel(); if (currentIsolationLevel != TransactionDefinition.ISOLATION_DEFAULT) { - // Pass custom isolation level on to EclipseLink's DatabaseLogin configuration - // (since Spring 4.1.2 / revised in 5.3.28) + // Pass custom isolation level on to EclipseLink's DatabaseLogin configuration. UnitOfWork uow = entityManager.unwrap(UnitOfWork.class); DatabaseLogin databaseLogin = uow.getLogin(); - // Synchronize on shared DatabaseLogin instance for consistent isolation level + // Lock around shared DatabaseLogin instance for consistent isolation level // set and reset in case of concurrent transactions with different isolation. - synchronized (databaseLogin) { + this.transactionIsolationLock.lock(); + try { int originalIsolationLevel = databaseLogin.getTransactionIsolation(); // Apply current isolation level value, if necessary. if (currentIsolationLevel != originalIsolationLevel) { @@ -116,20 +124,26 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { databaseLogin.setTransactionIsolation(originalIsolationLevel); } } + finally { + this.transactionIsolationLock.unlock(); + } } else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) { // Begin an early transaction to force EclipseLink to get a JDBC Connection // so that Spring can manage transactions with JDBC as well as EclipseLink. UnitOfWork uow = entityManager.unwrap(UnitOfWork.class); - DatabaseLogin databaseLogin = uow.getLogin(); - // Synchronize on shared DatabaseLogin instance for consistently picking up + // Lock around 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) { + // a custom isolation level (see above). + this.transactionIsolationLock.lock(); + try { entityManager.getTransaction().begin(); uow.beginEarlyTransaction(); entityManager.unwrap(Connection.class); } + finally { + this.transactionIsolationLock.unlock(); + } } else { // Regular transaction begin with lazy database transaction. @@ -143,9 +157,6 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly) throws PersistenceException, SQLException { - // As of Spring 4.1.2, we're using a custom ConnectionHandle for lazy retrieval - // of the underlying Connection (allowing for deferred internal transaction begin - // within the EclipseLink EntityManager) return new EclipseLinkConnectionHandle(entityManager); }