Replace transaction isolation synchronization with ReentrantLock

Closes gh-33546
This commit is contained in:
Juergen Hoeller 2024-09-27 11:36:21 +02:00
parent 8680c43368
commit 594ed95f3c
1 changed files with 28 additions and 17 deletions

View File

@ -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.
*
* <p><b>NOTE: This dialect supports custom isolation levels with limitations.</b>
* 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);
}