Skip to content

Commit 594ed95

Browse files
committed
Replace transaction isolation synchronization with ReentrantLock
Closes gh-33546
1 parent 8680c43 commit 594ed95

File tree

1 file changed

+28
-17
lines changed

1 file changed

+28
-17
lines changed

spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
1818

1919
import java.sql.Connection;
2020
import java.sql.SQLException;
21+
import java.util.concurrent.locks.Lock;
22+
import java.util.concurrent.locks.ReentrantLock;
2123

2224
import jakarta.persistence.EntityManager;
2325
import jakarta.persistence.PersistenceException;
@@ -39,14 +41,18 @@
3941
* JDBC and JPA operations in the same transaction, with cross visibility of
4042
* their impact. If this is not needed, set the "lazyDatabaseTransaction" flag to
4143
* {@code true} or consistently declare all affected transactions as read-only.
42-
* As of Spring 4.1.2, this will reliably avoid early JDBC Connection retrieval
43-
* and therefore keep EclipseLink in shared cache mode.
44+
* This will reliably avoid early JDBC Connection retrieval and therefore keep
45+
* EclipseLink in shared cache mode.
4446
*
4547
* <p><b>NOTE: This dialect supports custom isolation levels with limitations.</b>
46-
* Consistent isolation level handling is only guaranteed when all Spring transaction
47-
* definitions specify a concrete isolation level, and as of 6.0.10 also when using
48-
* the default isolation level with non-readOnly and non-lazy transactions. See the
48+
* Consistent isolation level handling is only guaranteed when all Spring
49+
* transaction definitions specify a concrete isolation level and when using the
50+
* default isolation level with non-readOnly and non-lazy transactions; see the
4951
* {@link #setLazyDatabaseTransaction "lazyDatabaseTransaction" javadoc} for details.
52+
* Internal locking happens for transaction isolation management in EclipseLink's
53+
* DatabaseLogin, at the granularity of the {@code EclipseLinkJpaDialect} instance;
54+
* for independent persistence units with different target databases, use distinct
55+
* {@code EclipseLinkJpaDialect} instances in order to minimize the locking impact.
5056
*
5157
* @author Juergen Hoeller
5258
* @since 2.5.2
@@ -58,6 +64,8 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
5864

5965
private boolean lazyDatabaseTransaction = false;
6066

67+
private final Lock transactionIsolationLock = new ReentrantLock();
68+
6169

6270
/**
6371
* Set whether to lazily start a database resource transaction within a
@@ -94,13 +102,13 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio
94102

95103
int currentIsolationLevel = definition.getIsolationLevel();
96104
if (currentIsolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
97-
// Pass custom isolation level on to EclipseLink's DatabaseLogin configuration
98-
// (since Spring 4.1.2 / revised in 5.3.28)
105+
// Pass custom isolation level on to EclipseLink's DatabaseLogin configuration.
99106
UnitOfWork uow = entityManager.unwrap(UnitOfWork.class);
100107
DatabaseLogin databaseLogin = uow.getLogin();
101-
// Synchronize on shared DatabaseLogin instance for consistent isolation level
108+
// Lock around shared DatabaseLogin instance for consistent isolation level
102109
// set and reset in case of concurrent transactions with different isolation.
103-
synchronized (databaseLogin) {
110+
this.transactionIsolationLock.lock();
111+
try {
104112
int originalIsolationLevel = databaseLogin.getTransactionIsolation();
105113
// Apply current isolation level value, if necessary.
106114
if (currentIsolationLevel != originalIsolationLevel) {
@@ -116,20 +124,26 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio
116124
databaseLogin.setTransactionIsolation(originalIsolationLevel);
117125
}
118126
}
127+
finally {
128+
this.transactionIsolationLock.unlock();
129+
}
119130
}
120131
else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
121132
// Begin an early transaction to force EclipseLink to get a JDBC Connection
122133
// so that Spring can manage transactions with JDBC as well as EclipseLink.
123134
UnitOfWork uow = entityManager.unwrap(UnitOfWork.class);
124-
DatabaseLogin databaseLogin = uow.getLogin();
125-
// Synchronize on shared DatabaseLogin instance for consistently picking up
135+
// Lock around shared DatabaseLogin instance for consistently picking up
126136
// the default isolation level even in case of concurrent transactions with
127-
// a custom isolation level (see above), as of 6.0.10
128-
synchronized (databaseLogin) {
137+
// a custom isolation level (see above).
138+
this.transactionIsolationLock.lock();
139+
try {
129140
entityManager.getTransaction().begin();
130141
uow.beginEarlyTransaction();
131142
entityManager.unwrap(Connection.class);
132143
}
144+
finally {
145+
this.transactionIsolationLock.unlock();
146+
}
133147
}
134148
else {
135149
// Regular transaction begin with lazy database transaction.
@@ -143,9 +157,6 @@ else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
143157
public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly)
144158
throws PersistenceException, SQLException {
145159

146-
// As of Spring 4.1.2, we're using a custom ConnectionHandle for lazy retrieval
147-
// of the underlying Connection (allowing for deferred internal transaction begin
148-
// within the EclipseLink EntityManager)
149160
return new EclipseLinkConnectionHandle(entityManager);
150161
}
151162

0 commit comments

Comments
 (0)