Skip to content

Commit 3b263c5

Browse files
committed
Synchronize transaction begin for non-lazy default transactions as well
Lazy and read-only database transactions remain exposed to potential isolation level mismatches in case of concurrent transactions with custom isolation levels. Closes gh-29997
1 parent f18a85e commit 3b263c5

File tree

1 file changed

+35
-9
lines changed

1 file changed

+35
-9
lines changed

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

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,20 @@
3434
* {@link org.springframework.orm.jpa.JpaDialect} implementation for Eclipse
3535
* Persistence Services (EclipseLink). Compatible with EclipseLink 3.0/4.0.
3636
*
37-
* <p>By default, this class acquires an early EclipseLink transaction with an early
38-
* JDBC Connection for non-read-only transactions. This allows for mixing JDBC and
39-
* JPA/EclipseLink operations in the same transaction, with cross visibility of
37+
* <p>By default, this dialect acquires an early EclipseLink transaction with an
38+
* early JDBC Connection for non-read-only transactions. This allows for mixing
39+
* JDBC and JPA operations in the same transaction, with cross visibility of
4040
* their impact. If this is not needed, set the "lazyDatabaseTransaction" flag to
4141
* {@code true} or consistently declare all affected transactions as read-only.
4242
* As of Spring 4.1.2, this will reliably avoid early JDBC Connection retrieval
4343
* and therefore keep EclipseLink in shared cache mode.
4444
*
45+
* <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
49+
* {@link #setLazyDatabaseTransaction "lazyDatabaseTransaction" javadoc} for details.
50+
*
4551
* @author Juergen Hoeller
4652
* @since 2.5.2
4753
* @see #setLazyDatabaseTransaction
@@ -65,7 +71,16 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
6571
* even for non-read-only transactions, allowing access to EclipseLink's
6672
* shared cache and following EclipseLink's connection mode configuration,
6773
* assuming that isolation and visibility at the JDBC level are less important.
74+
* <p><b>NOTE: Lazy database transactions are not guaranteed to work reliably
75+
* in combination with custom isolation levels. Use read-only as well as this
76+
* lazy flag with care. If other transactions use custom isolation levels,
77+
* it is not recommended to use read-only and lazy transactions at all.</b>
78+
* Otherwise, you may see non-default isolation levels used during read-only
79+
* or lazy access. If this is not acceptable, don't use read-only and lazy
80+
* next to custom isolation levels in potentially concurrent transactions.
6881
* @see org.eclipse.persistence.sessions.UnitOfWork#beginEarlyTransaction()
82+
* @see TransactionDefinition#isReadOnly()
83+
* @see TransactionDefinition#getIsolationLevel()
6984
*/
7085
public void setLazyDatabaseTransaction(boolean lazyDatabaseTransaction) {
7186
this.lazyDatabaseTransaction = lazyDatabaseTransaction;
@@ -83,7 +98,8 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio
8398
// (since Spring 4.1.2 / revised in 5.3.28)
8499
UnitOfWork uow = entityManager.unwrap(UnitOfWork.class);
85100
DatabaseLogin databaseLogin = uow.getLogin();
86-
// Synchronize on shared DatabaseLogin instance (-> concurrent transactions)
101+
// Synchronize on shared DatabaseLogin instance for consistent isolation level
102+
// set and reset in case of concurrent transactions with different isolation.
87103
synchronized (databaseLogin) {
88104
int originalIsolationLevel = databaseLogin.getTransactionIsolation();
89105
// Apply current isolation level value, if necessary.
@@ -101,13 +117,23 @@ public Object beginTransaction(EntityManager entityManager, TransactionDefinitio
101117
}
102118
}
103119
}
120+
else if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
121+
// Begin an early transaction to force EclipseLink to get a JDBC Connection
122+
// so that Spring can manage transactions with JDBC as well as EclipseLink.
123+
UnitOfWork uow = entityManager.unwrap(UnitOfWork.class);
124+
DatabaseLogin databaseLogin = uow.getLogin();
125+
// Synchronize on shared DatabaseLogin instance for consistently picking up
126+
// 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) {
129+
entityManager.getTransaction().begin();
130+
uow.beginEarlyTransaction();
131+
entityManager.unwrap(Connection.class);
132+
}
133+
}
104134
else {
135+
// Regular transaction begin with lazy database transaction.
105136
entityManager.getTransaction().begin();
106-
if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
107-
// Begin an early transaction to force EclipseLink to get a JDBC Connection
108-
// so that Spring can manage transactions with JDBC as well as EclipseLink.
109-
entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction();
110-
}
111137
}
112138

113139
return null;

0 commit comments

Comments
 (0)