Skip to content

Commit

Permalink
Replace transaction isolation synchronization with ReentrantLock
Browse files Browse the repository at this point in the history
Closes gh-33546
  • Loading branch information
jhoeller committed Sep 27, 2024
1 parent 8680c43 commit 594ed95
Showing 1 changed file with 28 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,6 +18,8 @@

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;
Expand All @@ -39,14 +41,18 @@
* 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
Expand All @@ -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
Expand Down Expand Up @@ -94,13 +102,13 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio

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) {
Expand All @@ -116,20 +124,26 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio
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.
Expand All @@ -143,9 +157,6 @@ else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
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);
}

Expand Down

0 comments on commit 594ed95

Please sign in to comment.