Skip to content

Commit e4bd134

Browse files
committed
Set and reset shared isolation value within synchronized transaction begin
Since EclipseLink applies a custom transaction isolation value to its shared DatabasePlatform instance, we need to immediately restore the original value after the current value got picked up for JDBC Connection access inside of EclipseLink. In order to not interfere with concurrent transactions, we need to use synchronization around the transaction begin sequence in such a case. Closes gh-29997
1 parent 9decbf2 commit e4bd134

File tree

2 files changed

+45
-14
lines changed

2 files changed

+45
-14
lines changed

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

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -22,6 +22,7 @@
2222
import javax.persistence.EntityManager;
2323
import javax.persistence.PersistenceException;
2424

25+
import org.eclipse.persistence.sessions.DatabaseLogin;
2526
import org.eclipse.persistence.sessions.UnitOfWork;
2627

2728
import org.springframework.jdbc.datasource.ConnectionHandle;
@@ -78,19 +79,37 @@ public void setLazyDatabaseTransaction(boolean lazyDatabaseTransaction) {
7879
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
7980
throws PersistenceException, SQLException, TransactionException {
8081

81-
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
82+
int currentIsolationLevel = definition.getIsolationLevel();
83+
if (currentIsolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
8284
// Pass custom isolation level on to EclipseLink's DatabaseLogin configuration
83-
// (since Spring 4.1.2)
85+
// (since Spring 4.1.2 / revised in 5.3.28)
8486
UnitOfWork uow = entityManager.unwrap(UnitOfWork.class);
85-
uow.getLogin().setTransactionIsolation(definition.getIsolationLevel());
87+
DatabaseLogin databaseLogin = uow.getLogin();
88+
// Synchronize on shared DatabaseLogin instance (-> concurrent transactions)
89+
synchronized (databaseLogin) {
90+
int originalIsolationLevel = databaseLogin.getTransactionIsolation();
91+
// Apply current isolation level value, if necessary.
92+
if (currentIsolationLevel != originalIsolationLevel) {
93+
databaseLogin.setTransactionIsolation(currentIsolationLevel);
94+
}
95+
// Transaction begin including enforced JDBC Connection access
96+
// (picking up current isolation level from DatabaseLogin)
97+
entityManager.getTransaction().begin();
98+
uow.beginEarlyTransaction();
99+
entityManager.unwrap(Connection.class);
100+
// Restore original isolation level value, if necessary.
101+
if (currentIsolationLevel != originalIsolationLevel) {
102+
databaseLogin.setTransactionIsolation(originalIsolationLevel);
103+
}
104+
}
86105
}
87-
88-
entityManager.getTransaction().begin();
89-
90-
if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
91-
// Begin an early transaction to force EclipseLink to get a JDBC Connection
92-
// so that Spring can manage transactions with JDBC as well as EclipseLink.
93-
entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction();
106+
else {
107+
entityManager.getTransaction().begin();
108+
if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
109+
// Begin an early transaction to force EclipseLink to get a JDBC Connection
110+
// so that Spring can manage transactions with JDBC as well as EclipseLink.
111+
entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction();
112+
}
94113
}
95114

96115
return null;

spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -27,8 +27,10 @@
2727
import org.junit.jupiter.api.Test;
2828

2929
import org.springframework.core.testfixture.io.SerializationTestUtils;
30+
import org.springframework.jdbc.datasource.DataSourceUtils;
3031
import org.springframework.orm.jpa.domain.DriversLicense;
3132
import org.springframework.orm.jpa.domain.Person;
33+
import org.springframework.transaction.TransactionDefinition;
3234

3335
import static org.assertj.core.api.Assertions.assertThat;
3436
import static org.assertj.core.api.Assertions.assertThatException;
@@ -114,24 +116,34 @@ public void testGetReferenceWhenNoRow() {
114116
}
115117

116118
@Test
117-
public void testLazyLoading() {
119+
public void testLazyLoading() throws Exception {
118120
try {
119121
Person tony = new Person();
120122
tony.setFirstName("Tony");
121123
tony.setLastName("Blair");
122124
tony.setDriversLicense(new DriversLicense("8439DK"));
123125
sharedEntityManager.persist(tony);
126+
assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation())
127+
.isEqualTo(TransactionDefinition.ISOLATION_READ_COMMITTED);
124128
setComplete();
125129
endTransaction();
126130

131+
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
127132
startNewTransaction();
133+
assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation())
134+
.isEqualTo(TransactionDefinition.ISOLATION_SERIALIZABLE);
128135
sharedEntityManager.clear();
129136
Person newTony = entityManagerFactory.createEntityManager().getReference(Person.class, tony.getId());
130137
assertThat(tony).isNotSameAs(newTony);
131138
endTransaction();
132139

133-
assertThat(newTony.getDriversLicense()).isNotNull();
140+
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
141+
startNewTransaction();
142+
assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation())
143+
.isEqualTo(TransactionDefinition.ISOLATION_READ_COMMITTED);
144+
endTransaction();
134145

146+
assertThat(newTony.getDriversLicense()).isNotNull();
135147
newTony.getDriversLicense().getSerialNumber();
136148
}
137149
finally {

0 commit comments

Comments
 (0)