Replace transaction isolation synchronization with ReentrantLock
Closes gh-33546
This commit is contained in:
parent
8680c43368
commit
594ed95f3c
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue