diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java index 30db932f16..4c0ab302aa 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2023 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. @@ -22,6 +22,7 @@ import java.sql.SQLException; import javax.persistence.EntityManager; import javax.persistence.PersistenceException; +import org.eclipse.persistence.sessions.DatabaseLogin; import org.eclipse.persistence.sessions.UnitOfWork; import org.springframework.jdbc.datasource.ConnectionHandle; @@ -78,19 +79,37 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition) throws PersistenceException, SQLException, TransactionException { - if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + int currentIsolationLevel = definition.getIsolationLevel(); + if (currentIsolationLevel != TransactionDefinition.ISOLATION_DEFAULT) { // Pass custom isolation level on to EclipseLink's DatabaseLogin configuration - // (since Spring 4.1.2) + // (since Spring 4.1.2 / revised in 5.3.28) UnitOfWork uow = entityManager.unwrap(UnitOfWork.class); - uow.getLogin().setTransactionIsolation(definition.getIsolationLevel()); + DatabaseLogin databaseLogin = uow.getLogin(); + // Synchronize on shared DatabaseLogin instance (-> concurrent transactions) + synchronized (databaseLogin) { + int originalIsolationLevel = databaseLogin.getTransactionIsolation(); + // Apply current isolation level value, if necessary. + if (currentIsolationLevel != originalIsolationLevel) { + databaseLogin.setTransactionIsolation(currentIsolationLevel); + } + // Transaction begin including enforced JDBC Connection access + // (picking up current isolation level from DatabaseLogin) + entityManager.getTransaction().begin(); + uow.beginEarlyTransaction(); + entityManager.unwrap(Connection.class); + // Restore original isolation level value, if necessary. + if (currentIsolationLevel != originalIsolationLevel) { + databaseLogin.setTransactionIsolation(originalIsolationLevel); + } + } } - - entityManager.getTransaction().begin(); - - 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. - entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction(); + else { + entityManager.getTransaction().begin(); + 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. + entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction(); + } } return null; diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java index 81c7b0b912..c403a9aa68 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/AbstractContainerEntityManagerFactoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -27,8 +27,10 @@ import javax.persistence.Query; import org.junit.jupiter.api.Test; import org.springframework.core.testfixture.io.SerializationTestUtils; +import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.orm.jpa.domain.DriversLicense; import org.springframework.orm.jpa.domain.Person; +import org.springframework.transaction.TransactionDefinition; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; @@ -114,24 +116,34 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests } @Test - public void testLazyLoading() { + public void testLazyLoading() throws Exception { try { Person tony = new Person(); tony.setFirstName("Tony"); tony.setLastName("Blair"); tony.setDriversLicense(new DriversLicense("8439DK")); sharedEntityManager.persist(tony); + assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation()) + .isEqualTo(TransactionDefinition.ISOLATION_READ_COMMITTED); setComplete(); endTransaction(); + transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); startNewTransaction(); + assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation()) + .isEqualTo(TransactionDefinition.ISOLATION_SERIALIZABLE); sharedEntityManager.clear(); Person newTony = entityManagerFactory.createEntityManager().getReference(Person.class, tony.getId()); assertThat(tony).isNotSameAs(newTony); endTransaction(); - assertThat(newTony.getDriversLicense()).isNotNull(); + transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); + startNewTransaction(); + assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation()) + .isEqualTo(TransactionDefinition.ISOLATION_READ_COMMITTED); + endTransaction(); + assertThat(newTony.getDriversLicense()).isNotNull(); newTony.getDriversLicense().getSerialNumber(); } finally {